/******************************************************************************* * * Copyright 2019 Adobe. All rights reserved. * This file is licensed to you 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 REPRESENTATIONS * 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.it.http; import java.util.Iterator; import java.util.List; import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.sling.testing.clients.ClientException; import org.apache.sling.testing.clients.SlingHttpResponse; import org.apache.sling.testing.clients.util.FormEntityBuilder; import org.apache.sling.testing.clients.util.URLParameterBuilder; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import com.adobe.cq.testing.mockserver.RequestResponseRule; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import static com.adobe.cq.testing.mockserver.MockRequest.request; import static com.adobe.cq.testing.mockserver.MockResponse.response; import static com.adobe.cq.testing.mockserver.RequestResponseRule.rule; import static org.apache.http.HttpStatus.SC_CREATED; import static org.apache.http.HttpStatus.SC_OK; public class GraphqlProductConsoleIT extends CommerceTestBase { private static final String CORAL_COLUMN_FORMAT_EQUALS = "coral-columnview-item[data-foundation-collection-item-id=%s]"; private static final String CORAL_COLUMN_FORMAT_STARTS_WITH = "coral-columnview-item[data-foundation-collection-item-id^=%s]"; private static final String JCR_PRODUCT_ROOT = "we-retail"; private static final String JCR_BASE_PATH = "/var/commerce/products/graphql"; private static final String FOLDER_PROPERTIES = "/mnt/overlay/commerce/gui/content/products/folderproperties.html"; @BeforeClass public static void setup() throws ClientException, InterruptedException { // Create product binding HttpEntity params = FormEntityBuilder.create() .addParameter("_charset_", "utf-8") .addParameter("./jcr:title", "graphql") .addParameter(":name", "graphql") .addParameter("./cq:catalogDataResourceProviderFactory", "magento-graphql") .addParameter("./cq:catalogIdentifier", "default") .addParameter("./cq:magentoStore", "default") .addParameter("./magentoRootCategoryId", "4") .addParameter("./jcr:language", "en_us") .addParameter("./jcr:primaryType", "sling:Folder") .build(); cAdminAuthor.doPost("/var/commerce/products/", params, SC_CREATED); Thread.sleep(2000); } @Before public void resetMockServer() { mockServerRule.reset(); } @AfterClass public static void cleanup() throws ClientException, InterruptedException { Thread.sleep(5000); cAdminAuthor.deletePage(new String[] { JCR_BASE_PATH }, true, false, SC_OK); } public static RequestResponseRule.Builder CATEGORY_ROOT_RULE = rule() .on(request() .withMethod(HttpMethod.POST) .withRequestURI("/graphql") .withBody(s -> s.startsWith("{\"query\":\"{categoryList(filters:{ids:{eq:\\\"4\\\""))) .send(response() .withStatus(HttpStatus.OK_200) .withContentFromResource("/com/adobe/cq/commerce/it/http/magento-graphql-categorylist-root.json") .withContentType("application/json; charset=utf-8")); public static RequestResponseRule.Builder CATEGORY_MEN_RULE = rule() .on(request() .withMethod(HttpMethod.POST) .withRequestURI("/graphql") .withBody(s -> s.startsWith("{\"query\":\"{categoryList(filters:{url_key:{eq:\\\"men\\\""))) .send(response() .withStatus(HttpStatus.OK_200) .withContentFromResource("/com/adobe/cq/commerce/it/http/magento-graphql-categorylist-men.json") .withContentType("application/json; charset=utf-8")); public static RequestResponseRule.Builder CATEGORY_COATS_RULE = rule() .on(request() .withMethod(HttpMethod.POST) .withRequestURI("/graphql") .withBody(s -> s.startsWith("{\"query\":\"{categoryList(filters:{url_key:{eq:\\\"coats\\\""))) .send(response() .withStatus(HttpStatus.OK_200) .withContentFromResource("/com/adobe/cq/commerce/it/http/magento-graphql-categorylist-coats.json") .withContentType("application/json; charset=utf-8")); public static RequestResponseRule.Builder CATEGORY_MESKWIELT_EMPTY_RULE = rule() .on(request() .withMethod(HttpMethod.POST) .withRequestURI("/graphql") .withBody(s -> s.startsWith("{\"query\":\"{categoryList(filters:{url_key:{eq:\\\"meskwielt\\\""))) .send(response() .withStatus(HttpStatus.OK_200) .withContentFromResource("/com/adobe/cq/commerce/it/http/magento-graphql-categorylist-men.json") .withContentType("application/json; charset=utf-8")); public static RequestResponseRule.Builder SEARCH_PRODUCTS_IN_CATEGORY = rule() .on(request() .withMethod(HttpMethod.POST) .withRequestURI("/graphql") .withBody(s -> s.startsWith("{\"query\":\"{category(id:19)"))) .send(response() .withStatus(HttpStatus.OK_200) .withContentFromResource("/com/adobe/cq/commerce/it/http/magento-graphql-category-products.json") .withContentType("application/json; charset=utf-8")); public static RequestResponseRule.Builder SEARCH_PRODUCT_BY_SKU = rule() .on(request() .withMethod(HttpMethod.POST) .withRequestURI("/graphql") .withBody(s -> s.startsWith("{\"query\":\"{products(filter:{sku:{eq:\\\"meskwielt\\\"}})"))) .send(response() .withStatus(HttpStatus.OK_200) .withContentFromResource("/com/adobe/cq/commerce/it/http/magento-graphql-product.json") .withContentType("application/json; charset=utf-8")); public static RequestResponseRule.Builder SEARCH_PRODUCTS_FULL_TEXT = rule() .on(request() .withMethod(HttpMethod.POST) .withRequestURI("/graphql") .withBody(s -> s.startsWith("{\"query\":\"{products(search:\\\"coats\\\""))) .send(response() .withStatus(HttpStatus.OK_200) .withContentFromResource("/com/adobe/cq/commerce/it/http/magento-graphql-product.json") .withContentType("application/json; charset=utf-8")); @Test public void testCategoryRoot() throws Exception { // Prepare mockServerRule.add(CATEGORY_ROOT_RULE.build()); // Perform SlingHttpResponse response = cAuthorAuthor.doGet("/libs/commerce/gui/content/products.html" + JCR_BASE_PATH, null, NO_CACHE_HEADERS, SC_OK); // Verify mockServerRule.verify(); Document doc = Jsoup.parse(response.getContent()); // Check existence of root categories Assert.assertTrue(doc.select(String.format(CORAL_COLUMN_FORMAT_EQUALS, JCR_BASE_PATH + "/equipment")).size() > 0); Assert.assertTrue(doc.select(String.format(CORAL_COLUMN_FORMAT_EQUALS, JCR_BASE_PATH + "/men")).size() > 0); Assert.assertTrue(doc.select(String.format(CORAL_COLUMN_FORMAT_EQUALS, JCR_BASE_PATH + "/women")).size() > 0); // Check that child categories are not displayed Assert.assertEquals(0, doc.select(String.format(CORAL_COLUMN_FORMAT_EQUALS, JCR_BASE_PATH + "/men/pants")).size()); } @Test public void testProductsInCategory() throws Exception { // Prepare mockServerRule.add(CATEGORY_COATS_RULE.build()); mockServerRule.add(CATEGORY_MEN_RULE.build()); mockServerRule.add(SEARCH_PRODUCTS_IN_CATEGORY.build()); // Perform SlingHttpResponse response = cAuthorAuthor.doGet("/aem/products.html" + JCR_BASE_PATH + "/men/coats", null, NO_CACHE_HEADERS, SC_OK); // Verify mockServerRule.verify(); Document doc = Jsoup.parse(response.getContent()); // Check existence of one child product Assert.assertTrue(doc.select(String.format(CORAL_COLUMN_FORMAT_EQUALS, JCR_BASE_PATH + "/men/coats/meotwibrt")).size() > 0); // Check that products from other categories are not displayed Assert.assertEquals(0, doc.select(String.format(CORAL_COLUMN_FORMAT_EQUALS, JCR_BASE_PATH + "/men/footwear/meotwisus")).size()); } @Test public void testProductDetails() throws Exception { // Prepare mockServerRule.add(CATEGORY_MESKWIELT_EMPTY_RULE.build()); mockServerRule.add(CATEGORY_COATS_RULE.build()); mockServerRule.add(CATEGORY_MEN_RULE.build()); mockServerRule.add(SEARCH_PRODUCT_BY_SKU.build()); // Perform SlingHttpResponse response = cAuthorAuthor.doGet("/aem/products.html" + JCR_BASE_PATH + "/men/coats/meskwielt", null, NO_CACHE_HEADERS, SC_OK); // Verify mockServerRule.verify(); Document doc = Jsoup.parse(response.getContent()); // Check variants Assert.assertEquals(15, doc.select(String.format(CORAL_COLUMN_FORMAT_STARTS_WITH, JCR_BASE_PATH + "/men/coats/meskwielt/meskwielt-")).size()); } @Test public void testProductPropertiesPage() throws ClientException { // Prepare mockServerRule.add(CATEGORY_MESKWIELT_EMPTY_RULE.build()); mockServerRule.add(CATEGORY_COATS_RULE.build()); mockServerRule.add(SEARCH_PRODUCT_BY_SKU.build()); // 1. Update the scaffolding to point to the test catalog String scaffoldingPath = "/apps/commerce/scaffolding/product/jcr:content"; UrlEncodedFormEntity postParams = FormEntityBuilder.create().addParameter("cq:targetPath", JCR_BASE_PATH).build(); cAdminAuthor.doPost(scaffoldingPath, postParams, 200); // 2. Request a product properties page String productPropertiesUrl = "/mnt/overlay/commerce/gui/content/products/properties.html"; List<NameValuePair> params = URLParameterBuilder.create() .add("item", JCR_BASE_PATH + "/men/coats/meskwielt") .getList(); SlingHttpResponse response = cAuthorAuthor.doGet(productPropertiesUrl, params, 200); // 3. Check the fields Document doc = Jsoup.parse(response.getContent()); Assert.assertEquals("The Title field is preset", 1, doc.select("input[name=jcr:title]").size()); Assert.assertEquals("The Title has the correct value", "El Gordo Down Jacket", doc.select("input[name=jcr:title]").val()); Assert.assertEquals("The Price field is correct", 1, doc.select("input[name=formattedPrice]").size()); Assert.assertEquals("The Price field has the correct value", "USD 119.0", doc.select("input[name=formattedPrice]").val()); Assert.assertEquals("The SKU field is correct", 1, doc.select("input[name=sku]").size()); Assert.assertEquals("The SKU field has the correct value", "meskwielt", doc.select("input[name=sku]").val()); } @Test public void testCifFolderProperties() throws Exception { mockServerRule.add(CATEGORY_MEN_RULE.build()); SlingHttpResponse response = cAuthorAuthor.doGet(FOLDER_PROPERTIES + JCR_BASE_PATH + "/men", SC_OK); mockServerRule.verify(); Document doc = Jsoup.parse(response.getContent()); Assert.assertEquals("Close", doc.select("a[id=shell-propertiespage-closeactivator]").text()); Elements title = doc.select("input[name=jcr:title]"); Assert.assertTrue(title.hasAttr("disabled")); Assert.assertEquals("Men", title.val()); Elements categoryId = doc.select("input[name=cifId]"); Assert.assertTrue(title.hasAttr("disabled")); Assert.assertEquals("15", categoryId.val()); } @Test public void testJCRFolderProperties() throws Exception { SlingHttpResponse response = cAuthorAuthor.doGet(FOLDER_PROPERTIES + "/var/commerce/products/" + JCR_PRODUCT_ROOT, SC_OK); mockServerRule.verify(); Document doc = Jsoup.parse(response.getContent()); Assert.assertEquals("Cancel", doc.select("a[id=shell-propertiespage-closeactivator]").text()); Assert.assertFalse(doc.select("input[name=./jcr:title]").hasAttr("disabled")); Assert.assertFalse(doc.select("input[name=./jcr:primaryType]").hasAttr("disabled")); } @Test public void testCategoryFolderProperties() throws Exception { // Prepare mockServerRule.add(CATEGORY_COATS_RULE.build()); // Perform SlingHttpResponse response = cAuthorAuthor.doGet(FOLDER_PROPERTIES + JCR_BASE_PATH + "/men/coats", null, NO_CACHE_HEADERS, SC_OK); // Verify mockServerRule.verify(); Document doc = Jsoup.parse(response.getContent()); // Verify property fields Assert.assertEquals("Coats", doc.select("input[name=jcr:title]").val()); Assert.assertTrue(doc.select("input[name=jcr:title]").hasAttr("disabled")); } @Test public void testAssetsProductsFinder() throws Exception { // Prepare mockServerRule.add(CATEGORY_COATS_RULE.build()); mockServerRule.add(CATEGORY_MEN_RULE.build()); mockServerRule.add(SEARCH_PRODUCTS_FULL_TEXT.build()); mockServerRule.add(SEARCH_PRODUCT_BY_SKU.build()); List<NameValuePair> params = URLParameterBuilder.create() .add("_dc", "1560254795557") .add("query", "coats") .add("itemResourceType", "commerce/gui/components/authoring/assetfinder/product") .add("limit", "0..20") .add("_charset_", "utf-8") .add("_", "1560254761223") .getList(); // Perform SlingHttpResponse response = cAuthorAuthor.doGet("/bin/wcm/contentfinder/cifproduct/view.html" + JCR_BASE_PATH, params, NO_CACHE_HEADERS, SC_OK); // Verify mockServerRule.verify(); Document doc = Jsoup.parse(response.getContent()); Elements elements = doc.select("coral-card[data-asset-group=product]"); Assert.assertEquals(1, elements.size()); Assert.assertEquals(JCR_BASE_PATH + "/men/coats/meskwielt", elements.attr("data-path")); } @Test public void testOmnisearch() throws Exception { // Prepare mockServerRule.add(CATEGORY_COATS_RULE.build()); mockServerRule.add(CATEGORY_MEN_RULE.build()); mockServerRule.add(SEARCH_PRODUCTS_FULL_TEXT.build()); mockServerRule.add(SEARCH_PRODUCT_BY_SKU.build()); List<NameValuePair> params = URLParameterBuilder.create() .add("p.guessTotal", "1000") .add("fulltext", "coats") .add("path", "/var/commerce/products") .add("property", "cq:commerceType") .add("property.value", "product") .add("_", "1562672338517") .getList(); // Perform SlingHttpResponse response = cAuthorAuthor.doGet("/mnt/overlay/granite/ui/content/shell/omnisearch/searchresults.html", params, NO_CACHE_HEADERS, SC_OK); // Verify mockServerRule.verify(); Document doc = Jsoup.parse(response.getContent()); Elements elements = doc.select("coral-card[data-path^=" + JCR_BASE_PATH + "]"); Assert.assertEquals(1, elements.size()); Assert.assertEquals(JCR_BASE_PATH + "/men/coats/meskwielt", elements.attr("data-path")); } @Test public void testOmnisearchSuggestions() throws Exception { // Prepare mockServerRule.add(CATEGORY_COATS_RULE.build()); mockServerRule.add(SEARCH_PRODUCTS_FULL_TEXT.build()); mockServerRule.add(SEARCH_PRODUCT_BY_SKU.build()); List<NameValuePair> params = URLParameterBuilder.create() .add("p.guessTotal", "1000") .add("fulltext", "coats") .add("path", "/var/commerce/products") .add("property", "cq:commerceType") .add("property.value", "product") .add("location", "product") .add("_", "1562672338517") .getList(); // Perform SlingHttpResponse response = cAuthorAuthor.doGet("/libs/granite/omnisearch", params, NO_CACHE_HEADERS, SC_OK); // Verify mockServerRule.verify(); // the JSON result must contain the suggestion "El gordo down jacket" JsonNode jsonNode = new ObjectMapper().readTree(response.getContent()); JsonNode suggestions = jsonNode.get("suggestions"); Assert.assertEquals(2, suggestions.size()); boolean hasJacket = false; Iterator<JsonNode> elements = suggestions.elements(); while (elements.hasNext()) { JsonNode suggestion = elements.next().get("suggestion"); if (suggestion != null && suggestion.asText().equals("El gordo down jacket")) { hasJacket = true; break; } } Assert.assertTrue(hasJacket); } }