Skip to content

Commit

Permalink
test(#549): tests for accompanying prices in GQL
Browse files Browse the repository at this point in the history
  • Loading branch information
lukashornych committed Jun 3, 2024
1 parent de4576d commit 0f02512
Show file tree
Hide file tree
Showing 10 changed files with 1,232 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,31 @@ public List<PriceContract> getAllPricesForSale() {
}
}



@Nonnull
@Override
public List<PriceForSaleWithAccompanyingPrices> getAllPricesForSaleWithAccompanyingPrices(@Nullable Currency currency,
@Nullable OffsetDateTime atTheMoment,
@Nullable String[] priceListPriority,
@Nonnull AccompanyingPrice[] accompanyingPricesRequest) {
pricePredicate.checkFetched(currency, priceListPriority);
final List<PriceForSaleWithAccompanyingPrices> allPricesForSale = SealedEntity.super.getAllPricesForSaleWithAccompanyingPrices(currency, atTheMoment, priceListPriority, accompanyingPricesRequest);
if (allPricesForSale.size() > 1) {
return allPricesForSale
.stream()
.sorted(
Comparator.comparing(
pricePredicate.getQueryPriceMode() == QueryPriceMode.WITH_TAX ?
it -> it.priceForSale().priceWithTax() :
it -> it.priceForSale().priceWithoutTax()
)
).toList();
} else {
return allPricesForSale;
}
}

