package com.commercetools.sync.integration.externalsource.products; 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.function.BiConsumer; import java.util.function.Consumer; 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; import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.createCustomerGroup; import static com.commercetools.sync.integration.commons.utils.ITUtils.EMPTY_SET_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.NON_EMPTY_SEY_CUSTOM_FIELD_NAME; import static com.commercetools.sync.integration.commons.utils.ITUtils.NULL_NODE_SET_CUSTOM_FIELD_NAME; import static com.commercetools.sync.integration.commons.utils.ITUtils.NULL_SET_CUSTOM_FIELD_NAME; import static com.commercetools.sync.integration.commons.utils.ITUtils.createVariantDraft; import static com.commercetools.sync.integration.commons.utils.ProductITUtils.createPricesCustomType; 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.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_DE_111_EUR; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_DE_111_EUR_01_02; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_DE_111_EUR_02_03; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_DE_111_EUR_03_04; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_DE_111_USD; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_DE_222_EUR_CUST1; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_DE_22_USD; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_DE_333_USD_CUST1; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_FR_777_EUR_01_04; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_FR_888_EUR_01_03; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_FR_999_EUR_03_06; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_NE_123_EUR_01_04; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_NE_321_EUR_04_06; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_NE_777_EUR_01_04; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_NE_777_EUR_05_07; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_UK_111_GBP; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_UK_111_GBP_01_02; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_UK_111_GBP_02_03; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_UK_333_GBP_03_05; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_UK_999_GBP; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_US_111_USD; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_US_666_USD_CUST2_01_02; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.byMonth; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.createCustomFieldsJsonMap; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.getPriceDraft; import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; import static com.neovisionaries.i18n.CountryCode.DE; import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; import static io.sphere.sdk.models.DefaultCurrencyUnits.USD; 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.emptyList; 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<String> errorCallBackMessages; private List<String> warningCallBackMessages; private List<Throwable> errorCallBackExceptions; private List<UpdateAction<Product>> 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 BiConsumer<String, Throwable> errorCallBack = (errorMessage, exception) -> { errorCallBackMessages.add(errorMessage); errorCallBackExceptions.add(exception); }; final Consumer<String> warningCallBack = warningMessage -> warningCallBackMessages.add(warningMessage); final TriFunction<List<UpdateAction<Product>>, ProductDraft, Product, List<UpdateAction<Product>>> 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"), createVariantDraft("v1", null, null)) .key("existingProduct") .build(); product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); 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<PriceDraft> 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))); 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<PriceDraft> 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); 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))); 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( 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), AddPrice.ofVariantId(masterVariantId, DRAFT_DE_111_EUR_02_03, true), 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<PriceDraft> newPrices = asList( DRAFT_US_111_USD, 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); 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))); 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( AddPrice.ofVariantId(masterVariantId, DRAFT_US_111_USD, true), 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), AddPrice.ofVariantId(masterVariantId, DRAFT_DE_111_EUR_02_03, true), 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<PriceDraft> oldPrices = 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); 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))); 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<PriceDraft> 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<PriceDraft> newPrices = asList( DRAFT_DE_111_EUR, DRAFT_UK_111_GBP, price1WithCustomerGroupWithKey, price2WithCustomerGroupWithKey); 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))); 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<PriceDraft> newPricesWithIds = asList( DRAFT_DE_111_EUR, DRAFT_UK_111_GBP, price1WithCustomerGroupWithId, price2WithCustomerGroupWithId); assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), newPricesWithIds); } @Test void withMixedCasesOfPriceMatches_ShouldBuildActions() { // Preparation createExistingProductWithPrices(); final ProductDraft newProductDraft = createProductDraftWithNewPrices(); 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 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 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<PriceDraft> newPricesWithReferenceIds = asList( DRAFT_DE_111_EUR, de222EurCust1Ofid, DRAFT_DE_111_EUR_01_02, DRAFT_DE_111_EUR_03_04, withChannel1CustomType1WithEnDeOfId, withChannel2CustomType1WithEnDeOfId, de333UsdCust1Ofid, DRAFT_DE_22_USD, DRAFT_UK_111_GBP_01_02, DRAFT_UK_999_GBP, us666Usd0102Cust2OfId, DRAFT_FR_888_EUR_01_03, 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<String, JsonNode> 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<String, JsonNode> 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.ofTypeIdAndJson(customType1.getId(), newCustomFieldsJsonMap); final PriceDraft withChannel1CustomType1WithNullSet = getPriceDraft(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<Price> 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<String, JsonNode> 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<String, JsonNode> 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.ofTypeIdAndJson(customType1.getId(), newCustomFieldsJsonMap); final PriceDraft withChannel1CustomType1WithNullSet = getPriceDraft(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<Price> 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<String, JsonNode> 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<String, JsonNode> 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.ofTypeIdAndJson(customType1.getId(), newCustomFieldsJsonMap); final PriceDraft withChannel1CustomType1WithNullSet = getPriceDraft(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<Price> 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<String, JsonNode> 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.ofTypeIdAndJson(customType1.getId(), new HashMap<>()); final PriceDraft withChannel1CustomType1WithNullSet = getPriceDraft(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<Price> 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: * <ul> * <li>DE_111_EUR</li> * <li>DE_222_EUR_CUST1</li> * <li>DE_111_EUR_01_02</li> * <li>DE_111_EUR_03_04</li> * <li>DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELD{en, de}</li> * <li>DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELD{en, de}</li> * <li>DE_333_USD_CUST1</li> * <li>DE_22_USD</li> * <li>UK_111_GBP_01_02</li> * <li>UK_999_GBP</li> * <li>US_666_USD_CUST2_01_02,</li> * <li>FR_888_EUR_01_03</li> * <li>FR_999_EUR_03_06</li> * <li>NE_777_EUR_01_04</li> * <li>NE_777_EUR_05_07</li> * </ul> */ private ProductDraft createProductDraftWithNewPrices() { final ObjectNode lTextWithEnDe = JsonNodeFactory.instance.objectNode() .put("de", "rot") .put("en", "red"); final CustomFieldsDraft customType1WithEnDeOfKey = CustomFieldsDraft.ofTypeIdAndJson("customType1", createCustomFieldsJsonMap(LOCALISED_STRING_CUSTOM_FIELD_NAME, lTextWithEnDe)); final PriceDraft withChannel1CustomType1WithEnDeOfKey = getPriceDraft(BigDecimal.valueOf(100), EUR, DE, null, byMonth(1), byMonth(2), "channel1", customType1WithEnDeOfKey); final PriceDraft withChannel2CustomType1WithEnDeOfKey = getPriceDraft(BigDecimal.valueOf(100), EUR, DE, null, byMonth(1), byMonth(2), "channel2", customType1WithEnDeOfKey); final List<PriceDraft> newPrices = asList( DRAFT_DE_111_EUR, DRAFT_DE_222_EUR_CUST1, DRAFT_DE_111_EUR_01_02, DRAFT_DE_111_EUR_03_04, withChannel1CustomType1WithEnDeOfKey, withChannel2CustomType1WithEnDeOfKey, DRAFT_DE_333_USD_CUST1, DRAFT_DE_22_USD, DRAFT_UK_111_GBP_01_02, DRAFT_UK_999_GBP, DRAFT_US_666_USD_CUST2_01_02, DRAFT_FR_888_EUR_01_03, DRAFT_FR_999_EUR_03_06, 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: * <ul> * <li>DE_111_EUR</li> * <li>DE_345_EUR_CUST2</li> * <li>DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELD{en, de, it}</li> * <li>DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE2_CUSTOMFIELD{en, de}</li> * <li>DE_22_USD</li> * <li>UK_111_GBP_01_02</li> * <li>UK_111_GBP_02_03</li> * <li>UK_333_GBP_03_05</li> * <li>FR_777_EUR_01_04</li> * <li>NE_123_EUR_01_04</li> * <li>NE_321_EUR_04_06</li> * </ul> */ 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<PriceDraft> oldPrices = asList( DRAFT_DE_111_EUR, de345EurCust2OfId, de222Eur0102Channel1Ct1DeEnItOfId, de222Eur0102Channel2Ct2DeEnOfId, DRAFT_DE_22_USD, DRAFT_UK_111_GBP_01_02, DRAFT_UK_111_GBP_02_03, DRAFT_UK_333_GBP_03_05, DRAFT_FR_777_EUR_01_04, DRAFT_NE_123_EUR_01_04, DRAFT_NE_321_EUR_04_06 ); 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. * * <p>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<Price> prices, @Nonnull final List<PriceDraft> priceDrafts) { final Map<PriceCompositeId, Price> priceMap = collectionToMap(prices, PriceCompositeId::of); final PriceCompositeId[] priceCompositeIds = priceDrafts.stream() .map(PriceCompositeId::of) .toArray(PriceCompositeId[]::new); assertThat(priceMap).containsOnlyKeys(priceCompositeIds); } }