diff --git a/.circleci/config.yml b/.circleci/config.yml
index c0a75fcc..8c94d6e0 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,14 +2,14 @@ version: 2.1
orbs:
codecov: codecov/codecov@1.1.1
- browser-tools: circleci/browser-tools@1.2.4
+ browser-tools: circleci/browser-tools@1.4.4
common:
integration_test_steps: &integration_test_steps
steps:
- checkout
- browser-tools/install-browser-tools:
- chrome-version: 114.0.5735.90 # TODO: remove -> https://github.com/CircleCI-Public/browser-tools-orb/issues/75
+ chrome-version: 116.0.5845.96 # TODO: remove -> https://github.com/CircleCI-Public/browser-tools-orb/issues/75
- restore_cache:
keys:
- maven-repo-{{ .Environment.CACHE_VERSION }}-its-{{ arch }}-{{ .Branch }}-{{ checksum "pom.xml" }}
diff --git a/classic/dispatcher/src/conf.d/available_vhosts/venia_publish.vhost b/classic/dispatcher/src/conf.d/available_vhosts/venia_publish.vhost
index 7da338dd..5e278053 100644
--- a/classic/dispatcher/src/conf.d/available_vhosts/venia_publish.vhost
+++ b/classic/dispatcher/src/conf.d/available_vhosts/venia_publish.vhost
@@ -64,4 +64,5 @@ PassEnv DISP_ID
ErrorDocument 404 ${404_PAGE}
-
\ No newline at end of file
+ ErrorDocument 503 ${503_PAGE}
+
diff --git a/classic/dispatcher/src/conf.d/variables/ams_default.vars b/classic/dispatcher/src/conf.d/variables/ams_default.vars
index f2548c39..2041fc9a 100644
--- a/classic/dispatcher/src/conf.d/variables/ams_default.vars
+++ b/classic/dispatcher/src/conf.d/variables/ams_default.vars
@@ -22,6 +22,7 @@ Define ASSET_DOWNLOAD_RULE deny
# Customer specific values
Define CONTENT_FOLDER_NAME venia
Define 404_PAGE /content/venia/us/en/errors/404.html
+Define 503_PAGE /content/venia/us/en/errors/503.html
#Enable/Disable 3DMime type. Enabling default by setting to 1
Define 3D_MIMETYPE_ENABLED 1
diff --git a/core/pom.xml b/core/pom.xml
index 0e8e209e..70543cd3 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -135,6 +135,34 @@ Import-Package: javax.annotation;version=0.0.0,*
+
+ javax.servlet
+ javax.servlet-api
+ 3.1.0
+ provided
+
+
+ javax.servlet
+ jsp-api
+ 2.0
+ provided
+
+
+ javax.jcr
+ jcr
+ 2.0
+ provided
+
+
+ org.osgi
+ osgi.cmpn
+ 6.0.0
+ provided
+
+
+ org.slf4j
+ slf4j-simple
+ javax.annotationjavax.annotation-api
@@ -163,6 +191,12 @@ Import-Package: javax.annotation;version=0.0.0,*
magento-graphqlprovided
+
+ org.osgi
+ osgi.core
+ 6.0.0
+ provided
+ com.adobe.cqcore.wcm.components.core
@@ -172,7 +206,7 @@ Import-Package: javax.annotation;version=0.0.0,*
org.apache.slingorg.apache.sling.api
- provided
+ testorg.apache.sling
@@ -197,6 +231,14 @@ Import-Package: javax.annotation;version=0.0.0,*
org.junit.jupiterjunit-jupiter
+ 5.10.2
+ test
+
+
+
+ org.mockito
+ mockito-core
+ 5.11.0test
@@ -213,13 +255,32 @@ Import-Package: javax.annotation;version=0.0.0,*
com.adobe.aemuber-jar
- 6.4.4
- apis
- test
+ ${uber.jar.version}
+ provided
+
+
+ org.slf4j
+ slf4j-simple
+
+ io.wcmio.wcm.testing.aem-mock.junit5
+ 5.5.0
+ test
+
+
+
+ org.apache.sling
+ org.apache.sling.testing.sling-mock.junit5
+ 3.5.0
+ test
+
+
+ junit
+ junit
+ 4.13.2test
diff --git a/core/src/main/java/com/venia/core/models/commerce/MySearchResultsImpl.java b/core/src/main/java/com/venia/core/models/commerce/MySearchResultsImpl.java
index 85f87f7e..ebb1ca65 100644
--- a/core/src/main/java/com/venia/core/models/commerce/MySearchResultsImpl.java
+++ b/core/src/main/java/com/venia/core/models/commerce/MySearchResultsImpl.java
@@ -121,6 +121,10 @@ public String getId() {
@Override
public ComponentData getData() {
final ComponentData data = ((Component) searchResults).getData();
+ if (data == null) {
+ return null;
+ }
+
final AtomicReference dataRef = new AtomicReference<>();
ComponentData componentData = (ComponentData) Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[]{ComponentData.class}, (proxy, method, args) -> {
diff --git a/core/src/main/java/com/venia/core/models/commerce/services/CommerceComponentModelFinder.java b/core/src/main/java/com/venia/core/models/commerce/services/CommerceComponentModelFinder.java
new file mode 100644
index 00000000..20626241
--- /dev/null
+++ b/core/src/main/java/com/venia/core/models/commerce/services/CommerceComponentModelFinder.java
@@ -0,0 +1,96 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Copyright 2021 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.venia.core.models.commerce.services;
+
+import com.adobe.cq.commerce.core.components.models.product.Product;
+import com.adobe.cq.commerce.core.components.models.productlist.ProductList;
+import com.drew.lang.annotations.Nullable;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.factory.ModelFactory;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * This service allows to traverse a {@link Resource} tree looking for a {@link Resource} of a set of particular resource types and if
+ * found adapting them to given adapter type. This helps for example finding the product component on the page and return the Product model
+ * from it.
+ */
+@Component(
+ service = com.venia.core.models.commerce.services.CommerceComponentModelFinder.class)
+public class CommerceComponentModelFinder {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(com.venia.core.models.commerce.services.CommerceComponentModelFinder.class);
+ private static final Collection PRODUCT_RTS = Collections.singleton("core/cif/components/commerce/product/v1/product");
+ private static final Collection PRODUCT_LIST_RTS = Arrays.asList(
+ "core/cif/components/commerce/productlist/v2/productlist",
+ "core/cif/components/commerce/productlist/v1/productlist");
+
+ @Reference
+ private ModelFactory modelFactory;
+
+ @Nullable
+ public Product findProductComponentModel(SlingHttpServletRequest request, Resource root) {
+ return findComponentModel(request, root, PRODUCT_RTS, Product.class);
+ }
+
+ @Nullable
+ public ProductList findProductListComponentModel(SlingHttpServletRequest request, Resource root) {
+ return findComponentModel(request, root, PRODUCT_LIST_RTS, ProductList.class);
+ }
+
+ @Nullable
+ public T findComponentModel(SlingHttpServletRequest request, Resource root, Collection resourceTypes,
+ Class adapterType) {
+ Resource componentResource = findChildResourceWithType(root, resourceTypes);
+ if (componentResource != null) {
+ return modelFactory.getModelFromWrappedRequest(request, componentResource, adapterType);
+ } else {
+ return null;
+ }
+ }
+
+ private Resource findChildResourceWithType(Resource fromResource, Collection resourceTypes) {
+ if (fromResource == null) {
+ return null;
+ }
+
+ LOGGER.debug("Looking for child resource type '{}' from {}", resourceTypes, fromResource.getPath());
+
+ for (Resource child : fromResource.getChildren()) {
+ for (String resourceType : resourceTypes) {
+ if (child.isResourceType(resourceType)) {
+ LOGGER.debug("Found child resource type '{}' at {}", resourceType, child.getPath());
+ return child;
+ }
+ }
+
+ Resource resource = findChildResourceWithType(child, resourceTypes);
+ if (resource != null) {
+ return resource;
+ }
+ }
+
+ return null;
+ }
+}
+
diff --git a/core/src/main/java/com/venia/core/models/commerce/servlets/CatalogPageErrorFilter.java b/core/src/main/java/com/venia/core/models/commerce/servlets/CatalogPageErrorFilter.java
new file mode 100644
index 00000000..9a9cc410
--- /dev/null
+++ b/core/src/main/java/com/venia/core/models/commerce/servlets/CatalogPageErrorFilter.java
@@ -0,0 +1,144 @@
+package com.venia.core.models.commerce.servlets;
+
+import com.adobe.cq.commerce.core.components.models.common.SiteStructure;
+import com.adobe.cq.commerce.core.components.models.product.Product;
+import com.adobe.cq.commerce.core.components.models.productlist.ProductList;
+import com.adobe.cq.commerce.core.components.models.retriever.AbstractCategoryRetriever;
+import com.adobe.cq.commerce.core.components.models.retriever.AbstractProductRetriever;
+import com.day.cq.wcm.api.Page;
+import com.day.cq.wcm.api.PageManager;
+import com.day.cq.wcm.api.PageManagerFactory;
+import com.venia.core.models.commerce.services.CommerceComponentModelFinder;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.scripting.core.ScriptHelper;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component(
+ service = {Filter.class},
+ property = {
+ "sling.filter.scope=REQUEST",
+ "sling.filter.resourceTypes=cq:Page",
+ "sling.filter.resourceTypes=core/cif/components/structure/page/v1/page",
+ "sling.filter.resourceTypes=core/cif/components/structure/page/v2/page",
+ "sling.filter.resourceTypes=core/cif/components/structure/page/v3/page",
+ "sling.filter.extensions=html",
+ "sling.filter.extensions=json",
+ "sling.filter.resource.pattern=/content(/.+)?",
+ "service.ranking:Integer=-4000"
+ })
+public class CatalogPageErrorFilter implements Filter {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CatalogPageErrorFilter.class);
+
+ @Reference
+ private PageManagerFactory pageManagerFactory;
+
+ @Reference
+ private CommerceComponentModelFinder commerceModelFinder;
+
+ public CatalogPageErrorFilter() {
+ }
+
+ private BundleContext bundleContext;
+
+ @Activate
+ protected void activate(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ if (servletRequest instanceof SlingHttpServletRequest && servletResponse instanceof SlingHttpServletResponse) {
+ SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) servletRequest;
+ SlingHttpServletResponse slingResponse = (SlingHttpServletResponse) servletResponse;
+ PageManager pageManager = pageManagerFactory.getPageManager(slingRequest.getResourceResolver());
+ Page currentPage = pageManager.getContainingPage(slingRequest.getResource());
+ boolean removeSlingScriptHelperFromBindings = false;
+ if (currentPage != null) {
+ // Get the SiteStructure model
+ SiteStructure siteStructure = slingRequest.adaptTo(SiteStructure.class);
+ if (siteStructure.isProductPage(currentPage)) {
+ // add the SlingScriptHelper to the bindings if it is not there yet
+ removeSlingScriptHelperFromBindings = addSlingScriptHelperIfNeeded(slingRequest, slingResponse);
+ Product product = commerceModelFinder.findProductComponentModel(slingRequest, currentPage.getContentResource());
+ if (product != null) {
+ AbstractProductRetriever productRetriever = product.getProductRetriever();
+ // force GraphQL query execution
+ if (productRetriever != null && !product.getFound() && productRetriever.hasErrors()) {
+ slingResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Commerce application not reachable");
+ return;
+ }
+ }
+ } else if (siteStructure.isCategoryPage(currentPage)) {
+ // add the SlingScriptHelper to the bindings if it is not there yet
+ removeSlingScriptHelperFromBindings = addSlingScriptHelperIfNeeded(slingRequest, slingResponse);
+ ProductList productList = commerceModelFinder.findProductListComponentModel(slingRequest, currentPage.getContentResource());
+ if (productList != null) {
+ // Get the AbstractCategoryRetriever model
+ AbstractCategoryRetriever categoryRetriever = productList.getCategoryRetriever();
+ if ((categoryRetriever != null &&
+ // force GraphQL query execution for category
+ categoryRetriever.fetchCategory() == null && categoryRetriever.hasErrors()) ||
+ // force GraphQL query execution for products
+ productList.getSearchResultsSet().hasErrors()) {
+ slingResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Commerce application not reachable");
+ return;
+ }
+ }
+ }
+ if (removeSlingScriptHelperFromBindings) {
+ // remove the ScriptHelper if we added it before
+ SlingBindings slingBindings = getSlingBindings(slingRequest);
+ if (slingBindings != null) {
+ slingBindings.remove("sling");
+ }
+ }
+ }
+ }
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+ public void destroy() {
+ }
+
+ /**
+ * The {@link com.venia.core.models.commerce.services.CommerceComponentModelFinder} uses
+ * {@link org.apache.sling.models.factory.ModelFactory#getModelFromWrappedRequest(SlingHttpServletRequest, Resource, Class)}
+ * to obtain the model of either {@link Product} or {@link ProductList}. That method invokes all
+ * {@link org.apache.sling.scripting.api.BindingsValuesProvider}
+ * while creating the wrapped request. In AEM 6.5 they are not executed lazily and depend on some existing bindings on construction of
+ * which one requires the SlingScriptHelper.
+ *
+ * @param slingRequest
+ */
+ private boolean addSlingScriptHelperIfNeeded(SlingHttpServletRequest slingRequest, SlingHttpServletResponse slingResponse) {
+ SlingBindings slingBindings = getSlingBindings(slingRequest);
+ if (slingBindings != null && slingBindings.getSling() == null) {
+ slingBindings.put("sling", new ScriptHelper(bundleContext, null, slingRequest, slingResponse));
+ return true;
+ }
+ return false;
+ }
+
+ private static SlingBindings getSlingBindings(SlingHttpServletRequest slingRequest) {
+ Object attr = slingRequest.getAttribute(SlingBindings.class.getName());
+ if (attr == null) {
+ attr = new SlingBindings();
+ slingRequest.setAttribute(SlingBindings.class.getName(), attr);
+ }
+ return attr instanceof SlingBindings ? (SlingBindings) attr : null;
+ }
+}
diff --git a/core/src/test/java/com/venia/core/models/commerce/MyProductTeaserImplTest.java b/core/src/test/java/com/venia/core/models/commerce/MyProductTeaserImplTest.java
index 7a42319b..54d6626f 100644
--- a/core/src/test/java/com/venia/core/models/commerce/MyProductTeaserImplTest.java
+++ b/core/src/test/java/com/venia/core/models/commerce/MyProductTeaserImplTest.java
@@ -41,8 +41,7 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import org.mockito.internal.util.reflection.FieldReader;
-import org.mockito.internal.util.reflection.FieldSetter;
+import org.apache.commons.lang3.reflect.FieldUtils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -120,10 +119,14 @@ void setup(String resourceName) throws Exception {
underTest = context.request().adaptTo(MyProductTeaser.class);
Assertions.assertNotNull(underTest);
- Class extends MyProductTeaser> clazz = underTest.getClass();
- productTeaser = Mockito.spy((ProductTeaser)(new FieldReader(underTest, clazz.getDeclaredField("productTeaser")).read()));
- FieldSetter.setField(underTest, clazz.getDeclaredField("productTeaser"), productTeaser);
- FieldSetter.setField(underTest, clazz.getDeclaredField("productRetriever"), productRetriever);
+ // Use FieldUtils to get the private fields (if needed)
+ productTeaser = Mockito.spy((ProductTeaser) FieldUtils.readField(
+ underTest,
+ "productTeaser",
+ true
+ ));
+ FieldUtils.writeField(underTest, "productTeaser", productTeaser, true);
+ FieldUtils.writeField(underTest, "productRetriever", productRetriever, true);
}
@ParameterizedTest
@@ -282,4 +285,10 @@ public void testDataLayerFeature() throws Exception {
Assertions.assertEquals(dataLayerJson, underTest.getData().getJson());
}
+
+ @Test
+ void testGetProductRetriever() throws Exception {
+ setup(PRODUCTTEASER_NO_BADGE);
+ Assertions.assertNotNull(underTest.getProductRetriever());
+ }
}
diff --git a/core/src/test/java/com/venia/core/models/commerce/MySearchResultsImplTest.java b/core/src/test/java/com/venia/core/models/commerce/MySearchResultsImplTest.java
index 91c755ab..6a15f793 100644
--- a/core/src/test/java/com/venia/core/models/commerce/MySearchResultsImplTest.java
+++ b/core/src/test/java/com/venia/core/models/commerce/MySearchResultsImplTest.java
@@ -13,16 +13,20 @@
******************************************************************************/
package com.venia.core.models.commerce;
-import com.adobe.cq.commerce.core.components.models.searchresults.SearchResults;
import com.adobe.cq.commerce.core.components.services.urls.UrlProvider;
import com.adobe.cq.commerce.core.search.models.SearchResultsSet;
import com.adobe.cq.commerce.core.search.models.Sorter;
import com.adobe.cq.commerce.core.search.models.SorterKey;
import com.adobe.cq.commerce.core.search.services.SearchResultsService;
+import com.adobe.cq.commerce.magento.graphql.FilterMatchTypeInput;
import com.adobe.cq.commerce.magento.graphql.ProductAttributeFilterInput;
+import com.adobe.cq.wcm.core.components.models.datalayer.ComponentData;
import com.day.cq.wcm.api.Page;
import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.caconfig.ConfigurationBuilder;
import org.apache.sling.testing.mock.sling.ResourceResolverType;
import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
import org.junit.jupiter.api.Assertions;
@@ -38,8 +42,7 @@
import java.util.Map;
import java.util.function.Function;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -49,7 +52,8 @@ public class MySearchResultsImplTest {
private static final String PAGE = "/content/page";
private final AemContext context = new AemContext(ResourceResolverType.JCR_MOCK);
- private SearchResults underTest;
+ private MySearchResultsImpl underTest;
+ private HashMap dataLayerConfigMap;
@BeforeEach
void beforeEach() {
@@ -79,6 +83,12 @@ void beforeEach() {
context.registerService(UrlProvider.class, mock(UrlProvider.class));
context.addModelsForClasses(MySearchResultsImpl.class);
+ ConfigurationBuilder configurationBuilder = mock(ConfigurationBuilder.class);
+ when(configurationBuilder.name("com.adobe.cq.wcm.core.components.internal.DataLayerConfig")).thenReturn(configurationBuilder);
+ dataLayerConfigMap = new HashMap<>();
+ when(configurationBuilder.asValueMap()).thenReturn(new ValueMapDecorator(dataLayerConfigMap));
+ context.registerAdapter(Resource.class, ConfigurationBuilder.class, configurationBuilder);
+
MockSlingHttpServletRequest request = context.request();
request.addRequestParameter("search_query", "test");
@@ -86,6 +96,36 @@ void beforeEach() {
Assertions.assertNotNull(underTest);
}
+ @Test
+ void testInstance() {
+ assertEquals("paginationbar", underTest.getPaginationType());
+ assertFalse(underTest.isAddToCartEnabled());
+ assertFalse(underTest.isAddToWishListEnabled());
+ assertTrue(underTest.loadClientPrice());
+ assertNotNull(underTest.getSearchResultsStorefrontContext());
+ assertNotNull(underTest.getSearchStorefrontContext());
+ underTest.getAppliedCssClasses();
+ assertEquals("venia/components/commerce/searchresults", underTest.getExportedType());
+ ComponentData componentData = underTest.getData();
+ assertNull(componentData);
+ assertEquals("searchresults-50df7e8869", underTest.getId());
+
+ underTest.extendProductQueryWith(p-> p.color());
+ underTest.extendProductFilterWith(f -> f.setName(new FilterMatchTypeInput().setMatch("winter")));
+ assertNotNull(underTest.getProducts());
+ }
+
+ @Test
+ void testComponentData() {
+ dataLayerConfigMap.put("enabled", true);
+
+ ComponentData componentData = underTest.getData();
+ assertNotNull(componentData);
+ assertEquals("venia/components/commerce/searchresults", componentData.getType());
+ assertEquals("searchresults-50df7e8869", componentData.getId());
+ }
+
+
@Test
void testSorterKeys() {
List keys = underTest.getSearchResultsSet().getSorter().getKeys();
diff --git a/core/src/test/java/com/venia/core/models/commerce/services/CommerceComponentModelFinderTest.java b/core/src/test/java/com/venia/core/models/commerce/services/CommerceComponentModelFinderTest.java
new file mode 100644
index 00000000..37e8b4ef
--- /dev/null
+++ b/core/src/test/java/com/venia/core/models/commerce/services/CommerceComponentModelFinderTest.java
@@ -0,0 +1,101 @@
+package com.venia.core.models.commerce.services;
+
+
+import com.adobe.cq.commerce.core.components.models.product.Product;
+import com.adobe.cq.commerce.core.components.models.productlist.ProductList;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.factory.ModelFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import java.util.Collections;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith({AemContextExtension.class, MockitoExtension.class})
+class CommerceComponentModelFinderTest {
+
+ @Mock
+ ModelFactory modelFactory;
+
+ @Mock
+ private Resource resource;
+
+ @Mock
+ private MockSlingHttpServletRequest request;
+
+
+ private final AemContext context = new AemContext();
+
+ @InjectMocks
+ private CommerceComponentModelFinder finder;
+
+ @BeforeEach
+ void setUp() {;
+ }
+
+ @Test
+ void findProductComponentModelWithResourceReturnsNullWhenNoProductComponent() {
+ Product product = finder.findProductComponentModel(request, resource);
+ assertNull(product);
+ }
+
+ @Test
+ void findProductComponentModelWithResourceReturnsProductWhenProductComponentExists() {
+ Resource mockChildResource = mock(Resource.class);
+ Product mockProduct = mock(Product.class);
+ when(resource.getChildren()).thenReturn(Collections.singletonList(mockChildResource));
+ when(mockChildResource.isResourceType(anyString())).thenReturn(true);
+ when(modelFactory.getModelFromWrappedRequest(any(), any(), eq(Product.class))).thenReturn(mockProduct);
+ Product product = finder.findProductComponentModel(request, resource);
+ assertNotNull(product);
+ assertEquals(mockProduct, product);
+ }
+
+ @Test
+ void findProductListComponentModelWithResourceReturnsNullWhenNoProductListComponent() {
+ ProductList productList = finder.findProductListComponentModel(request, resource);
+ assertNull(productList);
+ }
+
+ @Test
+ void findComponentModelWithResourceReturnsProductWhenProductAdapterType() {
+ Resource mockChildResource = mock(Resource.class);
+ Product mockProduct = mock(Product.class);
+
+ List mockResourceTypes = Arrays.asList("mockResourceType1", "mockResourceType2");
+
+ when(resource.getChildren()).thenReturn(Collections.singletonList(mockChildResource));
+ when(mockChildResource.isResourceType(anyString())).thenReturn(true);
+ when(modelFactory.getModelFromWrappedRequest(any(), any(), eq(Product.class))).thenReturn(mockProduct);
+
+ Product product = finder.findComponentModel(request, resource, mockResourceTypes, Product.class);
+ assertNotNull(product);
+ assertEquals(mockProduct, product);
+ }
+
+ @Test
+ void findComponentModelWithResourceReturnsProductListWhenProductListAdapterType() {
+ Resource mockChildResource = mock(Resource.class);
+ ProductList mockProductList = mock(ProductList.class);
+
+ List mockResourceTypes = Arrays.asList("mockResourceType1", "mockResourceType2");
+
+ when(resource.getChildren()).thenReturn(Collections.singletonList(mockChildResource));
+ when(mockChildResource.isResourceType(anyString())).thenReturn(true);
+ when(modelFactory.getModelFromWrappedRequest(any(), any(), eq(ProductList.class))).thenReturn(mockProductList);
+
+ ProductList productList = finder.findComponentModel(request, resource, mockResourceTypes, ProductList.class);
+ assertNotNull(productList);
+ assertEquals(mockProductList, productList);
+ }
+}
diff --git a/core/src/test/java/com/venia/core/models/commerce/servlets/CatalogPageErrorFilterTest.java b/core/src/test/java/com/venia/core/models/commerce/servlets/CatalogPageErrorFilterTest.java
new file mode 100644
index 00000000..e2db8c45
--- /dev/null
+++ b/core/src/test/java/com/venia/core/models/commerce/servlets/CatalogPageErrorFilterTest.java
@@ -0,0 +1,213 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Copyright 2021 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.venia.core.models.commerce.servlets;
+
+import com.adobe.cq.commerce.core.components.models.common.SiteStructure;
+import com.adobe.cq.commerce.core.components.models.product.Product;
+import com.adobe.cq.commerce.core.components.models.productlist.CategoryRetriever;
+import com.adobe.cq.commerce.core.components.models.productlist.ProductList;
+import com.adobe.cq.commerce.core.components.models.retriever.AbstractProductRetriever;
+import com.adobe.cq.commerce.core.search.models.SearchResultsSet;
+import com.day.cq.wcm.api.Page;
+import com.day.cq.wcm.api.PageManager;
+import com.day.cq.wcm.api.PageManagerFactory;
+import com.venia.core.models.commerce.services.CommerceComponentModelFinder;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.osgi.framework.BundleContext;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith({AemContextExtension.class, MockitoExtension.class})
+public class CatalogPageErrorFilterTest {
+
+ private final AemContext aemContext = new AemContext();
+
+ @Mock
+ private PageManagerFactory pageManagerFactory;
+
+ @Mock
+ private PageManager pageManager;
+
+ @Mock
+ private Page currentPage;
+
+ @Mock
+ private Resource resource;
+
+ @Mock
+ private CommerceComponentModelFinder commerceModelFinder;
+
+ @Mock
+ private BundleContext bundleContext;
+
+ @Mock
+ private MockSlingHttpServletRequest slingRequest;
+
+ @Mock
+ private MockSlingHttpServletResponse slingResponse;
+
+ @InjectMocks
+ private CatalogPageErrorFilter catalogPageErrorFilter;
+
+ private FilterChain filterChain;
+
+ @BeforeEach
+ void setUp() {
+ filterChain = mock(FilterChain.class);
+ when(pageManagerFactory.getPageManager(any())).thenReturn(pageManager);
+ when(pageManager.getContainingPage(slingRequest.getResource())).thenReturn(currentPage);
+ }
+
+ @Test
+ void doFilterWhenCurrentPageNull() throws IOException, ServletException {
+ when(pageManager.getContainingPage(slingRequest.getResource())).thenReturn(null);
+
+ catalogPageErrorFilter.doFilter(slingRequest, slingResponse, filterChain);
+ verify(filterChain).doFilter(slingRequest, slingResponse);
+
+ }
+
+ @Test
+ void doFilterWhenProductNull() throws IOException, ServletException {
+ ResourceResolver resourceResolver = mock(ResourceResolver.class);
+ SiteStructure siteStructure = mock(SiteStructure.class);
+
+ when(slingRequest.getResourceResolver()).thenReturn(resourceResolver);
+ when(slingRequest.adaptTo(SiteStructure.class)).thenReturn(siteStructure);
+ when(siteStructure.isProductPage(any())).thenReturn(true);
+ when(commerceModelFinder.findProductComponentModel(any(), any())).thenReturn(null);
+
+ catalogPageErrorFilter.doFilter(slingRequest, slingResponse, filterChain);
+
+ verify(filterChain).doFilter(slingRequest, slingResponse);
+ }
+
+ @Test
+ void doFilterWhenProductRetrieverNull() throws IOException, ServletException {
+ ResourceResolver resourceResolver = mock(ResourceResolver.class);
+ SiteStructure siteStructure = mock(SiteStructure.class);
+ Product product = mock(Product.class);
+
+
+ when(slingRequest.getResourceResolver()).thenReturn(resourceResolver);
+ when(slingRequest.adaptTo(SiteStructure.class)).thenReturn(siteStructure);
+ when(siteStructure.isProductPage(any())).thenReturn(true);
+ when(commerceModelFinder.findProductComponentModel(any(), any())).thenReturn(product);
+ when(product.getProductRetriever()).thenReturn(null);
+ catalogPageErrorFilter.doFilter(slingRequest, slingResponse, filterChain);
+ verify(filterChain).doFilter(slingRequest, slingResponse);
+ }
+
+ @Test
+ void doFilterWhenProductRetrieverHasErrors() throws IOException, ServletException {
+ ResourceResolver resourceResolver = mock(ResourceResolver.class);
+ SiteStructure siteStructure = mock(SiteStructure.class);
+ Product product = mock(Product.class);
+ AbstractProductRetriever productRetriever = mock(AbstractProductRetriever.class);
+
+ when(slingRequest.getResourceResolver()).thenReturn(resourceResolver);
+ when(slingRequest.adaptTo(SiteStructure.class)).thenReturn(siteStructure);
+ when(siteStructure.isProductPage(any())).thenReturn(true);
+ when(commerceModelFinder.findProductComponentModel(any(), any())).thenReturn(product);
+ when(product.getProductRetriever()).thenReturn(productRetriever);
+ when(productRetriever.hasErrors()).thenReturn(true);
+
+ catalogPageErrorFilter.doFilter(slingRequest, slingResponse, filterChain);
+ verify(slingResponse).sendError(
+ HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+ "Commerce application not reachable"
+ );
+ verify(filterChain, never()).doFilter(slingRequest, slingResponse);
+ }
+
+ @Test
+ void doFilterWithCategoryPageWhenProductListNull() throws IOException, ServletException {
+ ResourceResolver resourceResolver = mock(ResourceResolver.class);
+ SiteStructure siteStructure = mock(SiteStructure.class);
+
+ when(slingRequest.getResourceResolver()).thenReturn(resourceResolver);
+ when(slingRequest.adaptTo(SiteStructure.class)).thenReturn(siteStructure);
+ when(siteStructure.isCategoryPage(any())).thenReturn(true);
+ when(commerceModelFinder.findProductListComponentModel(any(), any())).thenReturn(null);
+ catalogPageErrorFilter.doFilter(slingRequest, slingResponse, filterChain);
+
+ verify(filterChain).doFilter(slingRequest, slingResponse);
+ }
+
+ @Test
+ void doFilterWithCategoryPageWhenCategoryRetrieverNullAndSearchResultErrorEmpty() throws IOException, ServletException {
+ PageManager pageManager = mock(PageManager.class);
+ Page currentPage = mock(Page.class);
+ ResourceResolver resourceResolver = mock(ResourceResolver.class);
+ SiteStructure siteStructure = mock(SiteStructure.class);
+ ProductList productList = mock(ProductList.class);
+ SearchResultsSet searchResultsSet = mock(SearchResultsSet.class);
+
+ when(slingRequest.getResourceResolver()).thenReturn(resourceResolver);
+ when(slingRequest.adaptTo(SiteStructure.class)).thenReturn(siteStructure);
+ when(siteStructure.isCategoryPage(any())).thenReturn(true);
+ when(commerceModelFinder.findProductListComponentModel(any(), any())).thenReturn(productList);
+ when(productList.getCategoryRetriever()).thenReturn(null);
+ when(productList.getSearchResultsSet()).thenReturn(searchResultsSet);
+ when(searchResultsSet.hasErrors()).thenReturn(false);
+ catalogPageErrorFilter.doFilter(slingRequest, slingResponse, filterChain);
+ verify(filterChain).doFilter(slingRequest, slingResponse);
+ }
+
+ @Test
+ void doFilterWithCategoryPageHasErrors() throws IOException, ServletException {
+ PageManager pageManager = mock(PageManager.class);
+ Page currentPage = mock(Page.class);
+ ResourceResolver resourceResolver = mock(ResourceResolver.class);
+ SiteStructure siteStructure = mock(SiteStructure.class);
+ ProductList productList = mock(ProductList.class);
+ CategoryRetriever categoryRetriever = mock(CategoryRetriever.class);
+
+ when(slingRequest.getResourceResolver()).thenReturn(resourceResolver);
+ when(slingRequest.adaptTo(SiteStructure.class)).thenReturn(siteStructure);
+ when(siteStructure.isCategoryPage(any())).thenReturn(true);
+ when(commerceModelFinder.findProductListComponentModel(any(), any())).thenReturn(productList);
+ when(productList.getCategoryRetriever()).thenReturn(categoryRetriever);
+ when(categoryRetriever.fetchCategory()).thenReturn(null);
+ when(categoryRetriever.hasErrors()).thenReturn(true);
+
+ catalogPageErrorFilter.doFilter(slingRequest, slingResponse, filterChain);
+
+ verify(slingResponse).sendError(
+ HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+ "Commerce application not reachable"
+ );
+ verify(filterChain, never()).doFilter(slingRequest, slingResponse);
+ }
+
+}
diff --git a/dispatcher/src/conf.d/available_vhosts/venia.vhost b/dispatcher/src/conf.d/available_vhosts/venia.vhost
index 99ac19d3..eb98f757 100644
--- a/dispatcher/src/conf.d/available_vhosts/venia.vhost
+++ b/dispatcher/src/conf.d/available_vhosts/venia.vhost
@@ -64,4 +64,5 @@ Include conf.d/variables/custom.vars
# Customizations made to the vhost compared to default
ErrorDocument 404 ${404_PAGE}
+ ErrorDocument 503 ${503_PAGE}
diff --git a/dispatcher/src/conf.d/variables/custom.vars b/dispatcher/src/conf.d/variables/custom.vars
index 743d81aa..ed9c72bb 100644
--- a/dispatcher/src/conf.d/variables/custom.vars
+++ b/dispatcher/src/conf.d/variables/custom.vars
@@ -5,3 +5,4 @@
#
Define CONTENT_FOLDER_NAME venia
Define 404_PAGE /content/venia/us/en/errors/404.html
+Define 503_PAGE /content/venia/us/en/errors/503.html
diff --git a/it.tests/src/main/resources/datalayer/grouped-product-65.json b/it.tests/src/main/resources/datalayer/grouped-product-65.json
index 2ecaa222..b044eedf 100644
--- a/it.tests/src/main/resources/datalayer/grouped-product-65.json
+++ b/it.tests/src/main/resources/datalayer/grouped-product-65.json
@@ -1,34 +1,33 @@
{
- "product-3c02043652": {
- "xdm:listPrice": 78.0,
- "xdm:SKU": "VA23",
- "xdm:assets": [
- {
-
- "repo:path": "https://mystage1-amspro120.amscommerce.cloud/media/catalog/product/cache/8735dd21982cf027014173d1affcf80c/v/a/va23_main.jpg",
- "repo:id": "image-3d3553c5dc",
- "@type": "image"
- }
- ],
- "xdm:categories": [
- {
- "repo:id": "category-8c01c593b9",
- "xdm:name": "Accessories",
- "xdm:asset": {
- "repo:path": "https://mystage1-amspro120.amscommerce.cloud/media/catalog/category/carefree.jpg",
- "repo:id": "image-6310f9e8a5",
- "@type": "image"
- }
- },
- {
- "repo:id": "category-f5a5c5aea0",
- "xdm:name": "Jewelry",
- "xdm:asset": null
- }
- ],
- "xdm:currencyCode": "USD",
- "dc:description": "This trio is designed for versatility. Brighten up a simple look or add another layer of “wow” to an already vibrant ensemble. Go as big and bold, or understated as you're in the mood to be.",
- "dc:title": "Augusta Trio",
- "@type": "venia/components/commerce/product"
- }
+ "product-3c02043652": {
+ "xdm:listPrice": 78.0,
+ "xdm:SKU": "VA23",
+ "xdm:assets": [
+ {
+ "repo:path": "https://mcprod.catalogservice4commerce.fun/media/catalog/product/cache/dbba4389ce8da2ab9399ca5f8b677aac/v/a/va23_main.jpg",
+ "repo:id": "image-daa709cd50",
+ "@type": "image"
+ }
+ ],
+ "xdm:categories": [
+ {
+ "repo:id": "category-4c0609820d",
+ "xdm:name": "Accessories",
+ "xdm:asset": {
+ "repo:path": "https://mcprod.catalogservice4commerce.fun/media/catalog/category/carefree.jpg",
+ "repo:id": "image-11a31da9a5",
+ "@type": "image"
+ }
+ },
+ {
+ "repo:id": "category-6e6f7c1718",
+ "xdm:name": "Jewelry",
+ "xdm:asset": null
+ }
+ ],
+ "xdm:currencyCode": "USD",
+ "dc:description": "This trio is designed for versatility. Brighten up a simple look or add another layer of “wow” to an already vibrant ensemble. Go as big and bold, or understated as you're in the mood to be.",
+ "dc:title": "Augusta Trio",
+ "@type": "venia/components/commerce/product"
+ }
}
\ No newline at end of file
diff --git a/it.tests/src/main/resources/datalayer/grouped-product.json b/it.tests/src/main/resources/datalayer/grouped-product.json
index 0c3827a6..17e4dd57 100644
--- a/it.tests/src/main/resources/datalayer/grouped-product.json
+++ b/it.tests/src/main/resources/datalayer/grouped-product.json
@@ -1,34 +1,33 @@
{
- "product-3c02043652": {
- "xdm:listPrice": 78.0,
- "xdm:SKU": "VA23",
- "xdm:assets": [
- {
-
- "repo:path": "https://mystage1-amspro120.amscommerce.cloud/media/catalog/product/cache/8735dd21982cf027014173d1affcf80c/v/a/va23_main.jpg",
- "repo:id": "image-3d3553c5dc",
- "@type": "image"
- }
- ],
- "xdm:categories": [
- {
- "repo:id": "category-8c01c593b9",
- "xdm:name": "Accessories",
- "xdm:asset": {
- "repo:path": "https://mystage1-amspro120.amscommerce.cloud/media/catalog/category/carefree.jpg",
- "repo:id": "image-6310f9e8a5",
- "@type": "image"
- }
- },
- {
- "repo:id": "category-f5a5c5aea0",
- "xdm:name": "Jewelry",
- "xdm:asset": null
- }
- ],
- "xdm:currencyCode": "USD",
- "dc:description": "This trio is designed for versatility. Brighten up a simple look or add another layer of “wow” to an already vibrant ensemble. Go as big and bold, or understated as you're in the mood to be.",
- "dc:title": "Augusta Trio",
- "@type": "venia/components/commerce/product"
- }
+ "product-3c02043652": {
+ "xdm:listPrice": 78.0,
+ "xdm:SKU": "VA23",
+ "xdm:assets": [
+ {
+ "repo:path": "https://mcprod.catalogservice4commerce.fun/media/catalog/product/cache/dbba4389ce8da2ab9399ca5f8b677aac/v/a/va23_main.jpg",
+ "repo:id": "image-daa709cd50",
+ "@type": "image"
+ }
+ ],
+ "xdm:categories": [
+ {
+ "repo:id": "category-4c0609820d",
+ "xdm:name": "Accessories",
+ "xdm:asset": {
+ "repo:path": "https://mcprod.catalogservice4commerce.fun/media/catalog/category/carefree.jpg",
+ "repo:id": "image-11a31da9a5",
+ "@type": "image"
+ }
+ },
+ {
+ "repo:id": "category-6e6f7c1718",
+ "xdm:name": "Jewelry",
+ "xdm:asset": null
+ }
+ ],
+ "xdm:currencyCode": "USD",
+ "dc:description": "This trio is designed for versatility. Brighten up a simple look or add another layer of “wow” to an already vibrant ensemble. Go as big and bold, or understated as you're in the mood to be.",
+ "dc:title": "Augusta Trio",
+ "@type": "venia/components/commerce/product"
+ }
}
\ No newline at end of file
diff --git a/it.tests/src/main/resources/datalayer/simple-product-65.json b/it.tests/src/main/resources/datalayer/simple-product-65.json
index f776e951..9244dad7 100644
--- a/it.tests/src/main/resources/datalayer/simple-product-65.json
+++ b/it.tests/src/main/resources/datalayer/simple-product-65.json
@@ -1,62 +1,58 @@
{
- "product-dc59ad50df": {
- "xdm:listPrice": 78.0,
- "xdm:SKU": "VP05",
- "xdm:assets": [
- {
- "repo:path": "https://mystage1-amspro120.amscommerce.cloud/media/catalog/product/cache/8735dd21982cf027014173d1affcf80c/v/p/vp05-rn_main_4.jpg",
- "repo:id": "image-73439b9cf3",
- "@type": "image"
- },
- {
- "repo:path": "https://mystage1-amspro120.amscommerce.cloud/media/catalog/product/cache/8735dd21982cf027014173d1affcf80c/v/p/vp05-rn_alt_2.jpg",
- "repo:id": "image-2e75bb2439",
- "@type": "image"
- },
- {
- "repo:path": "https://mystage1-amspro120.amscommerce.cloud/media/catalog/product/cache/8735dd21982cf027014173d1affcf80c/v/p/vp05-rn_back_2.jpg",
- "repo:id": "image-40bcf141f9",
- "@type": "image"
- },
- {
- "repo:path": "https://mystage1-amspro120.amscommerce.cloud/media/catalog/product/cache/8735dd21982cf027014173d1affcf80c/v/p/vp05_look_2.jpg",
- "repo:id": "image-e049cce860",
- "@type": "image"
- }
- ],
- "xdm:categories": [
- {
- "repo:id": "category-cf9d757f29",
- "xdm:name": "Bottoms",
- "xdm:asset": {
- "repo:path": "https://mystage1-amspro120.amscommerce.cloud/media/catalog/category/minimalist.jpg",
- "repo:id": "image-74c855fbf3",
- "@type": "image"
- }
- },
- {
- "repo:id": "category-6e60cf4797",
- "xdm:name": "Pants & Shorts",
- "xdm:asset": null
- },
- {
- "repo:id": "category-b8a3caa888",
- "xdm:name": "Shop The Look",
- "xdm:asset": {
- "repo:path": "https://mystage1-amspro120.amscommerce.cloud/media/catalog/category/beachy.jpg",
- "repo:id": "image-8a0806aaab",
- "@type": "image"
- }
- },
- {
- "repo:id": "category-df146c27a3",
- "xdm:name": "Minimalist Sensibility",
- "xdm:asset": null
- }
- ],
- "xdm:currencyCode": "USD",
- "dc:description": "
The Honora Wide Leg Pants definitely hold their own when it comes to standing out from the crowd. These pants feature a unique design and a varying color palette to make pairing a snap.
The Honora Wide Leg Pants definitely hold their own when it comes to standing out from the crowd. These pants feature a unique design and a varying color palette to make pairing a snap.
The Honora Wide Leg Pants definitely hold their own when it comes to standing out from the crowd. These pants feature a unique design and a varying color palette to make pairing a snap.
The Honora Wide Leg Pants definitely hold their own when it comes to standing out from the crowd. These pants feature a unique design and a varying color palette to make pairing a snap.