From 7b3fd5c1fb6552136af40470480c75dd9955578d Mon Sep 17 00:00:00 2001 From: Sarah Lander <70885646+salander85@users.noreply.github.com> Date: Mon, 9 Oct 2023 10:55:36 +0200 Subject: [PATCH] Migrate abstract syncer (#484) * Migrate abstract syncer, utils and tests (only compilable) * Adjust javadoc * Use consistent return type --- .../sync/BaseSyncStatisticsDeserializer.java | 5 +- .../commercetools/project/sync/Syncer.java | 135 +++++++------- .../model/response/LastSyncCustomObject.java | 1 - .../sync/service/CustomObjectService.java | 50 +++++- .../service/impl/CustomObjectServiceImpl.java | 147 +++++++--------- .../project/sync/util/SyncUtils.java | 15 +- .../impl/CustomObjectServiceImplTest.java | 164 +++++++++--------- .../project/sync/util/SyncUtilsTest.java | 15 +- .../project/sync/util/TestUtils.java | 97 +++++++---- 9 files changed, 344 insertions(+), 285 deletions(-) diff --git a/src/main/java/com/commercetools/project/sync/BaseSyncStatisticsDeserializer.java b/src/main/java/com/commercetools/project/sync/BaseSyncStatisticsDeserializer.java index 6f9330f7..645708c7 100644 --- a/src/main/java/com/commercetools/project/sync/BaseSyncStatisticsDeserializer.java +++ b/src/main/java/com/commercetools/project/sync/BaseSyncStatisticsDeserializer.java @@ -3,13 +3,14 @@ import com.commercetools.sync.commons.helpers.BaseSyncStatistics; import com.commercetools.sync.customers.helpers.CustomerSyncStatistics; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; +// This class compiles but not tested yet +// TODO: Test class and adjust logic if needed public class BaseSyncStatisticsDeserializer extends StdDeserializer { public BaseSyncStatisticsDeserializer() { @@ -22,7 +23,7 @@ public BaseSyncStatisticsDeserializer(final Class vc) { @Override public BaseSyncStatistics deserialize(JsonParser jsonParser, DeserializationContext ctxt) - throws IOException, JsonProcessingException { + throws IOException { final ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec(); final JsonNode syncStatisticsNode = mapper.readTree(jsonParser); // since BaseSyncStatistics is abstract and there's no difference in the Subclasses except diff --git a/src/main/java/com/commercetools/project/sync/Syncer.java b/src/main/java/com/commercetools/project/sync/Syncer.java index a46f9ab6..52928b93 100644 --- a/src/main/java/com/commercetools/project/sync/Syncer.java +++ b/src/main/java/com/commercetools/project/sync/Syncer.java @@ -1,9 +1,21 @@ package com.commercetools.project.sync; +import static com.commercetools.api.client.QueryUtils.queryAll; import static com.commercetools.project.sync.util.SyncUtils.getSyncModuleName; -import static com.commercetools.sync.commons.utils.CtpQueryUtils.queryAll; import static java.lang.String.format; +import com.commercetools.api.client.ProjectApiRoot; +import com.commercetools.api.models.DomainResource; +import com.commercetools.api.models.PagedQueryResourceRequest; +import com.commercetools.api.models.ResourcePagedQueryResponse; +import com.commercetools.api.models.ResourceUpdateAction; +import com.commercetools.api.models.WithKey; +import com.commercetools.api.models.category.Category; +import com.commercetools.api.models.category.CategoryDraft; +import com.commercetools.api.models.common.BaseResource; +import com.commercetools.api.models.custom_object.CustomObject; +import com.commercetools.api.models.product.ProductDraft; +import com.commercetools.api.models.product.ProductProjection; import com.commercetools.project.sync.model.response.LastSyncCustomObject; import com.commercetools.project.sync.service.CustomObjectService; import com.commercetools.sync.commons.BaseSync; @@ -11,12 +23,7 @@ import com.commercetools.sync.commons.helpers.BaseSyncStatistics; import com.commercetools.sync.commons.utils.CaffeineReferenceIdToKeyCacheImpl; import com.commercetools.sync.commons.utils.ReferenceIdToKeyCache; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.customobjects.CustomObject; -import io.sphere.sdk.models.ResourceView; -import io.sphere.sdk.models.Versioned; -import io.sphere.sdk.queries.QueryDsl; -import io.sphere.sdk.queries.QueryPredicate; +import io.vrap.rmf.base.client.ApiHttpResponse; import java.time.Clock; import java.time.ZonedDateTime; import java.util.List; @@ -28,39 +35,41 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +// This class compiles but not tested yet +// TODO: Test class and adjust logic if needed /** * Base class of the syncer that handles syncing a resource from a source CTP project to a target * CTP project. * - * @param The type of the resource to update (e.g. {@link - * io.sphere.sdk.products.ProductProjection}, {@link io.sphere.sdk.categories.CategoryDraft}, - * etc..) - * @param The type of the resource to create (e.g. {@link io.sphere.sdk.products.Product}, - * {@link io.sphere.sdk.categories.Category}, etc..) - * @param The type of the resource draft (e.g. {@link io.sphere.sdk.products.ProductDraft}, - * {@link io.sphere.sdk.categories.CategoryDraft}, etc..) - * @param The type of the sync statistics resulting from the sync process (e.g. {@link - * com.commercetools.sync.products.helpers.ProductSyncStatistics}, {@link + * @param The type of the resource to update (e.g. {@link ProductProjection}, {@link + * Category}, etc..) + * @param The update actions to update the resource with (e.g. + * {@link com.commercetools.api.models.product.ProductUpdateAction}) + * @param The type of the resource draft (e.g. {@link ProductDraft}, {@link + * CategoryDraft}, etc..) + * @param The type of the sync statistics resulting from the sync process (e.g. + * {@link com.commercetools.sync.products.helpers.ProductSyncStatistics}, {@link * com.commercetools.sync.categories.helpers.CategorySyncStatistics}, etc..) - * @param The type of the sync options used for the sync (e.g. {@link + * @param The type of the sync options used for the sync (e.g. {@link * com.commercetools.sync.products.ProductSyncOptions}, {@link * com.commercetools.sync.categories.CategorySyncOptions}, etc..) - * @param The type of the query used to query for the source resources (e.g. {@link - * io.sphere.sdk.products.queries.ProductProjectionQuery}, {@link - * io.sphere.sdk.categories.queries.CategoryQuery}, etc..) - * @param The type of the sync instance used to execute the sync process (e.g. {@link + * @param The type of the query used to query for the source resources (e.g. {@link + * com.commercetools.api.client.ByProjectKeyProductProjectionsGet}, {@link + * com.commercetools.api.client.ByProjectKeyCategoriesGet}, etc..) + * @param The type of the sync instance used to execute the sync process (e.g. {@link * com.commercetools.sync.products.ProductSync}, {@link * com.commercetools.sync.categories.CategorySync}, etc..) */ public abstract class Syncer< - RU extends ResourceView, - RC extends ResourceView, - RD, - V extends Versioned, - S extends BaseSyncStatistics, - O extends BaseSyncOptions, - Q extends QueryDsl, - B extends BaseSync> { + ResourceT extends BaseResource & DomainResource & WithKey, + ResourceUpdateActionT extends ResourceUpdateAction, + ResourceDraftT, + SyncStatisticsT extends BaseSyncStatistics, + SyncOptionsT extends BaseSyncOptions, + PagedQueryT extends PagedQueryResourceRequest, + PagedQueryResponseT extends ResourcePagedQueryResponse, + BaseSyncT extends + BaseSync> { private static final Logger LOGGER = LoggerFactory.getLogger(Syncer.class); @@ -70,9 +79,9 @@ public abstract class Syncer< protected static final ReferenceIdToKeyCache referenceIdToKeyCache = new CaffeineReferenceIdToKeyCacheImpl(); - private final B sync; - private final SphereClient sourceClient; - private final SphereClient targetClient; + private final BaseSyncT sync; + private final ProjectApiRoot sourceClient; + private final ProjectApiRoot targetClient; private final CustomObjectService customObjectService; private final Clock clock; @@ -90,9 +99,9 @@ public abstract class Syncer< * @param clock the clock to record the time for calculating the sync duration. */ public Syncer( - @Nonnull final B sync, - @Nonnull final SphereClient sourceClient, - @Nonnull final SphereClient targetClient, + @Nonnull final BaseSyncT sync, + @Nonnull final ProjectApiRoot sourceClient, + @Nonnull final ProjectApiRoot targetClient, @Nonnull final CustomObjectService customObjectService, @Nonnull final Clock clock) { this.sync = sync; @@ -103,13 +112,13 @@ public Syncer( } /** - * Fetches the sourceClient's project resources of type {@code T} with all needed references - * expanded and treats each page as a batch to the sync process. Then executes the sync process of - * on every page fetched from the source project sequentially. It then returns a completion stage + * Fetches the sourceClient's project resources of type {@code ResourceT} with all needed references + * expanded and treats each page as a batch to the sync process. Then executes the sync process + * on every page sequentially. It then returns a completion stage * containing a {@link Void} result after the execution of the sync process and logging the * result. * - *

Note: If {@code isFullSync} is {@code false}, i.e. a delta sync is required, the method + *

Note: If {@param isFullSync} is {@code false}, i.e. a delta sync is required, the method * checks if there was a last sync time stamp persisted as a custom object in the target project * for this specific source project and sync module. If there is, it will sync only the resources * which were modified after the last sync time stamp and before the start of this sync. @@ -122,10 +131,10 @@ public Syncer( */ public CompletionStage sync(@Nullable final String runnerName, final boolean isFullSync) { - final String sourceProjectKey = sourceClient.getConfig().getProjectKey(); + final String sourceProjectKey = sourceClient.getProjectKey(); final String syncModuleName = getSyncModuleName(sync.getClass()); if (getLoggerInstance().isInfoEnabled()) { - final String targetProjectKey = targetClient.getConfig().getProjectKey(); + final String targetProjectKey = targetClient.getProjectKey(); getLoggerInstance() .info( format( @@ -179,7 +188,7 @@ private CompletionStage syncResourcesSinceLastSync( } @Nonnull - private CompletionStage getQueryOfResourcesSinceLastSync( + private CompletionStage getQueryOfResourcesSinceLastSync( @Nonnull final String sourceProjectKey, @Nonnull final String syncModuleName, @Nullable final String runnerName, @@ -190,7 +199,6 @@ private CompletionStage getQueryOfResourcesSinceLastSync( .thenApply( customObjectOptional -> customObjectOptional - .map(CustomObject::getValue) .map(LastSyncCustomObject::getLastSyncTimestamp) .map( lastSyncTimestamp -> @@ -201,21 +209,21 @@ private CompletionStage getQueryOfResourcesSinceLastSync( } @Nonnull - private Q getQueryWithTimeBoundedPredicate( + private PagedQueryT getQueryWithTimeBoundedPredicate( @Nonnull final ZonedDateTime lowerBound, @Nonnull final ZonedDateTime upperBound) { - final QueryPredicate queryPredicate = - QueryPredicate.of( - format( - "lastModifiedAt >= \"%s\" AND lastModifiedAt <= \"%s\"", lowerBound, upperBound)); - return getQuery().plusPredicates(queryPredicate); + return (PagedQueryT) + getQuery() + .withWhere("lastModifiedAt >= \":lower\" AND lastModifiedAt <= \":upper\"") + .withPredicateVar("lower", lowerBound) + .withPredicateVar("upper", upperBound); } @Nonnull - private CompletionStage sync(@Nonnull final Q queryResourcesSinceLastSync) { + private CompletionStage sync(@Nonnull final PagedQueryT queryResourcesSinceLastSync) { final long timeBeforeSync = clock.millis(); - return queryAll(sourceClient, queryResourcesSinceLastSync, this::syncPage) + return queryAll(queryResourcesSinceLastSync, this::syncPage) .thenApply( ignoredResult -> { final long timeAfterSync = clock.millis(); @@ -224,7 +232,7 @@ private CompletionStage sync(@Nonnull final Q queryResourcesSinceLastSync) } @Nonnull - private CompletionStage> createNewLastSyncCustomObject( + private CompletableFuture> createNewLastSyncCustomObject( @Nonnull final String sourceProjectKey, @Nonnull final String syncModuleName, @Nullable final String runnerName, @@ -240,7 +248,7 @@ private CompletionStage> createNewLastSyncCus final ZonedDateTime lastSyncTimestampMinusBuffer = newLastSyncTimestamp.minusMinutes(bufferInMinutes); - final LastSyncCustomObject lastSyncCustomObject = + final LastSyncCustomObject lastSyncCustomObject = LastSyncCustomObject.of( lastSyncTimestampMinusBuffer, sync.getStatistics(), syncDurationInMillis); @@ -253,25 +261,26 @@ private CompletionStage> createNewLastSyncCus * {@link CompletableFuture} of each sync process on the given page as a batch. */ @Nonnull - private S syncPage(@Nonnull final List page) { + private SyncStatisticsT syncPage(@Nonnull final List page) { return transform(page).thenCompose(sync::sync).toCompletableFuture().join(); } /** - * Given a {@link List} representing a page of resources of type {@code T}, this method creates a - * a list of drafts of type {@link S} where reference ids of the references are replaced with keys - * and are ready for reference resolution by the sync process. + * Given a {@link List} representing a page of resources of type {@link ResourceT}, this method creates + * a list of drafts of type {@link ResourceDraftT} where reference ids of the references are + * replaced with keys and are ready for reference resolution by the sync process. * - * @return a {@link CompletionStage} containing a list of drafts of type {@link S} after being - * transformed from type {@link RU}. + * @return a {@link CompletionStage} containing a list of drafts of type {@link ResourceDraftT} + * after being transformed from type {@link ResourceT}. */ @Nonnull - protected abstract CompletionStage> transform(@Nonnull final List page); + protected abstract CompletionStage> transform( + @Nonnull final List page); @Nonnull - protected abstract Q getQuery(); + protected abstract PagedQueryT getQuery(); - public B getSync() { + public BaseSyncT getSync() { return sync; } @@ -279,7 +288,7 @@ public B getSync() { protected abstract Logger getLoggerInstance(); @Nonnull - public SphereClient getSourceClient() { + public ProjectApiRoot getSourceClient() { return sourceClient; } } diff --git a/src/main/java/com/commercetools/project/sync/model/response/LastSyncCustomObject.java b/src/main/java/com/commercetools/project/sync/model/response/LastSyncCustomObject.java index a4a69684..00db3af9 100644 --- a/src/main/java/com/commercetools/project/sync/model/response/LastSyncCustomObject.java +++ b/src/main/java/com/commercetools/project/sync/model/response/LastSyncCustomObject.java @@ -92,7 +92,6 @@ public long getLastSyncDurationInMillis() { // Setters are needed for the 'com.fasterxml.jackson' deserialization, for example, when fetching // from CTP custom objects. - public void setLastSyncTimestamp(@Nonnull final ZonedDateTime lastSyncTimestamp) { this.lastSyncTimestamp = lastSyncTimestamp; } diff --git a/src/main/java/com/commercetools/project/sync/service/CustomObjectService.java b/src/main/java/com/commercetools/project/sync/service/CustomObjectService.java index 769c2888..35499443 100644 --- a/src/main/java/com/commercetools/project/sync/service/CustomObjectService.java +++ b/src/main/java/com/commercetools/project/sync/service/CustomObjectService.java @@ -1,26 +1,66 @@ package com.commercetools.project.sync.service; +import com.commercetools.api.models.custom_object.CustomObject; import com.commercetools.project.sync.model.response.LastSyncCustomObject; -import io.sphere.sdk.customobjects.CustomObject; +import io.vrap.rmf.base.client.ApiHttpResponse; import java.time.ZonedDateTime; import java.util.Optional; -import java.util.concurrent.CompletionStage; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import javax.annotation.Nullable; public interface CustomObjectService { + + /** + * Creates or updates a custom object with the container named + * 'commercetools-project-sync.{@param runnerName}.{@param syncModuleName}.timestampGenerator' and + * key equals 'timestampGenerator' and then reading the 'lastModifiedAt' field of the persisted custom + * object and returning it. + * + * @param syncModuleName the name of the resource being synced. E.g. productSync, categorySync, + * etc.. + * @param runnerName the name of this specific running sync instance defined by the user. + * @return a {@link CompletableFuture} containing the current CTP timestamp as {@link + * ZonedDateTime}. + */ @Nonnull - CompletionStage getCurrentCtpTimestamp( + CompletableFuture getCurrentCtpTimestamp( @Nullable final String runnerName, @Nonnull final String syncModuleName); + /** + * Get's a custom object which has a container named 'commercetools-project-sync.{@param + * runnerName}.{@param syncModuleName}' and key equals {@param sourceProjectKey}. The value of the + * fetched custom object is deserialized and wrapped in an {@link Optional}. + * + * @param sourceProjectKey the source project from which the data is coming. + * @param syncModuleName the name of the resource being synced. E.g. productSync, categorySync, + * etc.. + * @param runnerName the name of this specific running sync instance defined by the user. + * @return the custom object with container 'commercetools-project-sync.{@param + * runnerName}.{@param syncModuleName}' and key '{@param sourceProjectKey}', wrapped in an {@link Optional} as a + * result of a {@link CompletableFuture}. + */ @Nonnull - CompletionStage>> getLastSyncCustomObject( + CompletableFuture> getLastSyncCustomObject( @Nonnull final String sourceProjectKey, @Nonnull final String syncModuleName, @Nullable final String runnerName); + /** + * Creates (or updates an already existing) custom object, with the container named + * 'commercetools-project-sync.{@param runnerName}.{@param syncModuleName}' and key equals {@param + * sourceProjectKey}, enriched with the information in the passed {@link LastSyncCustomObject} + * param. + * + * @param sourceProjectKey the source project key from which the data is coming. + * @param syncModuleName the name of the resource being synced. E.g. productSync, categorySync, + * etc.. + * @param runnerName the name of this specific running sync instance defined by the user. + * @param lastSyncCustomObject contains information about the last sync instance. + * @return a {@link CompletableFuture} of {@link ApiHttpResponse} with the created/updated custom object resource. + */ @Nonnull - CompletionStage> createLastSyncCustomObject( + CompletableFuture> createLastSyncCustomObject( @Nonnull final String sourceProjectKey, @Nonnull final String syncModuleName, @Nullable final String runnerName, diff --git a/src/main/java/com/commercetools/project/sync/service/impl/CustomObjectServiceImpl.java b/src/main/java/com/commercetools/project/sync/service/impl/CustomObjectServiceImpl.java index b36bc382..d1908c84 100644 --- a/src/main/java/com/commercetools/project/sync/service/impl/CustomObjectServiceImpl.java +++ b/src/main/java/com/commercetools/project/sync/service/impl/CustomObjectServiceImpl.java @@ -2,133 +2,110 @@ import static com.commercetools.project.sync.util.SyncUtils.buildCurrentCtpTimestampContainerName; import static com.commercetools.project.sync.util.SyncUtils.buildLastSyncTimestampContainerName; -import static java.lang.String.format; +import com.commercetools.api.client.ProjectApiRoot; +import com.commercetools.api.models.custom_object.CustomObject; +import com.commercetools.api.models.custom_object.CustomObjectDraft; +import com.commercetools.api.models.custom_object.CustomObjectDraftBuilder; import com.commercetools.project.sync.model.response.LastSyncCustomObject; import com.commercetools.project.sync.service.CustomObjectService; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.customobjects.CustomObject; -import io.sphere.sdk.customobjects.CustomObjectDraft; -import io.sphere.sdk.customobjects.commands.CustomObjectUpsertCommand; -import io.sphere.sdk.customobjects.queries.CustomObjectQuery; -import io.sphere.sdk.models.ResourceView; -import io.sphere.sdk.queries.PagedQueryResult; -import io.sphere.sdk.queries.QueryPredicate; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.vrap.rmf.base.client.ApiHttpResponse; +import io.vrap.rmf.base.client.utils.json.JsonUtils; import java.time.ZonedDateTime; -import java.util.Collection; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.CompletionStage; -import java.util.stream.Stream; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import javax.annotation.Nullable; +// This class compiles but not tested yet +// TODO: Test class and adjust logic if needed public class CustomObjectServiceImpl implements CustomObjectService { public static final String TIMESTAMP_GENERATOR_KEY = "timestampGenerator"; - private final SphereClient sphereClient; + private final ProjectApiRoot ctpClient; - public CustomObjectServiceImpl(@Nonnull final SphereClient sphereClient) { - this.sphereClient = sphereClient; + public CustomObjectServiceImpl(@Nonnull final ProjectApiRoot ctpClient) { + this.ctpClient = ctpClient; } - /** - * Gets the current timestamp of CTP by creating/updating a custom object with the container: - * 'commercetools-project-sync.{@code runnerName}.{@code syncModuleName}.timestampGenerator' and - * key: 'timestampGenerator' and then returning the lastModifiedAt of this created/updated custom - * object. - * - * @param syncModuleName the name of the resource being synced. E.g. productSync, categorySync, - * etc.. - * @param runnerName the name of this specific running sync instance defined by the user. - * @return a {@link CompletionStage} containing the current CTP timestamp as {@link - * ZonedDateTime}. - */ @Nonnull @Override - public CompletionStage getCurrentCtpTimestamp( + public CompletableFuture getCurrentCtpTimestamp( @Nullable final String runnerName, @Nonnull final String syncModuleName) { final String container = buildCurrentCtpTimestampContainerName(syncModuleName, runnerName); - final CustomObjectDraft currentTimestampDraft = - CustomObjectDraft.ofUnversionedUpsert( - container, TIMESTAMP_GENERATOR_KEY, UUID.randomUUID().toString(), String.class); + final CustomObjectDraft currentTimestampDraft = + CustomObjectDraftBuilder.of() + .container(container) + .key(TIMESTAMP_GENERATOR_KEY) + .value(UUID.randomUUID().toString()) + .build(); - return createCustomObject(currentTimestampDraft).thenApply(ResourceView::getLastModifiedAt); - } - - @Nonnull - private CompletionStage> createCustomObject( - @Nonnull final CustomObjectDraft customObjectDraft) { - return this.sphereClient.execute(CustomObjectUpsertCommand.of(customObjectDraft)); + return createCustomObject(currentTimestampDraft) + .thenApply(ApiHttpResponse::getBody) + .thenApply(CustomObject::getLastModifiedAt); } /** - * Queries for custom objects, on the CTP project defined by the {@code sphereClient}, which have - * a container: 'commercetools-project-sync.{@code runnerName}.{@code syncModuleName}' and key: - * {@code sourceProjectKey}. The method then returns the first custom object returned in the - * result set if there is, wrapped in an {@link Optional} as a result of a {@link - * CompletionStage}. It will be, at most, one custom object since the key is unique per custom - * object container as per CTP documentation. + * Helper to create a custom object of {@param customObjectDraft} on the CTP project defined by the {@code ctpClient}. * - * @param sourceProjectKey the source project from which the data is coming. - * @param syncModuleName the name of the resource being synced. E.g. productSync, categorySync, - * etc.. - * @param runnerName the name of this specific running sync instance defined by the user. - * @return the first custom object returned in the result set if there is, wrapped in an {@link - * Optional} as a result of a {@link CompletionStage}. It will be, at most, one custom object - * since the key is unique per custom object container as per CTP documentation. + * @param customObjectDraft draft of custom object to create + * @return a {@link CompletableFuture} of {@link ApiHttpResponse} with the created custom object resource. */ + @Nonnull + private CompletableFuture> createCustomObject( + @Nonnull final CustomObjectDraft customObjectDraft) { + return this.ctpClient.customObjects().post(customObjectDraft).execute(); + } + @Nonnull @Override - public CompletionStage>> getLastSyncCustomObject( + public CompletableFuture> getLastSyncCustomObject( @Nonnull final String sourceProjectKey, @Nonnull final String syncModuleName, @Nullable final String runnerName) { - final QueryPredicate> queryPredicate = - QueryPredicate.of( - format( - "container=\"%s\" AND key=\"%s\"", - buildLastSyncTimestampContainerName(syncModuleName, runnerName), sourceProjectKey)); - - return this.sphereClient - .execute(CustomObjectQuery.of(LastSyncCustomObject.class).plusPredicates(queryPredicate)) - .thenApply(PagedQueryResult::getResults) - .thenApply(Collection::stream) - .thenApply(Stream::findFirst); + final String containerName = buildLastSyncTimestampContainerName(syncModuleName, runnerName); + + return this.ctpClient + .customObjects() + .withContainerAndKey(containerName, sourceProjectKey) + .get() + .execute() + .handle( + (customObjectApiHttpResponse, throwable) -> { + if (throwable != null) { + return Optional.empty(); + } else { + final CustomObject responseBody = customObjectApiHttpResponse.getBody(); + final ObjectMapper objectMapper = JsonUtils.getConfiguredObjectMapper(); + final LastSyncCustomObject lastSyncCustomObject = + responseBody == null + ? null + : objectMapper.convertValue( + responseBody.getValue(), LastSyncCustomObject.class); + return Optional.ofNullable(lastSyncCustomObject); + } + }); } - /** - * Creates (or updates an already existing) custom object, with the container: - * 'commercetools-project-sync.{@code runnerName}.{@code syncModuleName}' and key: {@code - * sourceProjectKey}, enriched with the information in the passed {@link LastSyncCustomObject} - * param. - * - * @param sourceProjectKey the source project from which the data is coming. - * @param syncModuleName the name of the resource being synced. E.g. productSync, categorySync, - * etc.. - * @param runnerName the name of this specific running sync instance defined by the user. - * @param lastSyncCustomObject contains information about the last sync instance. - * @return the first custom object returned in the result set if there is, wrapped in an {@link - * Optional} as a result of a {@link CompletionStage}. It will be, at most, one custom object - * since the key is unique per custom object container as per CTP documentation. - */ @Nonnull @Override - public CompletionStage> createLastSyncCustomObject( + public CompletableFuture> createLastSyncCustomObject( @Nonnull final String sourceProjectKey, @Nonnull final String syncModuleName, @Nullable final String runnerName, @Nonnull final LastSyncCustomObject lastSyncCustomObject) { - final CustomObjectDraft lastSyncCustomObjectDraft = - CustomObjectDraft.ofUnversionedUpsert( - buildLastSyncTimestampContainerName(syncModuleName, runnerName), - sourceProjectKey, - lastSyncCustomObject, - LastSyncCustomObject.class); + final CustomObjectDraft lastSyncCustomObjectDraft = + CustomObjectDraftBuilder.of() + .container(buildLastSyncTimestampContainerName(syncModuleName, runnerName)) + .key(sourceProjectKey) + .value(lastSyncCustomObject) + .build(); return createCustomObject(lastSyncCustomObjectDraft); } diff --git a/src/main/java/com/commercetools/project/sync/util/SyncUtils.java b/src/main/java/com/commercetools/project/sync/util/SyncUtils.java index a4448fc1..66196a07 100644 --- a/src/main/java/com/commercetools/project/sync/util/SyncUtils.java +++ b/src/main/java/com/commercetools/project/sync/util/SyncUtils.java @@ -5,11 +5,10 @@ import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.StringUtils.isBlank; +import com.commercetools.api.models.ResourceUpdateAction; +import com.commercetools.api.models.WithKey; import com.commercetools.sync.commons.BaseSync; import com.commercetools.sync.commons.exceptions.SyncException; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.models.ResourceView; -import io.sphere.sdk.models.WithKey; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -18,6 +17,8 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; +// This class compiles but not tested yet +// TODO: Test class and adjust logic if needed public final class SyncUtils { public static final String APPLICATION_DEFAULT_NAME = "commercetools-project-sync"; @@ -42,12 +43,12 @@ public static String getApplicationVersion() { return isBlank(implementationVersion) ? APPLICATION_DEFAULT_VERSION : implementationVersion; } - public static void logErrorCallback( + public static void logErrorCallback( @Nonnull final Logger logger, @Nonnull final String resourceName, @Nonnull final SyncException exception, @Nonnull final Optional resource, - @Nullable final List> updateActions) { + @Nullable final List updateActions) { String updateActionsString = "[]"; if (updateActions != null) { updateActionsString = @@ -64,12 +65,12 @@ public static void logErrorCallback( } } - public static void logErrorCallback( + public static void logErrorCallback( @Nonnull final Logger logger, @Nonnull final String resourceName, @Nonnull final SyncException exception, @Nonnull final String resourceIdentifier, - @Nullable final List> updateActions) { + @Nullable final List updateActions) { String updateActionsString = "[]"; if (updateActions != null) { updateActionsString = diff --git a/src/test/java/com/commercetools/project/sync/service/impl/CustomObjectServiceImplTest.java b/src/test/java/com/commercetools/project/sync/service/impl/CustomObjectServiceImplTest.java index 898b6334..fcf34d34 100644 --- a/src/test/java/com/commercetools/project/sync/service/impl/CustomObjectServiceImplTest.java +++ b/src/test/java/com/commercetools/project/sync/service/impl/CustomObjectServiceImplTest.java @@ -1,57 +1,74 @@ package com.commercetools.project.sync.service.impl; import static com.commercetools.project.sync.util.SyncUtils.DEFAULT_RUNNER_NAME; -import static java.util.Collections.singletonList; import static java.util.Optional.empty; 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.spy; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; +import com.commercetools.api.client.ProjectApiRoot; +import com.commercetools.api.models.custom_object.CustomObject; +import com.commercetools.api.models.custom_object.CustomObjectDraft; import com.commercetools.project.sync.model.response.LastSyncCustomObject; import com.commercetools.project.sync.service.CustomObjectService; +import com.commercetools.project.sync.util.TestUtils; import com.commercetools.sync.products.helpers.ProductSyncStatistics; -import io.sphere.sdk.client.BadGatewayException; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.customobjects.CustomObject; -import io.sphere.sdk.customobjects.CustomObjectDraft; -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 io.vrap.rmf.base.client.ApiHttpResponse; +import io.vrap.rmf.base.client.error.BadGatewayException; +import io.vrap.rmf.base.client.utils.CompletableFutureUtils; import java.time.ZonedDateTime; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +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 org.mockito.ArgumentCaptor; +// These tests do compile but not valid +// TODO: Make tests valid class CustomObjectServiceImplTest { - private static final SphereClient CLIENT = mock(SphereClient.class); + private ProjectApiRoot ctpClient; @SuppressWarnings("unchecked") - private static final CustomObject STRING_CUSTOM_OBJECT = mock(CustomObject.class); + private static CustomObject STRING_CUSTOM_OBJECT = mock(CustomObject.class); @SuppressWarnings("unchecked") - private static final CustomObject LAST_SYNC_CUSTOM_OBJECT = - mock(CustomObject.class); + private static LastSyncCustomObject LAST_SYNC_CUSTOM_OBJECT = mock(LastSyncCustomObject.class); + + private ApiHttpResponse apiHttpResponse; @BeforeAll static void setup() { when(STRING_CUSTOM_OBJECT.getLastModifiedAt()).thenReturn(ZonedDateTime.now()); - when(LAST_SYNC_CUSTOM_OBJECT.getLastModifiedAt()).thenReturn(ZonedDateTime.now()); + when(LAST_SYNC_CUSTOM_OBJECT.getLastSyncTimestamp()).thenReturn(ZonedDateTime.now()); + when(STRING_CUSTOM_OBJECT.getValue()).thenReturn(LAST_SYNC_CUSTOM_OBJECT); + } + + @BeforeEach + void init() { + apiHttpResponse = mock(ApiHttpResponse.class); + ctpClient = mock(ProjectApiRoot.class); + } + + @AfterEach + void cleanup() { + reset(ctpClient); } @Test @SuppressWarnings("unchecked") void getCurrentCtpTimestamp_OnSuccessfulUpsert_ShouldCompleteWithCtpTimestamp() { // preparation - when(CLIENT.execute(any(CustomObjectUpsertCommand.class))) - .thenReturn(CompletableFuture.completedFuture(STRING_CUSTOM_OBJECT)); - final CustomObjectService customObjectService = new CustomObjectServiceImpl(CLIENT); + when(apiHttpResponse.getBody()).thenReturn(STRING_CUSTOM_OBJECT); + when(ctpClient.customObjects().post(any(CustomObjectDraft.class)).execute()) + .thenReturn(CompletableFuture.completedFuture(apiHttpResponse)); + final CustomObjectService customObjectService = new CustomObjectServiceImpl(ctpClient); // test final CompletionStage ctpTimestamp = @@ -65,11 +82,10 @@ void getCurrentCtpTimestamp_OnSuccessfulUpsert_ShouldCompleteWithCtpTimestamp() @SuppressWarnings("unchecked") void getCurrentCtpTimestamp_OnFailedUpsert_ShouldCompleteExceptionally() { // preparation - when(CLIENT.execute(any(CustomObjectUpsertCommand.class))) - .thenReturn( - CompletableFutureUtils.exceptionallyCompletedFuture( - new BadGatewayException("CTP error!"))); - final CustomObjectService customObjectService = new CustomObjectServiceImpl(CLIENT); + final BadGatewayException badGatewayException = TestUtils.createBadGatewayException(); + when(ctpClient.customObjects().post(any(CustomObjectDraft.class)).execute()) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(badGatewayException)); + final CustomObjectService customObjectService = new CustomObjectServiceImpl(ctpClient); // test final CompletionStage ctpTimestamp = @@ -87,17 +103,14 @@ void getCurrentCtpTimestamp_OnFailedUpsert_ShouldCompleteExceptionally() { void getLastSyncCustomObject_OnSuccessfulQueryWithResults_ShouldCompleteWithLastSyncCustomObject() { // preparation - final PagedQueryResult> queriedCustomObjects = - spy(PagedQueryResult.empty()); - when(queriedCustomObjects.getResults()).thenReturn(singletonList(LAST_SYNC_CUSTOM_OBJECT)); - - when(CLIENT.execute(any(CustomObjectQuery.class))) - .thenReturn(CompletableFuture.completedFuture(queriedCustomObjects)); + when(apiHttpResponse.getBody()).thenReturn(STRING_CUSTOM_OBJECT); + when(ctpClient.customObjects().withContainerAndKey(anyString(), anyString()).get().execute()) + .thenReturn(CompletableFuture.completedFuture(apiHttpResponse)); - final CustomObjectService customObjectService = new CustomObjectServiceImpl(CLIENT); + final CustomObjectService customObjectService = new CustomObjectServiceImpl(ctpClient); // test - final CompletionStage>> lastSyncCustomObject = + final CompletionStage> lastSyncCustomObject = customObjectService.getLastSyncCustomObject("foo", "bar", DEFAULT_RUNNER_NAME); // assertions @@ -108,16 +121,14 @@ void getCurrentCtpTimestamp_OnFailedUpsert_ShouldCompleteExceptionally() { @SuppressWarnings("unchecked") void getLastSyncCustomObject_OnSuccessfulQueryWithNoResults_ShouldCompleteWithEmptyOptional() { // preparation - final PagedQueryResult> queriedCustomObjects = - PagedQueryResult.empty(); + when(apiHttpResponse.getBody()).thenReturn(null); + when(ctpClient.customObjects().withContainerAndKey(anyString(), anyString()).get().execute()) + .thenReturn(CompletableFuture.completedFuture(apiHttpResponse)); - when(CLIENT.execute(any(CustomObjectQuery.class))) - .thenReturn(CompletableFuture.completedFuture(queriedCustomObjects)); - - final CustomObjectService customObjectService = new CustomObjectServiceImpl(CLIENT); + final CustomObjectService customObjectService = new CustomObjectServiceImpl(ctpClient); // test - final CompletionStage>> lastSyncCustomObject = + final CompletionStage> lastSyncCustomObject = customObjectService.getLastSyncCustomObject("foo", "bar", DEFAULT_RUNNER_NAME); // assertions @@ -128,19 +139,15 @@ void getLastSyncCustomObject_OnSuccessfulQueryWithNoResults_ShouldCompleteWithEm @SuppressWarnings("unchecked") void getLastSyncCustomObject_OnFailedQuery_ShouldCompleteExceptionally() { // preparation - final PagedQueryResult> queriedCustomObjects = - spy(PagedQueryResult.empty()); - when(queriedCustomObjects.getResults()).thenReturn(singletonList(LAST_SYNC_CUSTOM_OBJECT)); - - when(CLIENT.execute(any(CustomObjectQuery.class))) + when(ctpClient.customObjects().withContainerAndKey(anyString(), anyString()).get().execute()) .thenReturn( CompletableFutureUtils.exceptionallyCompletedFuture( - new BadGatewayException("CTP error!"))); + TestUtils.createBadGatewayException())); - final CustomObjectService customObjectService = new CustomObjectServiceImpl(CLIENT); + final CustomObjectService customObjectService = new CustomObjectServiceImpl(ctpClient); // test - final CompletionStage>> lastSyncCustomObject = + final CompletionStage> lastSyncCustomObject = customObjectService.getLastSyncCustomObject("foo", "bar", DEFAULT_RUNNER_NAME); // assertions @@ -154,61 +161,56 @@ void getLastSyncCustomObject_OnFailedQuery_ShouldCompleteExceptionally() { @SuppressWarnings("unchecked") void createLastSyncCustomObject_OnSuccessfulCreation_ShouldCompleteWithLastSyncCustomObject() { // preparation - when(CLIENT.execute(any(CustomObjectUpsertCommand.class))) - .thenReturn(CompletableFuture.completedFuture(LAST_SYNC_CUSTOM_OBJECT)); + when(apiHttpResponse.getBody()).thenReturn(LAST_SYNC_CUSTOM_OBJECT); + when(ctpClient.customObjects().post(any(CustomObjectDraft.class)).execute()) + .thenReturn(CompletableFuture.completedFuture(apiHttpResponse)); - final CustomObjectService customObjectService = new CustomObjectServiceImpl(CLIENT); + final CustomObjectService customObjectService = new CustomObjectServiceImpl(ctpClient); // test final LastSyncCustomObject lastSyncCustomObject = LastSyncCustomObject.of(ZonedDateTime.now(), new ProductSyncStatistics(), 100); - final CustomObject createdCustomObject = + final ApiHttpResponse createdCustomObject = customObjectService .createLastSyncCustomObject("foo", "bar", DEFAULT_RUNNER_NAME, lastSyncCustomObject) .toCompletableFuture() .join(); // assertions - assertThat(createdCustomObject).isEqualTo(LAST_SYNC_CUSTOM_OBJECT); + assertThat(createdCustomObject.getBody()).isEqualTo(LAST_SYNC_CUSTOM_OBJECT); } @Test @SuppressWarnings("unchecked") void createLastSyncCustomObject_OnFailedCreation_ShouldCompleteExceptionally() { // preparation - when(CLIENT.execute(any(CustomObjectUpsertCommand.class))) + when(ctpClient.customObjects().post(any(CustomObjectDraft.class)).execute()) .thenReturn( CompletableFutureUtils.exceptionallyCompletedFuture( - new BadGatewayException("CTP error!"))); + TestUtils.createBadGatewayException())); - final CustomObjectService customObjectService = new CustomObjectServiceImpl(CLIENT); + final CustomObjectService customObjectService = new CustomObjectServiceImpl(ctpClient); // test - final LastSyncCustomObject lastSyncCustomObject = - LastSyncCustomObject.of(ZonedDateTime.now(), new ProductSyncStatistics(), 100); - - final CompletionStage> createdCustomObject = + final CompletableFuture> createdCustomObject = customObjectService.createLastSyncCustomObject( - "foo", "bar", DEFAULT_RUNNER_NAME, lastSyncCustomObject); + "foo", "bar", DEFAULT_RUNNER_NAME, LAST_SYNC_CUSTOM_OBJECT); // assertions assertThat(createdCustomObject) - .hasFailedWithThrowableThat() - .isInstanceOf(BadGatewayException.class) - .hasMessageContaining("CTP error!"); + .isCompletedExceptionally() + .isInstanceOf(BadGatewayException.class); } @Test @SuppressWarnings("unchecked") void createLastSyncCustomObject_WithValidTestRunnerName_ShouldCreateCorrectCustomObjectDraft() { // preparation - final SphereClient client = mock(SphereClient.class); - final ArgumentCaptor arg = - ArgumentCaptor.forClass(CustomObjectUpsertCommand.class); - when(client.execute(arg.capture())).thenReturn(null); + final ArgumentCaptor arg = ArgumentCaptor.forClass(CustomObjectDraft.class); + when(ctpClient.customObjects().post(arg.capture()).execute()).thenReturn(null); - final CustomObjectService customObjectService = new CustomObjectServiceImpl(client); + final CustomObjectService customObjectService = new CustomObjectServiceImpl(ctpClient); final LastSyncCustomObject lastSyncCustomObject = LastSyncCustomObject.of(ZonedDateTime.now(), new ProductSyncStatistics(), 100); @@ -218,22 +220,22 @@ void createLastSyncCustomObject_WithValidTestRunnerName_ShouldCreateCorrectCusto "foo", "bar", "testRunnerName", lastSyncCustomObject); // assertions - final CustomObjectDraft createdDraft = (CustomObjectDraft) arg.getValue().getDraft(); + final CustomObjectDraft createdDraft = arg.getValue(); assertThat(createdDraft.getContainer()) .isEqualTo("commercetools-project-sync.testRunnerName.bar"); assertThat(createdDraft.getKey()).isEqualTo("foo"); + assertThat(createdDraft.getValue()).isInstanceOf(LastSyncCustomObject.class); + assertThat((LastSyncCustomObject) createdDraft.getValue()).isEqualTo(lastSyncCustomObject); } @Test @SuppressWarnings("unchecked") void createLastSyncCustomObject_WithEmptyRunnerName_ShouldCreateCorrectCustomObjectDraft() { // preparation - final SphereClient client = mock(SphereClient.class); - final ArgumentCaptor arg = - ArgumentCaptor.forClass(CustomObjectUpsertCommand.class); - when(client.execute(arg.capture())).thenReturn(null); + final ArgumentCaptor arg = ArgumentCaptor.forClass(CustomObjectDraft.class); + when(ctpClient.customObjects().post(arg.capture()).execute()).thenReturn(null); - final CustomObjectService customObjectService = new CustomObjectServiceImpl(client); + final CustomObjectService customObjectService = new CustomObjectServiceImpl(ctpClient); final LastSyncCustomObject lastSyncCustomObject = LastSyncCustomObject.of(ZonedDateTime.now(), new ProductSyncStatistics(), 100); @@ -242,21 +244,21 @@ void createLastSyncCustomObject_WithEmptyRunnerName_ShouldCreateCorrectCustomObj customObjectService.createLastSyncCustomObject("foo", "bar", "", lastSyncCustomObject); // assertions - final CustomObjectDraft createdDraft = (CustomObjectDraft) arg.getValue().getDraft(); + final CustomObjectDraft createdDraft = arg.getValue(); assertThat(createdDraft.getContainer()).isEqualTo("commercetools-project-sync.runnerName.bar"); assertThat(createdDraft.getKey()).isEqualTo("foo"); + assertThat(createdDraft.getValue()).isInstanceOf(LastSyncCustomObject.class); + assertThat((LastSyncCustomObject) createdDraft.getValue()).isEqualTo(lastSyncCustomObject); } @Test @SuppressWarnings("unchecked") void createLastSyncCustomObject_WithNullRunnerName_ShouldCreateCorrectCustomObjectDraft() { // preparation - final SphereClient client = mock(SphereClient.class); - final ArgumentCaptor arg = - ArgumentCaptor.forClass(CustomObjectUpsertCommand.class); - when(client.execute(arg.capture())).thenReturn(null); + final ArgumentCaptor arg = ArgumentCaptor.forClass(CustomObjectDraft.class); + when(ctpClient.customObjects().post(arg.capture()).execute()).thenReturn(null); - final CustomObjectService customObjectService = new CustomObjectServiceImpl(client); + final CustomObjectService customObjectService = new CustomObjectServiceImpl(ctpClient); final LastSyncCustomObject lastSyncCustomObject = LastSyncCustomObject.of(ZonedDateTime.now(), new ProductSyncStatistics(), 100); @@ -265,8 +267,10 @@ void createLastSyncCustomObject_WithNullRunnerName_ShouldCreateCorrectCustomObje customObjectService.createLastSyncCustomObject("foo", "bar", null, lastSyncCustomObject); // assertions - final CustomObjectDraft createdDraft = (CustomObjectDraft) arg.getValue().getDraft(); + final CustomObjectDraft createdDraft = arg.getValue(); assertThat(createdDraft.getContainer()).isEqualTo("commercetools-project-sync.runnerName.bar"); assertThat(createdDraft.getKey()).isEqualTo("foo"); + assertThat(createdDraft.getValue()).isInstanceOf(LastSyncCustomObject.class); + assertThat((LastSyncCustomObject) createdDraft.getValue()).isEqualTo(lastSyncCustomObject); } } diff --git a/src/test/java/com/commercetools/project/sync/util/SyncUtilsTest.java b/src/test/java/com/commercetools/project/sync/util/SyncUtilsTest.java index f75154fc..79d5e899 100644 --- a/src/test/java/com/commercetools/project/sync/util/SyncUtilsTest.java +++ b/src/test/java/com/commercetools/project/sync/util/SyncUtilsTest.java @@ -5,12 +5,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.commercetools.api.models.ResourceUpdateAction; +import com.commercetools.api.models.WithKey; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.products.ProductSync; import com.commercetools.sync.types.TypeSync; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.models.ResourceView; -import io.sphere.sdk.models.WithKey; import java.util.Arrays; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; @@ -19,6 +18,8 @@ import uk.org.lidalia.slf4jtest.TestLogger; import uk.org.lidalia.slf4jtest.TestLoggerFactory; +// These tests do compile but are not valid +// TODO: Make tests valid class SyncUtilsTest { @BeforeEach @@ -62,9 +63,9 @@ void getApplicationVersion_ShouldGetDefaultVersion() { void logErrorCallbackWithStringResourceIdentifier_ShouldLogErrorWithCorrectMessage() { final TestLogger testLogger = TestLoggerFactory.getTestLogger(SyncUtilsTest.class); SyncException exception = new SyncException("test sync exception"); - UpdateAction updateAction1 = mock(UpdateAction.class); + ResourceUpdateAction updateAction1 = mock(ResourceUpdateAction.class); when(updateAction1.toString()).thenReturn("updateAction1"); - UpdateAction updateAction2 = mock(UpdateAction.class); + ResourceUpdateAction updateAction2 = mock(ResourceUpdateAction.class); when(updateAction2.toString()).thenReturn("updateAction2"); logErrorCallback( @@ -104,9 +105,9 @@ void logErrorCallbackWithStringResourceIdentifier_ShouldLogErrorWithCorrectMessa void logErrorCallbackWithResource_ShouldLogErrorWithCorrectMessage() { final TestLogger testLogger = TestLoggerFactory.getTestLogger(SyncUtilsTest.class); final SyncException exception = new SyncException("test sync exception"); - final UpdateAction updateAction1 = mock(UpdateAction.class); + final ResourceUpdateAction updateAction1 = mock(ResourceUpdateAction.class); when(updateAction1.toString()).thenReturn("updateAction1"); - final UpdateAction updateAction2 = mock(UpdateAction.class); + final ResourceUpdateAction updateAction2 = mock(ResourceUpdateAction.class); when(updateAction2.toString()).thenReturn("updateAction2"); final WithKey resource = mock(WithKey.class); when(resource.getKey()).thenReturn("test identifier"); diff --git a/src/test/java/com/commercetools/project/sync/util/TestUtils.java b/src/test/java/com/commercetools/project/sync/util/TestUtils.java index 8d44d0e2..937804c0 100644 --- a/src/test/java/com/commercetools/project/sync/util/TestUtils.java +++ b/src/test/java/com/commercetools/project/sync/util/TestUtils.java @@ -1,28 +1,34 @@ package com.commercetools.project.sync.util; import static java.lang.String.format; -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.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import com.commercetools.api.client.ProjectApiRoot; +import com.commercetools.api.models.custom_object.CustomObject; +import com.commercetools.api.models.custom_object.CustomObjectDraft; +import com.commercetools.api.models.error.ErrorResponse; +import com.commercetools.api.models.error.ErrorResponseBuilder; +import com.commercetools.api.models.graph_ql.GraphQLRequest; +import com.commercetools.api.models.graph_ql.GraphQLResponse; +import com.commercetools.api.models.graph_ql.GraphQLResponseBuilder; import com.commercetools.project.sync.model.response.LastSyncCustomObject; -import com.commercetools.sync.commons.models.ResourceIdsGraphQlRequest; -import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; import com.commercetools.sync.products.helpers.ProductSyncStatistics; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.customobjects.CustomObject; -import io.sphere.sdk.customobjects.commands.CustomObjectUpsertCommand; -import io.sphere.sdk.customobjects.queries.CustomObjectQuery; -import io.sphere.sdk.json.SphereJsonUtils; -import io.sphere.sdk.queries.PagedQueryResult; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import io.vrap.rmf.base.client.ApiHttpResponse; +import io.vrap.rmf.base.client.error.BadGatewayException; +import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.ZonedDateTime; +import java.util.Collections; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; import org.assertj.core.api.Condition; @@ -30,6 +36,8 @@ import uk.org.lidalia.slf4jtest.LoggingEvent; import uk.org.lidalia.slf4jtest.TestLogger; +// This utility class compiles but not used yet +// TODO: Use the utility functions and adjust them public final class TestUtils { public static void assertTypeSyncerLoggingEvents( @@ -180,39 +188,32 @@ public static void assertSyncerLoggingEvents( } public static void verifyInteractionsWithClientAfterSync( - @Nonnull final SphereClient client, final int numberOfGetConfigInvocations) { + @Nonnull final ProjectApiRoot client, final int numberOfGetConfigInvocations) { verify(client, times(1)).close(); // Verify config is accessed for the success message after sync: // " example: Syncing products from CTP project with key 'x' to project with key 'y' is done"," - verify(client, times(numberOfGetConfigInvocations)).getConfig(); + verify(client, times(numberOfGetConfigInvocations)).getProjectKey(); verifyNoMoreInteractions(client); } @SuppressWarnings("unchecked") public static void stubClientsCustomObjectService( - @Nonnull final SphereClient client, @Nonnull final ZonedDateTime currentCtpTimestamp) { - - final CustomObject> customObject = - mockLastSyncCustomObject(currentCtpTimestamp); - - when(client.execute(any(CustomObjectUpsertCommand.class))) - .thenReturn(CompletableFuture.completedFuture(customObject)); - - final PagedQueryResult>> - queriedCustomObjects = spy(PagedQueryResult.empty()); - when(queriedCustomObjects.getResults()).thenReturn(singletonList(customObject)); - - when(client.execute(any(CustomObjectQuery.class))) - .thenReturn(CompletableFuture.completedFuture(queriedCustomObjects)); + @Nonnull final ProjectApiRoot client, @Nonnull final ZonedDateTime currentCtpTimestamp) { + + final CustomObject customObject = mockLastSyncCustomObject(currentCtpTimestamp); + final ApiHttpResponse apiHttpResponse = mock(ApiHttpResponse.class); + when(apiHttpResponse.getBody()).thenReturn(customObject); + when(client.customObjects().post(any(CustomObjectDraft.class)).execute()) + .thenReturn(CompletableFuture.completedFuture(apiHttpResponse)); + when(client.customObjects().withContainerAndKey(anyString(), anyString()).get().execute()) + .thenReturn(CompletableFuture.completedFuture(apiHttpResponse)); } @Nonnull @SuppressWarnings("unchecked") - public static CustomObject> mockLastSyncCustomObject( - @Nonnull ZonedDateTime currentCtpTimestamp) { - final CustomObject> customObject = - mock(CustomObject.class); + public static CustomObject mockLastSyncCustomObject(@Nonnull ZonedDateTime currentCtpTimestamp) { + final CustomObject customObject = mock(CustomObject.class); final LastSyncCustomObject lastSyncCustomObject = LastSyncCustomObject.of(ZonedDateTime.now(), new ProductSyncStatistics(), 100); @@ -229,14 +230,40 @@ public static Clock getMockedClock() { return clock; } - public static void mockResourceIdsGraphQlRequest(SphereClient client, String id, String key) { + public static void mockResourceIdsGraphQlRequest(ProjectApiRoot client, String id, String key) { final String jsonResponseString = "{\"results\":[{\"id\":\"" + id + "\"," + "\"key\":\"" + key + "\"}]}"; - final ResourceKeyIdGraphQlResult result = - SphereJsonUtils.readObject(jsonResponseString, ResourceKeyIdGraphQlResult.class); + final GraphQLResponse result = GraphQLResponseBuilder.of().data(jsonResponseString).build(); + + final ApiHttpResponse apiHttpResponse = mock(ApiHttpResponse.class); + when(apiHttpResponse.getBody()).thenReturn(result); + when(client.graphql().post(any(GraphQLRequest.class)).execute()) + .thenReturn(CompletableFuture.completedFuture(apiHttpResponse)); + } + + public static BadGatewayException createBadGatewayException() { + final String json = getErrorResponseJsonString(500); + return new BadGatewayException( + 500, "", null, "", new ApiHttpResponse<>(500, null, json.getBytes(StandardCharsets.UTF_8))); + } - when(client.execute(any(ResourceIdsGraphQlRequest.class))) - .thenReturn(CompletableFuture.completedFuture(result)); + private static String getErrorResponseJsonString(Integer errorCode) { + final ErrorResponse errorResponse = + ErrorResponseBuilder.of() + .statusCode(errorCode) + .errors(Collections.emptyList()) + .message("test") + .build(); + + final ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + String json; + try { + json = ow.writeValueAsString(errorResponse); + } catch (JsonProcessingException e) { + // ignore the error + json = null; + } + return json; } private TestUtils() {}