From f8ba7433b395ac9d37a25216d71ef60d8de83d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levente=20S=C3=A1ntha?= Date: Mon, 27 May 2024 22:16:03 +0300 Subject: [PATCH] SITES-22111 - Extend core CIF graphQL resolver with new function/API to expose Errors (#1006) * added getErrors() in AbstractRetriever * provided implementation for getErrors() in existing retrievers * added hasErrors() in AbstractRetriever * added hasErrors() and getErrors() in SearchResultSet * updated unit tests * added Javadoc * improved error message in the Error response based on runtime exception * fixed typo --- .../client/MagentoGraphqlClientImpl.java | 10 +- .../v1/breadcrumb/BreadcrumbRetriever.java | 3 +- .../models/productlist/package-info.java | 2 +- .../AbstractCategoriesRetriever.java | 3 +- .../retriever/AbstractCategoryRetriever.java | 3 +- .../retriever/AbstractProductRetriever.java | 3 +- .../retriever/AbstractProductsRetriever.java | 3 +- .../models/retriever/AbstractRetriever.java | 34 ++++++ .../models/retriever/package-info.java | 2 +- .../internal/models/SearchResultsSetImpl.java | 12 ++ .../services/SearchFilterServiceImpl.java | 66 ++++++---- .../services/SearchResultsServiceImpl.java | 10 +- .../core/search/models/SearchResultsSet.java | 20 ++++ .../core/search/models/package-info.java | 2 +- .../client/MagentoGraphqlClientImplTest.java | 14 ++- .../retriever/AbstractRetrieverTest.java | 113 ++++++++++++++++++ .../models/SearchResultsSetImplTest.java | 11 ++ .../services/SearchFilterServiceImplTest.java | 24 ++++ .../SearchResultsServiceImplTest.java | 6 +- 19 files changed, 300 insertions(+), 41 deletions(-) create mode 100644 bundles/core/src/test/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractRetrieverTest.java diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImpl.java index e7fcade412..c50c736ad4 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImpl.java @@ -347,7 +347,15 @@ private static GraphqlClient adaptToGraphqlClient(Resource resource) { private static GraphqlResponse newErrorResponse(Throwable throwable) { GraphqlResponse response = new GraphqlResponse<>(); Error error = new Error(); - error.setMessage(throwable.getMessage()); + + StringBuilder sb = new StringBuilder(); + sb.append(String.format("[%s: \"%s\"]", throwable.getClass().getName(), throwable.getMessage())); + while (throwable.getCause() != null) { + throwable = throwable.getCause(); + sb.append(String.format(" Caused by: [%s: \"%s\"]", throwable.getClass().getName(), throwable.getMessage())); + } + + error.setMessage(sb.toString()); error.setCategory(MagentoGraphqlClient.RUNTIME_ERROR_CATEGORY); response.setErrors(Collections.singletonList(error)); return response; diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetriever.java index da2b9a316b..2cb746683f 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetriever.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/internal/models/v1/breadcrumb/BreadcrumbRetriever.java @@ -104,7 +104,8 @@ protected void populate() { GraphqlResponse response = executeQuery(); - if (CollectionUtils.isNotEmpty(response.getErrors())) { + errors = response.getErrors(); + if (CollectionUtils.isNotEmpty(errors)) { categories = Collections.emptyList(); product = Optional.empty(); return; diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/productlist/package-info.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/productlist/package-info.java index 7350d70ca4..b8576bb5c0 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/productlist/package-info.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/productlist/package-info.java @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ -@Version("5.0.0") +@Version("5.1.0") package com.adobe.cq.commerce.core.components.models.productlist; import org.osgi.annotation.versioning.Version; \ No newline at end of file diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractCategoriesRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractCategoriesRetriever.java index 9eaa44ad00..aae64d3553 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractCategoriesRetriever.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractCategoriesRetriever.java @@ -194,7 +194,8 @@ protected GraphqlResponse executeQuery() { @Override protected void populate() { GraphqlResponse response = executeQuery(); - if (CollectionUtils.isEmpty(response.getErrors())) { + errors = response.getErrors(); + if (CollectionUtils.isEmpty(errors)) { Query rootQuery = response.getData(); categories = rootQuery.getCategoryList(); categories.sort(Comparator.comparing(c -> identifiers.indexOf(c.getUid().toString()))); diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractCategoryRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractCategoryRetriever.java index dec48f193d..17650acde0 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractCategoryRetriever.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractCategoryRetriever.java @@ -285,7 +285,8 @@ protected GraphqlResponse executeQuery() { @Override protected void populate() { GraphqlResponse response = executeQuery(); - if (CollectionUtils.isEmpty(response.getErrors())) { + errors = response.getErrors(); + if (CollectionUtils.isEmpty(errors)) { Query rootQuery = response.getData(); if (rootQuery.getCategoryList() != null && !rootQuery.getCategoryList().isEmpty()) { category = Optional.of(rootQuery.getCategoryList().get(0)); diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractProductRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractProductRetriever.java index 3d35b2d6e0..ff28cfb38e 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractProductRetriever.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractProductRetriever.java @@ -233,7 +233,8 @@ protected ProductPriceQueryDefinition generatePriceQuery() { protected void populate() { // Get product list from response GraphqlResponse response = executeQuery(); - if (CollectionUtils.isEmpty(response.getErrors())) { + errors = response.getErrors(); + if (CollectionUtils.isEmpty(errors)) { Query rootQuery = response.getData(); List products = rootQuery.getProducts().getItems(); diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractProductsRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractProductsRetriever.java index f5ac8b4a14..dab4512310 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractProductsRetriever.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractProductsRetriever.java @@ -204,7 +204,8 @@ protected GraphqlResponse executeQuery() { protected void populate() { // Get product list from response GraphqlResponse response = executeQuery(); - if (CollectionUtils.isEmpty(response.getErrors())) { + errors = response.getErrors(); + if (CollectionUtils.isEmpty(errors)) { Query rootQuery = response.getData(); products = rootQuery.getProducts().getItems(); } else { diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractRetriever.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractRetriever.java index 014e9c3309..e006ec06cf 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractRetriever.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractRetriever.java @@ -15,6 +15,10 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ package com.adobe.cq.commerce.core.components.models.retriever; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import com.adobe.cq.commerce.core.components.client.MagentoGraphqlClient; import com.adobe.cq.commerce.graphql.client.GraphqlResponse; import com.adobe.cq.commerce.magento.graphql.Query; @@ -24,6 +28,7 @@ * Abstract implementation of retriever that fetches data using GraphQL. */ public abstract class AbstractRetriever { + private static final List INITIAL_ERRORS = Collections.unmodifiableList(new ArrayList<>()); /** * Generated or fully customized query. @@ -34,6 +39,7 @@ public abstract class AbstractRetriever { * Instance of the Magento GraphQL client. */ protected MagentoGraphqlClient client; + protected List errors = INITIAL_ERRORS; public AbstractRetriever(MagentoGraphqlClient client) { if (client == null) { @@ -51,8 +57,36 @@ public void setQuery(String query) { this.query = query; } + /** + * Returns the errors encountered during the retrieval of GraphQL results. + * + * @return list of errors + * + * @throws IllegalStateException if this method is invoked earlier than {@link #populate()} on this instance + */ + public final List getErrors() { + if (errors == INITIAL_ERRORS) { + throw new IllegalStateException("The populate() method must be called and it must populate 'errors' before getErrors()."); + } + + return errors == null ? Collections.emptyList() : errors; + } + + /** + * Checks whether there were any errors encountered during the retrieval of GraphQL results. + * + * @return {@code true} if there where any errors, {@code false} otherwise + * + * @throws IllegalStateException if this method is invoked earlier than {@link #populate()} on this instance + */ + public final boolean hasErrors() { + return !getErrors().isEmpty(); + } + /** * Executes the query and parses the response. + * Implementors should initialize the {@link #errors} field with the errors + * in the GraphQL response returned by {@link #executeQuery()}. */ abstract protected void populate(); diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/package-info.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/package-info.java index ec0ded13a1..f2b42acf4b 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/package-info.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/components/models/retriever/package-info.java @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ -@Version("2.1.0") +@Version("2.2.0") package com.adobe.cq.commerce.core.components.models.retriever; import org.osgi.annotation.versioning.Version; \ No newline at end of file diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImpl.java index 1bd445acb3..4d070c6eaa 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImpl.java @@ -16,6 +16,7 @@ package com.adobe.cq.commerce.core.search.internal.models; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,6 +30,7 @@ import com.adobe.cq.commerce.core.search.models.SearchOptions; import com.adobe.cq.commerce.core.search.models.SearchResultsSet; import com.adobe.cq.commerce.core.search.models.SorterKey; +import com.adobe.cq.commerce.magento.graphql.gson.Error; public class SearchResultsSetImpl implements SearchResultsSet { @@ -38,6 +40,7 @@ public class SearchResultsSetImpl implements SearchResultsSet { private List searchAggregations = new ArrayList<>(); private Pager pager; private SorterImpl sorter = new SorterImpl(); + private List errors; @Nonnull @Override @@ -144,4 +147,13 @@ public boolean hasSorting() { List keys = getSorter().getKeys(); return keys != null && !keys.isEmpty(); } + + @Override + public List getErrors() { + return errors == null ? Collections.emptyList() : errors; + } + + public void setErrors(List errors) { + this.errors = errors; + } } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchFilterServiceImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchFilterServiceImpl.java index 1e226bba7d..60dbfc0670 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchFilterServiceImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchFilterServiceImpl.java @@ -15,12 +15,14 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ package com.adobe.cq.commerce.core.search.internal.services; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.SyntheticResource; @@ -85,41 +87,58 @@ public List retrieveCurrentlyAvailableCommerceFilters(f return Optional.ofNullable(page.adaptTo(Resource.class)) .map(r -> new SyntheticResource(r.getResourceResolver(), r.getPath(), SearchFilterService.class.getName())) .map(r -> r.adaptTo(MagentoGraphqlClient.class)) - .map(this::retrieveCurrentlyAvailableCommerceFilters) + .map(magentoGraphqlClient -> retrieveCurrentlyAvailableCommerceFiltersInfo(magentoGraphqlClient).getLeft()) .orElseGet(Collections::emptyList); } - public List retrieveCurrentlyAvailableCommerceFilters(final SlingHttpServletRequest request, Page page) { + public Pair, List> retrieveCurrentlyAvailableCommerceFiltersInfo( + final SlingHttpServletRequest request, Page page) { // This is used to configure the cache in the GraphqlClient with a cache name of // --> com.adobe.cq.commerce.core.search.services.SearchFilterService - return Optional.ofNullable(page.adaptTo(Resource.class)) - .map(r -> new SyntheticResource(r.getResourceResolver(), r.getPath(), SearchFilterService.class.getName())) - .map(r -> new SlingHttpServletRequestWrapper(request) { + Resource r = page.adaptTo(Resource.class); + if (r != null) { + SyntheticResource syntheticResource = new SyntheticResource(r.getResourceResolver(), r.getPath(), SearchFilterService.class + .getName()); + SlingHttpServletRequestWrapper slingHttpServletRequestWrapper = new SlingHttpServletRequestWrapper(request) { @Override public Resource getResource() { - return r; + return syntheticResource; } - }) - .map(r -> r.adaptTo(MagentoGraphqlClient.class)) - .map(this::retrieveCurrentlyAvailableCommerceFilters) - .orElseGet(Collections::emptyList); + }; + MagentoGraphqlClient magentoGraphqlClient = slingHttpServletRequestWrapper.adaptTo(MagentoGraphqlClient.class); + if (magentoGraphqlClient != null) { + Pair, List> filterAttributeMetadataInfo = retrieveCurrentlyAvailableCommerceFiltersInfo( + magentoGraphqlClient); + return filterAttributeMetadataInfo; + } + } + return Pair.of(Collections.emptyList(), Collections.emptyList()); } - private List retrieveCurrentlyAvailableCommerceFilters(MagentoGraphqlClient magentoGraphqlClient) { + private Pair, List> retrieveCurrentlyAvailableCommerceFiltersInfo( + MagentoGraphqlClient magentoGraphqlClient) { // First we query Magento for the required attribute and filter information - final List<__InputValue> availableFilters = fetchAvailableSearchFilters(magentoGraphqlClient); - final List attributes = fetchAttributeMetadata(magentoGraphqlClient, availableFilters); + final Pair, List> availableFiltersInfo = fetchAvailableSearchFilters(magentoGraphqlClient); + final Pair, List> attributesInfo = fetchAttributeMetadata(magentoGraphqlClient, availableFiltersInfo + .getLeft()); + final List errors = new ArrayList<>(); + if (availableFiltersInfo.getRight() != null) { + errors.addAll(availableFiltersInfo.getRight()); + } + if (attributesInfo.getRight() != null) { + errors.addAll(attributesInfo.getRight()); + } // Then we combine this data into a useful set of data usable by other systems - FilterAttributeMetadataConverter converter = new FilterAttributeMetadataConverter(attributes); - return availableFilters.stream().map(converter).collect(Collectors.toList()); + FilterAttributeMetadataConverter converter = new FilterAttributeMetadataConverter(attributesInfo.getLeft()); + return Pair.of(availableFiltersInfo.getLeft().stream().map(converter).collect(Collectors.toList()), errors); } - private List fetchAttributeMetadata(final MagentoGraphqlClient magentoGraphqlClient, + private Pair, List> fetchAttributeMetadata(final MagentoGraphqlClient magentoGraphqlClient, final List<__InputValue> availableFilters) { if (magentoGraphqlClient == null) { LOGGER.error("MagentoGraphQL client is null, unable to make query to fetch attribute metadata."); - return Collections.emptyList(); + return Pair.of(Collections.emptyList(), Collections.emptyList()); } List attributeInputs = availableFilters.stream().map(inputField -> { @@ -144,11 +163,12 @@ private List fetchAttributeMetadata(final MagentoGraphqlClient magent if (CollectionUtils.isNotEmpty(response.getErrors())) { response.getErrors() .forEach(err -> LOGGER.error("An error has occurred: {} ({})", err.getMessage(), err.getCategory())); - return Collections.emptyList(); + return Pair.of(Collections.emptyList(), response.getErrors()); } CustomAttributeMetadata cam = response.getData().getCustomAttributeMetadata(); - return cam != null ? response.getData().getCustomAttributeMetadata().getItems() : Collections.emptyList(); + return Pair.of(cam != null ? response.getData().getCustomAttributeMetadata().getItems() : Collections.emptyList(), + Collections.emptyList()); } /** @@ -157,11 +177,11 @@ private List fetchAttributeMetadata(final MagentoGraphqlClient magent * @param magentoGraphqlClient client for making Magento GraphQL requests * @return key value pair of the attribute code or identifier and filter type for that attribute */ - private List<__InputValue> fetchAvailableSearchFilters(final MagentoGraphqlClient magentoGraphqlClient) { + private Pair, List> fetchAvailableSearchFilters(final MagentoGraphqlClient magentoGraphqlClient) { if (magentoGraphqlClient == null) { LOGGER.error("MagentoGraphQL client is null, unable to make introspection call to fetch available filter attributes."); - return Collections.emptyList(); + return Pair.of(Collections.emptyList(), Collections.emptyList()); } __TypeQueryDefinition typeQuery = q -> q @@ -179,10 +199,10 @@ private List<__InputValue> fetchAvailableSearchFilters(final MagentoGraphqlClien if (CollectionUtils.isNotEmpty(response.getErrors())) { response.getErrors() .forEach(err -> LOGGER.error("An error has occurred: {} ({})", err.getMessage(), err.getCategory())); - return Collections.emptyList(); + return Pair.of(Collections.emptyList(), response.getErrors()); } __Type type = response.getData().__getType(); - return type != null ? type.getInputFields() : Collections.emptyList(); + return Pair.of(type != null ? type.getInputFields() : Collections.emptyList(), Collections.emptyList()); } } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java index 23ab8627f6..bbfb6d74ce 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImpl.java @@ -205,10 +205,16 @@ public Pair errors = new ArrayList<>(); // We will use the search filter service to retrieve all of the potential available filters the commerce system // has available for querying against - List availableFilters = searchFilterService.retrieveCurrentlyAvailableCommerceFilters(request, page); + Pair, List> filterAttributeInfo = searchFilterService + .retrieveCurrentlyAvailableCommerceFiltersInfo(request, page); + List availableFilters = filterAttributeInfo.getLeft(); SorterKey currentSorterKey = findSortKey(mutableSearchOptions); + if (filterAttributeInfo.getRight() != null) { + errors.addAll(filterAttributeInfo.getRight()); + } String productsQueryString = generateProductsQueryString(mutableSearchOptions, availableFilters, productQueryHook, productAttributeFilterHook, @@ -221,6 +227,7 @@ public Pair LOGGER.error("An error has occurred: {} ({})", err.getMessage(), err.getCategory())); @@ -240,6 +247,7 @@ public Pair(category, searchResultsSet); } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchResultsSet.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchResultsSet.java index 234bcf391a..3c341d24ca 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchResultsSet.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/SearchResultsSet.java @@ -15,6 +15,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ package com.adobe.cq.commerce.core.search.models; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -23,6 +24,7 @@ import org.osgi.annotation.versioning.ConsumerType; import com.adobe.cq.commerce.core.components.models.common.ProductListItem; +import com.adobe.cq.commerce.magento.graphql.gson.Error; /** * Represents a set of search results from a backend service. This would generally contain the actual {@link ProductListItem}s @@ -113,4 +115,22 @@ public interface SearchResultsSet { * @return {@code true} if the result set provides support for sorting of the results, {@code false} otherwise */ boolean hasSorting(); + + /** + * Get the list of errors that occurred during the search. + * + * @return the list of errors + */ + default List getErrors() { + return Collections.emptyList(); + } + + /** + * Check if there were errors during the search. + * + * @return {@code true} if the result set contains errors, {@code false} otherwise + */ + default boolean hasErrors() { + return getErrors() != null && !getErrors().isEmpty(); + } } diff --git a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/package-info.java b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/package-info.java index 190557ca78..9d53334100 100644 --- a/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/package-info.java +++ b/bundles/core/src/main/java/com/adobe/cq/commerce/core/search/models/package-info.java @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ -@Version("3.2.0") +@Version("3.3.0") package com.adobe.cq.commerce.core.search.models; import org.osgi.annotation.versioning.Version; \ No newline at end of file diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImplTest.java index 4461d79025..fede7949dc 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/internal/client/MagentoGraphqlClientImplTest.java @@ -383,22 +383,26 @@ public void testErrorResponses() { when(pageResource.adaptTo(ComponentsConfiguration.class)).thenReturn(MOCK_CONFIGURATION_OBJECT); MagentoGraphqlClient client = new MagentoGraphqlClientImpl(pageResource, page, context.request()); - - doThrow(new RuntimeException("foobar")).when(graphqlClient).execute(any(), any(), any()); - doThrow(new RuntimeException("foobar")).when(graphqlClient).execute(any(), any(), any(), any()); + RuntimeException foobar = new RuntimeException("foobar"); + doThrow(foobar).when(graphqlClient).execute(any(), any(), any(), any()); GraphqlResponse response = client.execute("query"); assertNull(response.getData()); assertNotNull(response.getErrors()); assertEquals(1, response.getErrors().size()); - assertEquals("foobar", response.getErrors().get(0).getMessage()); + assertEquals("[java.lang.RuntimeException: \"foobar\"]", response.getErrors().get(0).getMessage()); assertEquals(MagentoGraphqlClient.RUNTIME_ERROR_CATEGORY, response.getErrors().get(0).getCategory()); + RuntimeException bar = new RuntimeException("bar"); + RuntimeException foo = new RuntimeException("foo", bar); + doThrow(foo).when(graphqlClient).execute(any(), any(), any(), any()); + response = client.execute("query", HttpMethod.POST); assertNull(response.getData()); assertNotNull(response.getErrors()); assertEquals(1, response.getErrors().size()); - assertEquals("foobar", response.getErrors().get(0).getMessage()); + assertEquals("[java.lang.RuntimeException: \"foo\"] Caused by: [java.lang.RuntimeException: \"bar\"]", + response.getErrors().get(0).getMessage()); assertEquals(MagentoGraphqlClient.RUNTIME_ERROR_CATEGORY, response.getErrors().get(0).getCategory()); } diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractRetrieverTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractRetrieverTest.java new file mode 100644 index 0000000000..398f4440ee --- /dev/null +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/components/models/retriever/AbstractRetrieverTest.java @@ -0,0 +1,113 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2024 Adobe + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package com.adobe.cq.commerce.core.components.models.retriever; + +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.adobe.cq.commerce.core.components.client.MagentoGraphqlClient; +import com.adobe.cq.commerce.graphql.client.GraphqlResponse; +import com.adobe.cq.commerce.magento.graphql.Query; +import com.adobe.cq.commerce.magento.graphql.gson.Error; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.when; + +public class AbstractRetrieverTest { + + private TestRetriever subject; + + @Mock + private MagentoGraphqlClient client; + @Mock + private GraphqlResponse response; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + subject = new TestRetriever(client); + } + + @Test + public void testSetQuery() { + String testQuery = "test query"; + subject.setQuery(testQuery); + assertEquals(testQuery, subject.getQuery()); + } + + @Test + public void testGetErrorsEmpty() { + when(client.execute(any())).thenReturn(response); + when(response.getErrors()).thenReturn(null); + subject.populate(); + assertTrue(subject.getErrors().isEmpty()); + assertFalse(subject.hasErrors()); + } + + @Test + public void testGetErrors() { + when(client.execute(any())).thenReturn(response); + when(response.getErrors()).thenReturn(Collections.singletonList(new Error())); + subject.populate(); + assertEquals(1, subject.getErrors().size()); + assertTrue(subject.hasErrors()); + } + + @Test(expected = IllegalStateException.class) + public void testGetErrorsWithoutPopulate() { + subject.getErrors(); + } + + @Test + public void testCreateWithNullClient() { + try { + new TestRetriever(null); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("No GraphQL client provided", e.getMessage()); + } + } + + private static class TestRetriever extends AbstractRetriever { + + public TestRetriever(MagentoGraphqlClient client) { + super(client); + } + + @Override + protected void populate() { + GraphqlResponse response = executeQuery(); + errors = response.getErrors(); + } + + @Override + protected GraphqlResponse executeQuery() { + return client.execute(query); + } + + public String getQuery() { + return query; + } + } +} diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImplTest.java index e72aabe8e0..0691419d49 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/models/SearchResultsSetImplTest.java @@ -28,6 +28,7 @@ import com.adobe.cq.commerce.core.search.models.Pager; import com.adobe.cq.commerce.core.search.models.SearchAggregation; +import com.adobe.cq.commerce.magento.graphql.gson.Error; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -110,4 +111,14 @@ public void testSearchResultsSetPager() { public void testSearchResultsSetHasAggregations() { Assert.assertTrue(modelUnderTest.hasAggregations()); } + + @Test + public void testGetErrors() { + Assert.assertTrue(modelUnderTest.getErrors().isEmpty()); + Assert.assertFalse(modelUnderTest.hasErrors()); + + modelUnderTest.setErrors(Arrays.asList(new Error(), new Error())); + Assert.assertEquals(2, modelUnderTest.getErrors().size()); + Assert.assertTrue(modelUnderTest.hasErrors()); + } } diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/services/SearchFilterServiceImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/services/SearchFilterServiceImplTest.java index e68c30b92f..ac26e2059b 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/services/SearchFilterServiceImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/services/SearchFilterServiceImplTest.java @@ -19,9 +19,11 @@ import java.util.Collections; import java.util.List; +import org.apache.commons.lang3.tuple.Pair; import org.apache.http.HttpStatus; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.osgi.services.HttpClientBuilderFactory; +import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; import org.apache.sling.api.wrappers.ValueMapDecorator; @@ -51,6 +53,7 @@ import static com.adobe.cq.commerce.core.testing.TestContext.buildAemContext; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.mock; @@ -193,4 +196,25 @@ public void testGraphqlResponsesWithErrors() { .retrieveCurrentlyAvailableCommerceFilters(page); assertThat(filterAttributeMetadata).hasSize(0); } + + @Test + public void testRetrieveCurrentlyAvailableCommerceFiltersInfoWithErrors() { + GraphqlClient graphqlClient = Mockito.mock(GraphqlClient.class); + context.registerAdapter(Resource.class, GraphqlClient.class, (Function) input -> input.getValueMap().get( + "cq:graphqlClient") != null ? graphqlClient : null); + + Query query = new Query(); + GraphqlResponse response = new GraphqlResponse(); + response.setData(query); + Error error = new Error(); + response.setErrors(Collections.singletonList(error)); + when(graphqlClient.execute(any(), any(), any(), any())).thenReturn(response); + + SlingHttpServletRequest request = context.request(); + + final Pair, List> filterAttributeMetadata = searchFilterServiceUnderTest + .retrieveCurrentlyAvailableCommerceFiltersInfo(request, page); + assertThat(filterAttributeMetadata.getLeft()).hasSize(0); + assertThat(filterAttributeMetadata.getRight()).hasSize(2); + } } diff --git a/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImplTest.java b/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImplTest.java index 342eaeee32..b9d09690d3 100644 --- a/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImplTest.java +++ b/bundles/core/src/test/java/com/adobe/cq/commerce/core/search/internal/services/SearchResultsServiceImplTest.java @@ -155,7 +155,7 @@ public void setup() { new UrlRewrite().setUrl("just-another/category/product"))); productHits.add(product); - when(searchFilterService.retrieveCurrentlyAvailableCommerceFilters(any(), any())).thenReturn(Arrays.asList( + when(searchFilterService.retrieveCurrentlyAvailableCommerceFiltersInfo(any(), any())).thenReturn(Pair.of(Arrays.asList( createMatchFilterAttributeMetadata(FILTER_ATTRIBUTE_NAME_CODE), createStringEqualFilterAttributeMetadata(FILTER_ATTRIBUTE_COLOR_CODE), createRangeFilterAttributeMetadata(FILTER_ATTRIBUTE_PRICE1_CODE), @@ -164,7 +164,7 @@ public void setup() { createRangeFilterAttributeMetadata(FILTER_ATTRIBUTE_PRICE4_CODE), createRangeFilterAttributeMetadata(FILTER_ATTRIBUTE_PRICE5_CODE), createBooleanEqualFilterAttributeMetadata(FILTER_ATTRIBUTE_BOOLEAN_CODE), - createUnknownAttributeMetadata())); + createUnknownAttributeMetadata()), Collections.emptyList())); when(products.getTotalCount()).thenReturn(0); when(products.getItems()).thenReturn(productHits); @@ -259,7 +259,7 @@ public void testPerformSearch() { ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(magentoGraphqlClient, times(1)).execute(captor.capture()); - verify(searchFilterService, times(1)).retrieveCurrentlyAvailableCommerceFilters(any(), any()); + verify(searchFilterService, times(1)).retrieveCurrentlyAvailableCommerceFiltersInfo(any(), any()); assertThat(searchResultsSet).isNotNull(); assertThat(searchResultsSet.getTotalResults()).isEqualTo(0); assertThat(searchResultsSet.getAppliedQueryParameters()).containsKeys("search_query");