Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

ext{
commercetoolsJavaSdkV2Version = '17.18.0'
commercetoolsJavaSdkV2Version = '17.28.0'
mockitoJunitJupiterVersion = '5.16.1'
jupiterApiVersion = '5.11.3'
assertjVersion = '3.26.3'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,121 @@ void sync_shouldSyncProductsWithoutAnyVariants() {
assertThat(syncStatistics).hasValues(2, 1, 1, 0, 0);
}

@Test
void sync_withNoChangesInIntegerAttribute_shouldNotUpdateTheProduct() {
// preparation
final List<ProductUpdateAction> updateActions = new ArrayList<>();
final TriConsumer<SyncException, Optional<ProductDraft>, Optional<ProductProjection>>
warningCallBack =
(exception, newResource, oldResource) ->
warningCallBackMessages.add(exception.getMessage());

final ProductSyncOptions customOptions =
ProductSyncOptionsBuilder.of(TestClientUtils.CTP_TARGET_CLIENT)
.errorCallback(
(exception, oldResource, newResource, actions) ->
collectErrors(exception.getMessage(), exception.getCause()))
.warningCallback(warningCallBack)
.beforeUpdateCallback(
(actions, draft, old) -> {
updateActions.addAll(actions);
return actions;
})
.build();

final ProductDraft productDraft =
createProductDraftBuilder(
PRODUCT_KEY_1_RESOURCE_PATH,
ProductTypeResourceIdentifierBuilder.of().key(productType.getKey()).build())
.categories(emptyList())
.taxCategory((TaxCategoryResourceIdentifier) null)
.state((StateResourceIdentifier) null)
.build();

// Creating the attribute draft with the changes
final Attribute sortAttrDraft = AttributeBuilder.of().name("sort").value(10).build();

// Creating the product variant draft with the product reference attribute
final List<Attribute> attributes = singletonList(sortAttrDraft);

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);
productSync.sync(singletonList(productDraftWithChangedAttributes)).toCompletableFuture().join();
final ProductSyncStatistics syncStatistics =
productSync
.sync(singletonList(productDraftWithChangedAttributes))
.toCompletableFuture()
.join();

assertThat(syncStatistics).hasValues(2, 0, 1, 0, 0);
}

@Test
void sync_withNoChangesInLenumAttribute_shouldNotUpdateTheProduct() {
// preparation
final List<ProductUpdateAction> updateActions = new ArrayList<>();
final TriConsumer<SyncException, Optional<ProductDraft>, Optional<ProductProjection>>
warningCallBack =
(exception, newResource, oldResource) ->
warningCallBackMessages.add(exception.getMessage());

final ProductSyncOptions customOptions =
ProductSyncOptionsBuilder.of(TestClientUtils.CTP_TARGET_CLIENT)
.errorCallback(
(exception, oldResource, newResource, actions) ->
collectErrors(exception.getMessage(), exception.getCause()))
.warningCallback(warningCallBack)
.beforeUpdateCallback(
(actions, draft, old) -> {
updateActions.addAll(actions);
return actions;
})
.build();

final ProductDraft productDraft =
createProductDraftBuilder(
PRODUCT_KEY_1_RESOURCE_PATH,
ProductTypeResourceIdentifierBuilder.of().key(productType.getKey()).build())
.categories(emptyList())
.taxCategory((TaxCategoryResourceIdentifier) null)
.state((StateResourceIdentifier) null)
.build();

// Creating the attribute draft with the changes
final Attribute technologyAttrDraft =
AttributeBuilder.of().name("technology").value("laser").build();

// Creating the product variant draft with the product reference attribute
final List<Attribute> attributes = singletonList(technologyAttrDraft);

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);
productSync.sync(singletonList(productDraftWithChangedAttributes)).toCompletableFuture().join();
final ProductSyncStatistics syncStatistics =
productSync
.sync(singletonList(productDraftWithChangedAttributes))
.toCompletableFuture()
.join();

assertThat(syncStatistics).hasValues(2, 0, 1, 0, 0);
}

