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 4b01703c7a..9f7b0a4157 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 @@ -2,6 +2,7 @@ import static com.commercetools.api.models.common.LocalizedString.ofEnglish; import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.ensureStore; import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.*; import static com.commercetools.sync.integration.commons.utils.TestClientUtils.CTP_SOURCE_CLIENT; import static com.commercetools.sync.integration.commons.utils.TestClientUtils.CTP_TARGET_CLIENT; @@ -36,6 +37,7 @@ import com.commercetools.api.models.shopping_list.TextLineItem; import com.commercetools.api.models.shopping_list.TextLineItemDraft; import com.commercetools.api.models.shopping_list.TextLineItemDraftBuilder; +import com.commercetools.api.models.store.StoreResourceIdentifier; import com.commercetools.api.models.type.CustomFields; import com.commercetools.api.models.type.CustomFieldsDraftBuilder; import com.commercetools.api.models.type.TypeReference; @@ -521,4 +523,62 @@ private List setNullToAddedAtValuesForLineItems( ShoppingListLineItemDraftBuilder.of(lineItemDraft).addedAt(null).build()) .collect(toList()); } + + @Test + void sync_WithStoreChange_ShouldUpdateShoppingListStore() { + // Create the store that will be referenced + ensureStore(CTP_TARGET_CLIENT, "different-store-key"); + + // Create a shopping list draft with a different store + final ShoppingListDraft modifiedDraft = + ShoppingListDraftBuilder.of(shoppingListDraftSampleCarrotCake) + .store( + storeResourceIdentifierBuilder -> + storeResourceIdentifierBuilder.key("different-store-key")) + .build(); + + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(modifiedDraft)).toCompletableFuture().join(); + + assertThat(errorMessages).isEmpty(); + assertThat(warningMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + + // Verify that a setStore update action was generated + assertThat(updateActionList) + .anySatisfy( + action -> { + assertThat(action.getAction()).isEqualTo("setStore"); + }); + + assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); + } + + @Test + void sync_WithNullStore_ShouldUpdateShoppingListStoreToNull() { + // Create a shopping list draft with null store + final StoreResourceIdentifier nullStore = null; + final ShoppingListDraft modifiedDraft = + ShoppingListDraftBuilder.of(shoppingListDraftSampleCarrotCake).store(nullStore).build(); + + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(modifiedDraft)).toCompletableFuture().join(); + + assertThat(errorMessages).isEmpty(); + assertThat(warningMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + + // If the original had a store and we set it to null, we should see an update + if (shoppingListSampleCarrotCake.getStore() != null) { + assertThat(updateActionList) + .anySatisfy( + action -> { + assertThat(action.getAction()).isEqualTo("setStore"); + }); + assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); + } else { + // If both are null, no update should occur + assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 0); + } + } } 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 5e73c44ed3..057306571a 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListReferenceResolver.java @@ -11,6 +11,8 @@ import com.commercetools.api.models.customer.CustomerResourceIdentifierBuilder; import com.commercetools.api.models.shopping_list.ShoppingListDraft; import com.commercetools.api.models.shopping_list.ShoppingListDraftBuilder; +import com.commercetools.api.models.store.StoreResourceIdentifier; +import com.commercetools.api.models.store.StoreResourceIdentifierBuilder; import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.CustomReferenceResolver; import com.commercetools.sync.services.CustomerService; @@ -32,6 +34,9 @@ public final class ShoppingListReferenceResolver "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_STORE_REFERENCE = + "Failed to resolve store resource identifier on " + + "ShoppingListDraft with key:'%s'. Reason: %s"; static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = "Failed to resolve custom type reference on ShoppingListDraft with key:'%s'. "; @@ -66,9 +71,9 @@ public ShoppingListReferenceResolver( } /** - * Given a {@link ShoppingListDraft} this method attempts to resolve the customer and custom type - * references to return a {@link java.util.concurrent.CompletionStage} which contains a new - * instance of the draft with the resolved references. + * Given a {@link ShoppingListDraft} this method attempts to resolve the customer, store and + * custom type references to return a {@link java.util.concurrent.CompletionStage} which contains + * a new instance of the draft with the resolved references. * * @param shoppingListDraft the shoppingListDraft to resolve its references. * @return a {@link java.util.concurrent.CompletionStage} that contains as a result a new @@ -79,6 +84,7 @@ public ShoppingListReferenceResolver( public CompletionStage resolveReferences( @Nonnull final ShoppingListDraft shoppingListDraft) { return resolveCustomerReference(ShoppingListDraftBuilder.of(shoppingListDraft)) + .thenCompose(this::resolveStoreReference) .thenCompose(this::resolveCustomTypeReference) .thenCompose(this::resolveLineItemReferences) .thenCompose(this::resolveTextLineItemReferences) @@ -136,6 +142,32 @@ private CompletionStage fetchAndResolveCustomerReferen })); } + @Nonnull + protected CompletionStage resolveStoreReference( + @Nonnull final ShoppingListDraftBuilder draftBuilder) { + + final StoreResourceIdentifier storeResourceIdentifier = draftBuilder.getStore(); + if (storeResourceIdentifier != null && storeResourceIdentifier.getId() == null) { + try { + final String storeKey = getKeyFromResourceIdentifier(storeResourceIdentifier); + return completedFuture( + draftBuilder.store(StoreResourceIdentifierBuilder.of().key(storeKey).build())); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_STORE_REFERENCE, + draftBuilder.getKey(), + referenceResolutionException.getMessage()))); + } + } else if (storeResourceIdentifier != null && storeResourceIdentifier.getId() != null) { + return completedFuture( + draftBuilder.store( + StoreResourceIdentifierBuilder.of().id(storeResourceIdentifier.getId()).build())); + } + return completedFuture(draftBuilder); + } + @Nonnull protected CompletionStage resolveCustomTypeReference( @Nonnull final ShoppingListDraftBuilder draftBuilder) { 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 af88d46de8..16734002cd 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListReferenceResolutionUtils.java @@ -15,6 +15,8 @@ import com.commercetools.api.models.shopping_list.TextLineItem; import com.commercetools.api.models.shopping_list.TextLineItemDraft; import com.commercetools.api.models.shopping_list.TextLineItemDraftBuilder; +import com.commercetools.api.models.store.StoreResourceIdentifier; +import com.commercetools.api.models.store.StoreResourceIdentifierBuilder; import com.commercetools.sync.commons.utils.ReferenceIdToKeyCache; import java.util.List; import java.util.Objects; @@ -48,6 +50,11 @@ public final class ShoppingListReferenceResolutionUtils { * {@link com.commercetools.api.models.customer.CustomerResourceIdentifier} * * + * store + * {@link com.commercetools.api.models.store.StoreKeyReference} + * {@link com.commercetools.api.models.store.StoreResourceIdentifier} + * + * * custom.type * {@link com.commercetools.api.models.type.TypeReference} * {@link com.commercetools.api.models.type.TypeResourceIdentifier} @@ -107,6 +114,11 @@ public static List mapToShoppingListDrafts( * {@link com.commercetools.api.models.customer.CustomerResourceIdentifier} * * + * store + * {@link com.commercetools.api.models.store.StoreKeyReference} + * {@link com.commercetools.api.models.store.StoreResourceIdentifier} + * + * * custom.type * {@link com.commercetools.api.models.type.TypeReference} * {@link com.commercetools.api.models.type.TypeResourceIdentifier} @@ -139,16 +151,18 @@ public static ShoppingListDraft mapToShoppingListDraft( @Nonnull final ShoppingList shoppingList, @Nonnull final ReferenceIdToKeyCache referenceIdToKeyCache) { - final CustomerResourceIdentifier resourceIdentifierWithKey = + final CustomerResourceIdentifier customerResourceIdentifierWithKey = getResourceIdentifierWithKey( shoppingList.getCustomer(), referenceIdToKeyCache, (id, key) -> CustomerResourceIdentifierBuilder.of().id(id).key(key).build()); + return ShoppingListDraftBuilder.of() .name(shoppingList.getName()) .description(shoppingList.getDescription()) .key(shoppingList.getKey()) - .customer(resourceIdentifierWithKey) + .customer(customerResourceIdentifierWithKey) + .store(mapToStoreResourceIdentifier(shoppingList)) .slug(shoppingList.getSlug()) .lineItems(mapToLineItemDrafts(shoppingList.getLineItems(), referenceIdToKeyCache)) .textLineItems( @@ -217,5 +231,14 @@ private static TextLineItemDraft mapToTextLineItemDraft( .build(); } + @Nullable + private static StoreResourceIdentifier mapToStoreResourceIdentifier( + @Nonnull final ShoppingList shoppingList) { + if (shoppingList.getStore() != null) { + return StoreResourceIdentifierBuilder.of().key(shoppingList.getStore().getKey()).build(); + } + return null; + } + 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 09362ef02c..83279fc669 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListSyncUtils.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListSyncUtils.java @@ -46,6 +46,7 @@ public static List buildActions( buildSetDescriptionUpdateAction(oldShoppingList, newShoppingList), buildSetAnonymousIdUpdateAction(oldShoppingList, newShoppingList), buildSetCustomerUpdateAction(oldShoppingList, newShoppingList), + buildSetStoreUpdateAction(oldShoppingList, newShoppingList), buildSetDeleteDaysAfterLastModificationUpdateAction(oldShoppingList, newShoppingList)); updateActions.addAll( 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 fe0ea2f265..ecf4fdd0f7 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtils.java @@ -11,6 +11,7 @@ import com.commercetools.api.models.shopping_list.ShoppingListSetDeleteDaysAfterLastModificationActionBuilder; import com.commercetools.api.models.shopping_list.ShoppingListSetDescriptionActionBuilder; import com.commercetools.api.models.shopping_list.ShoppingListSetSlugActionBuilder; +import com.commercetools.api.models.shopping_list.ShoppingListSetStoreActionBuilder; import com.commercetools.api.models.shopping_list.ShoppingListUpdateAction; import java.util.Optional; import javax.annotation.Nonnull; @@ -116,6 +117,38 @@ public static Optional buildSetCustomerUpdateAction( .build()); } + /** + * Compares the store references of a {@link ShoppingList} and a {@link ShoppingListDraft} and + * returns an {@link ShoppingListUpdateAction} as a result in an {@link java.util.Optional}. If + * both the {@link ShoppingList} and the {@link ShoppingListDraft} have the same store, then no + * update action is needed and hence an empty {@link java.util.Optional} is returned. + * + *

Note: This method compares store keys since {@link + * com.commercetools.api.models.store.StoreKeyReference} contains the key directly. + * + * @param oldShoppingList the shopping list which should be updated. + * @param newShoppingList the shopping list draft which holds the new store. + * @return A filled optional with the update action or an empty optional if the stores are + * identical. + */ + @Nonnull + public static Optional buildSetStoreUpdateAction( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList) { + + final String oldStoreKey = + oldShoppingList.getStore() != null ? oldShoppingList.getStore().getKey() : null; + final String newStoreKey = + newShoppingList.getStore() != null && newShoppingList.getStore().getKey() != null + ? newShoppingList.getStore().getKey() + : (newShoppingList.getStore() != null ? newShoppingList.getStore().getId() : null); + + return buildUpdateAction( + oldStoreKey, + newStoreKey, + () -> ShoppingListSetStoreActionBuilder.of().store(newShoppingList.getStore()).build()); + } + /** * Compares the anonymousIds of {@link ShoppingList} and a {@link ShoppingListDraft} and returns * an {@link ShoppingListUpdateAction} as a result in an {@link java.util.Optional}. If both the 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 a13b788242..de32098a19 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtilsTest.java @@ -17,7 +17,12 @@ import com.commercetools.api.models.shopping_list.ShoppingListSetDeleteDaysAfterLastModificationAction; import com.commercetools.api.models.shopping_list.ShoppingListSetDescriptionAction; import com.commercetools.api.models.shopping_list.ShoppingListSetSlugAction; +import com.commercetools.api.models.shopping_list.ShoppingListSetStoreAction; import com.commercetools.api.models.shopping_list.ShoppingListUpdateAction; +import com.commercetools.api.models.store.StoreKeyReference; +import com.commercetools.api.models.store.StoreKeyReferenceBuilder; +import com.commercetools.api.models.store.StoreResourceIdentifier; +import com.commercetools.api.models.store.StoreResourceIdentifierBuilder; import java.util.Locale; import java.util.Optional; import java.util.UUID; @@ -351,4 +356,94 @@ void buildSetAnonymousId_WithSameValues_ShouldNotBuildUpdateActions() { assertThat(setDeleteDaysUpdateAction) .containsInstanceOf(ShoppingListSetDeleteDaysAfterLastModificationAction.class); } + + @Test + void buildSetStoreUpdateAction_WithDifferentStoreKeys_ShouldBuildUpdateAction() { + final StoreKeyReference oldStoreReference = + StoreKeyReferenceBuilder.of().key("old-store-key").build(); + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getStore()).thenReturn(oldStoreReference); + + final StoreResourceIdentifier newStoreIdentifier = + StoreResourceIdentifierBuilder.of().key("new-store-key").build(); + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getStore()).thenReturn(newStoreIdentifier); + + final ShoppingListUpdateAction setStoreUpdateAction = + ShoppingListUpdateActionUtils.buildSetStoreUpdateAction(oldShoppingList, newShoppingList) + .orElse(null); + + assertThat(setStoreUpdateAction).isNotNull(); + assertThat(setStoreUpdateAction.getAction()).isEqualTo("setStore"); + assertThat(((ShoppingListSetStoreAction) setStoreUpdateAction).getStore()) + .isEqualTo(newStoreIdentifier); + } + + @Test + void buildSetStoreUpdateAction_WithSameStoreKeys_ShouldNotBuildUpdateAction() { + final StoreKeyReference oldStoreReference = + StoreKeyReferenceBuilder.of().key("same-store-key").build(); + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getStore()).thenReturn(oldStoreReference); + + final StoreResourceIdentifier newStoreIdentifier = + StoreResourceIdentifierBuilder.of().key("same-store-key").build(); + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getStore()).thenReturn(newStoreIdentifier); + + final Optional setStoreUpdateAction = + ShoppingListUpdateActionUtils.buildSetStoreUpdateAction(oldShoppingList, newShoppingList); + + assertThat(setStoreUpdateAction).isNotPresent(); + } + + @Test + void buildSetStoreUpdateAction_WithNullOldStore_ShouldBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getStore()).thenReturn(null); + + final StoreResourceIdentifier newStoreIdentifier = + StoreResourceIdentifierBuilder.of().key("new-store-key").build(); + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getStore()).thenReturn(newStoreIdentifier); + + final ShoppingListUpdateAction setStoreUpdateAction = + ShoppingListUpdateActionUtils.buildSetStoreUpdateAction(oldShoppingList, newShoppingList) + .orElse(null); + + assertThat(setStoreUpdateAction).isNotNull(); + assertThat(setStoreUpdateAction.getAction()).isEqualTo("setStore"); + } + + @Test + void buildSetStoreUpdateAction_WithNullNewStore_ShouldBuildUpdateAction() { + final StoreKeyReference oldStoreReference = + StoreKeyReferenceBuilder.of().key("old-store-key").build(); + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getStore()).thenReturn(oldStoreReference); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getStore()).thenReturn(null); + + final ShoppingListUpdateAction setStoreUpdateAction = + ShoppingListUpdateActionUtils.buildSetStoreUpdateAction(oldShoppingList, newShoppingList) + .orElse(null); + + assertThat(setStoreUpdateAction).isNotNull(); + assertThat(setStoreUpdateAction.getAction()).isEqualTo("setStore"); + } + + @Test + void buildSetStoreUpdateAction_WithBothNull_ShouldNotBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getStore()).thenReturn(null); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getStore()).thenReturn(null); + + final Optional setStoreUpdateAction = + ShoppingListUpdateActionUtils.buildSetStoreUpdateAction(oldShoppingList, newShoppingList); + + assertThat(setStoreUpdateAction).isNotPresent(); + } } 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 8a3eee470e..2b3f936f3c 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListsReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListsReferenceResolutionUtilsTest.java @@ -16,6 +16,8 @@ import com.commercetools.api.models.shopping_list.ShoppingListLineItemDraftBuilder; import com.commercetools.api.models.shopping_list.TextLineItem; import com.commercetools.api.models.shopping_list.TextLineItemDraftBuilder; +import com.commercetools.api.models.store.StoreKeyReference; +import com.commercetools.api.models.store.StoreKeyReferenceBuilder; import com.commercetools.api.models.type.CustomFields; import com.commercetools.api.models.type.CustomFieldsDraftBuilder; import com.commercetools.api.models.type.TypeReference; @@ -225,4 +227,49 @@ void mapToShoppingListDrafts_WithOtherFields_ShouldReturnDraftsCorrectly() { .anonymousId("anonymousId") .build()); } + + @Test + void mapToShoppingListDrafts_WithStoreReference_ShouldReturnDraftsWithStoreKey() { + final String storeKey = "store-key"; + + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getName()).thenReturn(ofEnglish("name")); + when(mockShoppingList.getKey()).thenReturn("key"); + + final StoreKeyReference storeKeyReference = StoreKeyReferenceBuilder.of().key(storeKey).build(); + when(mockShoppingList.getStore()).thenReturn(storeKeyReference); + + when(mockShoppingList.getCustomer()).thenReturn(null); + when(mockShoppingList.getCustom()).thenReturn(null); + when(mockShoppingList.getLineItems()).thenReturn(null); + when(mockShoppingList.getTextLineItems()).thenReturn(null); + + final List shoppingListDrafts = + ShoppingListReferenceResolutionUtils.mapToShoppingListDrafts( + singletonList(mockShoppingList), referenceIdToKeyCache); + + assertThat(shoppingListDrafts).hasSize(1); + assertThat(shoppingListDrafts.get(0).getStore()).isNotNull(); + assertThat(shoppingListDrafts.get(0).getStore().getKey()).isEqualTo(storeKey); + } + + @Test + void mapToShoppingListDrafts_WithNullStore_ShouldReturnDraftsWithNullStore() { + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getName()).thenReturn(ofEnglish("name")); + when(mockShoppingList.getKey()).thenReturn("key"); + when(mockShoppingList.getStore()).thenReturn(null); + + when(mockShoppingList.getCustomer()).thenReturn(null); + when(mockShoppingList.getCustom()).thenReturn(null); + when(mockShoppingList.getLineItems()).thenReturn(null); + when(mockShoppingList.getTextLineItems()).thenReturn(null); + + final List shoppingListDrafts = + ShoppingListReferenceResolutionUtils.mapToShoppingListDrafts( + singletonList(mockShoppingList), referenceIdToKeyCache); + + assertThat(shoppingListDrafts).hasSize(1); + assertThat(shoppingListDrafts.get(0).getStore()).isNull(); + } }