@Override
public boolean hasPriceInInterval(@Nonnull BigDecimal from, @Nonnull BigDecimal to, @Nonnull QueryPriceMode queryPriceMode) throws ContextMissingException {
if (pricePredicate.isContextAvailable()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -801,13 +801,7 @@ private GraphQLObjectType buildPriceForSaleObject() {
.field(PriceForSaleDescriptor.TAX_RATE.to(fieldBuilderTransformer.with(priceFieldDecorator)))
.field(PriceForSaleDescriptor.ACCOMPANYING_PRICE.to(fieldBuilderTransformer)
.argument(AccompanyingPriceFieldHeaderDescriptor.PRICE_LISTS
.to(argumentBuilderTransformer))
.argument(AccompanyingPriceFieldHeaderDescriptor.CURRENCY
.to(argumentBuilderTransformer)
.type(typeRef(CURRENCY_ENUM.name())))
.argument(AccompanyingPriceFieldHeaderDescriptor.LOCALE
.to(argumentBuilderTransformer)
.type(typeRef(LOCALE_ENUM.name()))))
.to(argumentBuilderTransformer)))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/FgForrest/evitaDB/blob/main/LICENSE
* https://github.com/FgForrest/evitaDB/blob/master/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -25,10 +25,7 @@

import io.evitadb.externalApi.api.model.PropertyDescriptor;

import java.time.OffsetDateTime;

import static io.evitadb.externalApi.api.model.PrimitivePropertyDataTypeDescriptor.nonNull;
import static io.evitadb.externalApi.api.model.PrimitivePropertyDataTypeDescriptor.nullable;

/**
* Descriptor of header parameters of {@link PriceForSaleDescriptor#ACCOMPANYING_PRICE} field.
Expand All @@ -44,35 +41,4 @@ public interface AccompanyingPriceFieldHeaderDescriptor {
""")
.type(nonNull(String[].class))
.build();
PropertyDescriptor CURRENCY = PropertyDescriptor.builder()
.name("currency")
.description("""
Parameter specifying desired currency of output price if different currency that already defined is desired.
""")
// type is expected to be a currency enum
.build();
PropertyDescriptor VALID_IN = PropertyDescriptor.builder()
.name("validIn")
.description("""
Parameter specifying when output price should be valid. If both `validInNow` and `validIn` parameters are
specified `validIn` is used.
""")
.type(nullable(OffsetDateTime.class))
.build();
PropertyDescriptor VALID_NOW = PropertyDescriptor.builder()
.name("validNow")
.description("""
Parameter specifying when output price should be valid. The date time is resolved to `now` by evitaDB. If both `validNow`
and `validIn` parameters are specified `validIn` is used.
""")
.type(nullable(Boolean.class))
.build();
PropertyDescriptor LOCALE = PropertyDescriptor.builder()
.name("locale")
.description("""
Parameter specifying desired locale price formatting.
If not specified, desired entity locale is used instead.
""")
// type is expected to be a locale enum
.build();
}
Original file line number Diff line number Diff line change
Expand Up @@ -332,13 +332,7 @@ private boolean isCustomPriceFieldPresent(@Nonnull SelectionSetAggregator select
private boolean isCustomPriceForSaleFieldPresent(@Nonnull SelectionSetAggregator selectionSetAggregator) {
return selectionSetAggregator.getImmediateFields(PRICE_FOR_SALE_FIELDS)
.stream()
.anyMatch(f -> !f.getArguments().isEmpty() || isCustomAccompanyingPriceFieldPresent(f));
}

private boolean isCustomAccompanyingPriceFieldPresent(@Nonnull SelectedField priceForSaleField) {
return SelectionSetAggregator.getImmediateFields(PriceForSaleDescriptor.ACCOMPANYING_PRICE.name(), priceForSaleField.getSelectionSet())
.stream()
.anyMatch(apf -> apf.getArguments().size() > 1);
.anyMatch(f -> !f.getArguments().isEmpty());
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ protected OffsetDateTime resolveDesiredValidIn(@Nonnull DataFetchingEnvironment
protected AccompanyingPrice[] resolveDesiredAccompanyingPrices(@Nonnull DataFetchingEnvironment environment) {
return SelectionSetAggregator.getImmediateFields(PriceForSaleDescriptor.ACCOMPANYING_PRICE.name(), environment.getSelectionSet())
.stream()
.filter(f -> f.getArguments().size() == 1 && f.getArguments().containsKey(AccompanyingPriceFieldHeaderDescriptor.PRICE_LISTS.name()))
.map(f -> new AccompanyingPrice(
f.getAlias() != null ? f.getAlias() : f.getName(),
((List<String>) f.getArguments().get(AccompanyingPriceFieldHeaderDescriptor.PRICE_LISTS.name()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,10 @@
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import io.evitadb.api.requestResponse.data.PriceContract;
import io.evitadb.api.requestResponse.data.PricesContract.AccompanyingPrice;
import io.evitadb.api.requestResponse.data.PricesContract.PriceForSaleWithAccompanyingPrices;
import io.evitadb.api.requestResponse.data.structure.EntityDecorator;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.dto.PrefetchedPriceForSale;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.AccompanyingPriceFieldHeaderDescriptor;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.resolver.dataFetcher.EntityQueryContext;
import io.evitadb.externalApi.graphql.exception.GraphQLInternalError;
import io.evitadb.externalApi.graphql.exception.GraphQLInvalidArgumentException;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.OffsetDateTime;
import java.util.Currency;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

/**
Expand All @@ -51,45 +40,17 @@
*/
public class AccompanyingPriceDataFetcher implements DataFetcher<DataFetcherResult<PriceContract>> {

private static final String CUSTOM_ACCOMPANYING_PRICE_KEY = "customAccompanyingPrice";

@Override
@Nonnull
public DataFetcherResult<PriceContract> get(@Nonnull DataFetchingEnvironment environment) throws Exception {
final EntityQueryContext context = environment.getLocalContext();
final PrefetchedPriceForSale prefetchedPrices = environment.getSource();
final EntityDecorator entity = prefetchedPrices.getParentEntity();
final String priceName = resolvePriceName(environment);

PriceContract pickedPrice = null;

if (environment.getArguments().size() == 1 &&
(environment.getArguments().containsKey(AccompanyingPriceFieldHeaderDescriptor.PRICE_LISTS.name()))) {
final Optional<PriceContract> priceForName = getPrefetchedPrice(prefetchedPrices, priceName);
pickedPrice = priceForName.orElse(null);
} else {
final String[] priceLists = resolveDesiredPriceListsForCustomPrice(environment);
final Currency currency = resolveDesiredCurrencyForCustomPrice(environment, context);
final OffsetDateTime validIn = resolveDesiredValidInForCustomPrice(environment, entity, context);

final Optional<PriceForSaleWithAccompanyingPrices> customPriceForSaleWithAccompanyingPrices = entity.getPriceForSaleWithAccompanyingPrices(
currency,
validIn,
context.getDesiredPriceInPriceLists(),
new AccompanyingPrice[] { new AccompanyingPrice(CUSTOM_ACCOMPANYING_PRICE_KEY, priceLists) }
);

pickedPrice = customPriceForSaleWithAccompanyingPrices.flatMap(it -> it.accompanyingPrices().get(CUSTOM_ACCOMPANYING_PRICE_KEY)).orElse(null);
}

final Locale customLocale = environment.getArgument(AccompanyingPriceFieldHeaderDescriptor.LOCALE.name());
final EntityQueryContext newContext = context.toBuilder()
.desiredLocale(customLocale != null ? customLocale : context.getDesiredLocale())
.build();
final String priceName = resolvePriceName(environment);
final Optional<PriceContract> priceForName = getPrefetchedPrice(prefetchedPrices, priceName);
final PriceContract pickedPrice = priceForName.orElse(null);

return DataFetcherResult.<PriceContract>newResult()
.data(pickedPrice)
.localContext(newContext)
.build();
}

Expand All @@ -107,31 +68,4 @@ private Optional<PriceContract> getPrefetchedPrice(@Nonnull PrefetchedPriceForSa
}
return prefetchedPrice;
}

@Nonnull
private String[] resolveDesiredPriceListsForCustomPrice(@Nonnull DataFetchingEnvironment environment) {
return Optional.ofNullable((List<String>) environment.getArgument(AccompanyingPriceFieldHeaderDescriptor.PRICE_LISTS.name()))
.map(it -> it.toArray(String[]::new))
.orElseThrow(() -> new GraphQLInvalidArgumentException("Missing price list argument. You can use `" + AccompanyingPriceFieldHeaderDescriptor.PRICE_LISTS.name() + "` parameter for specifying custom price list."));
}

@Nonnull
private Currency resolveDesiredCurrencyForCustomPrice(@Nonnull DataFetchingEnvironment environment, @Nonnull EntityQueryContext context) {
return Optional.ofNullable((Currency) environment.getArgument(AccompanyingPriceFieldHeaderDescriptor.CURRENCY.name()))
.or(() -> Optional.ofNullable(context.getDesiredPriceInCurrency()))
.orElseThrow(() -> new GraphQLInvalidArgumentException("Missing `currency` argument. You can use `" + AccompanyingPriceFieldHeaderDescriptor.CURRENCY.name() + "` parameter for specifying custom currency."));
}

@Nullable
private OffsetDateTime resolveDesiredValidInForCustomPrice(@Nonnull DataFetchingEnvironment environment,
@Nonnull EntityDecorator entity,
@Nonnull EntityQueryContext context) {
return Optional.ofNullable((OffsetDateTime) environment.getArgument(AccompanyingPriceFieldHeaderDescriptor.VALID_IN.name()))
.or(() -> Optional.ofNullable((Boolean) environment.getArgument(AccompanyingPriceFieldHeaderDescriptor.VALID_NOW.name()))
.map(validNow -> validNow ? entity.getAlignedNow() : null))
.or(() -> Optional.ofNullable(context.getDesiredPriceValidIn()))
.or(() -> Optional.of(context.isDesiredPriceValidInNow())
.map(validNow -> validNow ? entity.getAlignedNow() : null))
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@

import io.evitadb.api.requestResponse.data.EntityClassifierWithParent;
import io.evitadb.api.requestResponse.data.PriceContract;
import io.evitadb.api.requestResponse.data.PricesContract.AccompanyingPrice;
import io.evitadb.api.requestResponse.data.PricesContract.PriceForSaleWithAccompanyingPrices;
import io.evitadb.api.requestResponse.data.SealedEntity;
import io.evitadb.api.requestResponse.data.structure.EntityDecorator;
import io.evitadb.externalApi.ExternalApiFunctionTestsSupport;
import io.evitadb.externalApi.api.catalog.dataApi.model.AssociatedDataDescriptor;
import io.evitadb.externalApi.api.catalog.dataApi.model.EntityDescriptor;
Expand Down Expand Up @@ -59,6 +62,8 @@
import static io.evitadb.test.generator.DataGenerator.*;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Ancestor for tests for GraphQL catalog endpoint.
Expand Down Expand Up @@ -291,6 +296,80 @@ protected Map<String, Object> createEntityDtoWithAssociatedData(@Nonnull SealedE
.build();
}

@Nonnull
protected Map<String, Object> createEntityDtoWithAccompanyingPricesForSinglePriceForSale(@Nonnull SealedEntity entity) {
final String vipPrice = "vipPrice";

final EntityDecorator entityDecorator = ((EntityDecorator) entity);
final Optional<PriceForSaleWithAccompanyingPrices> prices = entityDecorator.getPriceForSaleWithAccompanyingPrices(
CURRENCY_CZK,
null,
new String[]{PRICE_LIST_BASIC},
new AccompanyingPrice[]{
new AccompanyingPrice(PriceForSaleDescriptor.ACCOMPANYING_PRICE.name(), PRICE_LIST_REFERENCE),
new AccompanyingPrice(vipPrice, PRICE_LIST_VIP)
}
);
assertTrue(prices.isPresent());

return map()
.e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey())
.e(EntityDescriptor.TYPE.name(), Entities.PRODUCT)
.e(GraphQLEntityDescriptor.PRICE_FOR_SALE.name(), map()
.e(TYPENAME_FIELD, PriceForSaleDescriptor.THIS.name())
.e(PriceForSaleDescriptor.PRICE_WITH_TAX.name(), prices.get().priceForSale().priceWithTax().toString())
.e(PriceForSaleDescriptor.ACCOMPANYING_PRICE.name(), prices.get().accompanyingPrices().get(PriceForSaleDescriptor.ACCOMPANYING_PRICE.name())
.map(price -> map()
.e(TYPENAME_FIELD, PriceDescriptor.THIS.name())
.e(PriceDescriptor.PRICE_WITH_TAX.name(), price.priceWithTax().toString()))
.orElse(null))
.e(vipPrice, prices.get().accompanyingPrices().get(vipPrice)
.map(price -> map()
.e(TYPENAME_FIELD, PriceDescriptor.THIS.name())
.e(PriceDescriptor.PRICE_WITH_TAX.name(), price.priceWithTax().toString()))
.orElse(null))
.build())
.build();
}

@Nonnull
protected Map<String, Object> createEntityDtoWithAccompanyingPricesForAllPricesForSale(@Nonnull SealedEntity entity) {
final String vipPrice = "vipPrice";

final EntityDecorator entityDecorator = ((EntityDecorator) entity);
final List<PriceForSaleWithAccompanyingPrices> allPrices = entityDecorator.getAllPricesForSaleWithAccompanyingPrices(
CURRENCY_CZK,
null,
new String[]{PRICE_LIST_BASIC},
new AccompanyingPrice[]{
new AccompanyingPrice(PriceForSaleDescriptor.ACCOMPANYING_PRICE.name(), PRICE_LIST_REFERENCE),
new AccompanyingPrice(vipPrice, PRICE_LIST_VIP)
}
);
assertFalse(allPrices.isEmpty());

return map()
.e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey())
.e(EntityDescriptor.TYPE.name(), Entities.PRODUCT)
.e(GraphQLEntityDescriptor.ALL_PRICES_FOR_SALE.name(), allPrices.stream()
.map(prices -> map()
.e(TYPENAME_FIELD, PriceForSaleDescriptor.THIS.name())
.e(PriceForSaleDescriptor.PRICE_WITH_TAX.name(), prices.priceForSale().priceWithTax().toString())
.e(PriceForSaleDescriptor.ACCOMPANYING_PRICE.name(), prices.accompanyingPrices().get(PriceForSaleDescriptor.ACCOMPANYING_PRICE.name())
.map(price -> map()
.e(TYPENAME_FIELD, PriceDescriptor.THIS.name())
.e(PriceDescriptor.PRICE_WITH_TAX.name(), price.priceWithTax().toString()))
.orElse(null))
.e(vipPrice, prices.accompanyingPrices().get(vipPrice)
.map(price -> map()
.e(TYPENAME_FIELD, PriceDescriptor.THIS.name())
.e(PriceDescriptor.PRICE_WITH_TAX.name(), price.priceWithTax().toString()))
.orElse(null))
.build())
.toList())
.build();
}

@Nonnull
protected Map<String, Object> createEntityWithSelfParentsDto(@Nonnull SealedEntity hierarchicalEntity, boolean withBody) {
EntityClassifierWithParent node = hierarchicalEntity;
Expand Down
Loading

0 comments on commit 0f02512

Please sign in to comment.