@Test
void sync_withProductContainingAttributeChanges_shouldSyncProductCorrectly() {
// preparation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ public final class AttributeUtils {
* converted value in the attribute.
*
* @param attribute - Attribute to replace it's value with a JSON representation
* @return - a {@link JsonNode} representing the attribute's value. extracted from the given
* attribute or empty list if the attribute * doesn't contain reference types.
* @return - a {@link JsonNode} representing the attribute's value.
*/
@Nonnull
public static JsonNode replaceAttributeValueWithJsonAndReturnValue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import com.commercetools.api.models.product.ProductUpdateAction;
import com.commercetools.api.models.product.ProductVariant;
import com.commercetools.api.models.product.ProductVariantDraft;
import com.commercetools.api.models.product.SearchKeyword;
import com.commercetools.api.models.product.SearchKeywords;
import com.commercetools.api.models.state.State;
import com.commercetools.api.models.state.StateResourceIdentifier;
Expand Down Expand Up @@ -337,14 +338,23 @@ public static Optional<ProductUpdateAction> buildSetSearchKeywordsUpdateAction(
if (newSearchKeywords == null) {
return Optional.empty();
} else {
return buildUpdateAction(
oldSearchKeywords,
newSearchKeywords,
() ->
ProductSetSearchKeywordsActionBuilder.of()
.searchKeywords(newSearchKeywords)
.staged(true)
.build());
// For some reasons, values for searchKeywords could be null or {} since Java SDK v17.28.0
// Even though they mean the same thing, they are not equals and would trigger update action
// Thus I had to do a manual comparison here.
final Map<String, List<SearchKeyword>> newSearchValues = newSearchKeywords.values();
final Map<String, List<SearchKeyword>> oldSearchValue = oldSearchKeywords.values();
if (newSearchValues == null && (oldSearchValue == null || oldSearchValue.size() == 0)) {
return Optional.empty();
} else {
return buildUpdateAction(
oldSearchKeywords,
newSearchKeywords,
() ->
ProductSetSearchKeywordsActionBuilder.of()
.searchKeywords(newSearchKeywords)
.staged(true)
.build());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.commercetools.sync.products.utils;

import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction;
import static java.lang.String.format;

import com.commercetools.api.models.product.Attribute;
Expand All @@ -10,12 +9,17 @@
import com.commercetools.api.models.product.ProductSetAttributeInAllVariantsActionBuilder;
import com.commercetools.api.models.product.ProductUpdateAction;
import com.commercetools.sync.commons.exceptions.BuildUpdateActionException;
import com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils;
import com.commercetools.sync.products.AttributeMetaData;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import io.vrap.rmf.base.client.utils.json.JsonUtils;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

Expand Down Expand Up @@ -93,5 +97,22 @@ public static Optional<ProductUpdateAction> buildProductVariantAttributeUpdateAc
.build());
}

@Nonnull
private static Optional<ProductUpdateAction> buildUpdateAction(
final JsonNode oldAttributeValueAsJson,
final JsonNode newAttributeValueAsJson,
final Supplier<ProductUpdateAction> actionSupplier) {
if (oldAttributeValueAsJson instanceof ObjectNode
&& newAttributeValueAsJson instanceof TextNode) {
String oldKey = oldAttributeValueAsJson.get("key").asText();
String newKey = newAttributeValueAsJson.asText();
return !Objects.equals(oldKey, newKey)
? Optional.ofNullable(actionSupplier.get())
: Optional.empty();
}
return CommonTypeUpdateActionUtils.buildUpdateAction(
oldAttributeValueAsJson, newAttributeValueAsJson, actionSupplier);
}

private ProductVariantAttributeUpdateActionUtils() {}
}
34 changes: 34 additions & 0 deletions src/test/resources/product-type.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,40 @@
"description": "all fleisch",
"classifier": "Complex",
"attributes": [
{
"name": "technology",
"label": {
"en": "technology"
},
"isRequired": false,
"type": {
"name": "lenum",
"values": [
{
"key": "laser",
"label": {
"en": "Laser"
}
},
{
"key": "plasma",
"label": {
"en": "Plasma"
}
},
{
"key": "waterjet",
"label": {
"en": "Waterjet"
}
}
]
},
"attributeConstraint": "None",
"isSearchable": false,
"inputHint": "SingleLine",
"displayGroup": "Other"
},
{
"name": "priceInfo",
"label": {
Expand Down
Loading