diff --git a/e2e-test/pom.xml b/e2e-test/pom.xml index be3e26fba..f6f0c8256 100644 --- a/e2e-test/pom.xml +++ b/e2e-test/pom.xml @@ -11,7 +11,7 @@ Search Analytics E2E Tests Test Project to test Search Service and Analytics Features on a complete setup of Alfresco, Share - 6.0.0.1 + 6.0.0.3 6.0.0.0 2.0.1 2.6.0 diff --git a/e2e-test/src/main/resources/model/search-1063-model.xml b/e2e-test/src/main/resources/model/search-1063-model.xml new file mode 100755 index 000000000..816afd2bc --- /dev/null +++ b/e2e-test/src/main/resources/model/search-1063-model.xml @@ -0,0 +1,159 @@ + + + Administrator admin user + + + + + + + + + + + + + + + + + song + cm:content + + + Name + d:text + false + + true + BOTH + false + + + + Genre + d:text + false + + true + BOTH + false + + + + Producer + d:text + false + + true + BOTH + false + + + + + + + + + Artist + cm:content + + + Name + d:text + false + + true + BOTH + false + + + + Voice Type + d:text + false + + true + BOTH + false + + + + + + + + + + Bassist + cm:content + + + Name + d:text + false + + true + BOTH + false + + + + + + + + + Drummer + cm:content + + + Name + d:text + false + + true + BOTH + false + + + + + + + + + Sax + cm:content + + + Name + d:text + false + + true + BOTH + false + + + + + + + + + + \ No newline at end of file diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLAPITest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLAPITest.java new file mode 100644 index 000000000..d2f5c0be7 --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLAPITest.java @@ -0,0 +1,781 @@ +/* + * Copyright (C) 2018 Alfresco Software Limited. + * This file is part of Alfresco + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.insightEngine.sql; + +import static java.util.Arrays.asList; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalToIgnoringCase; +import static org.hamcrest.Matchers.everyItem; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isIn; +import static org.hamcrest.Matchers.not; + +import org.alfresco.rest.core.RestResponse; +import org.alfresco.service.search.e2e.searchservices.AbstractSearchTest; +import org.alfresco.rest.search.SearchSqlRequest; +import org.alfresco.utility.constants.UserRole; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.model.UserModel; +import org.hamcrest.Matchers; +import org.springframework.http.HttpStatus; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Tests for /sql end point Search API. + * + * @author Meenal Bhave + */ +public class SearchSQLAPITest extends AbstractSearchTest +{ + /** + * Path selector to get the values of all cm_name entries for returned search results. + *

+ * For example it will retrieve the "alfresco.txt" from: + *

+     * {"list": {
+     *    "entries": [
+     *       {"entry": [{
+     *          "label": "cm_name",
+     *          "value": "alfresco.txt"
+     *       }]},
+     *       ...
+     * 
+ */ + private static final String CM_NAME_VALUES = "list.entries.collect {it.entry.findAll {it.label == 'cm_name'}}.flatten().value"; + String sql = "select SITE, CM_OWNER from alfresco group by SITE,CM_OWNER"; + String[] locales = { "en-US" }; + String solrFormat = "solr"; + + /** + * API post: + * { + * "stmt": "select SITE from alfresco", + * "locales" : ["en_US"], + * "format" : "solr", + * "timezone":"", + * "includeMetadata":false + * } + * Example Response: In Solr Format + * { + * "result-set": + * { + * "docs": + * [ + * { + * "SITE": + * [ + * "swsdp" + * ] + * }, + * { + * "SITE": + * [ + * "swsdp" + * ] + * } + * ] + * } + */ + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 01) + public void testWithSolr() throws Exception + { + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setFormat(solrFormat); + sqlRequest.setLocales(locales); + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("result-set", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs", Matchers.notNullValue()); + } + + /** + * API post example: + * { + * "stmt": "select SITE from alfresco", + * "locales" : ["en_US"], + * "format" : "json", + * "timezone":"", + * "includeMetadata":false + * } + * Example Response: In json Format + * { + * "list": + * { + * "pagination": + * { + "count": 103, + "hasMoreItems": false, + "totalItems": 103, + "skipCount": 0, + "maxItems": 1000 + }, + * "entries": + * [ + * "entry": [ + { + "label": "SITE", + "value": "[\"swsdp\"]" + } + ], + "entry": [ + { + "label": "SITE", + "value": "[\"swsdp\"]" + } + ] + * ] + * } + */ + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 02) + public void testWithJson() throws Exception + { + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setLocales(locales); + // Format not set + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("result-set", Matchers.nullValue()); + restClient.onResponse().assertThat().body("list.entries", Matchers.notNullValue()); + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setLocales(locales); + sqlRequest.setFormat("json"); // Format json + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("result-set", Matchers.nullValue()); + restClient.onResponse().assertThat().body("list.entries", Matchers.notNullValue()); + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setLocales(locales); + sqlRequest.setFormat("abcd"); // Format any other than solr + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("result-set", Matchers.nullValue()); + restClient.onResponse().assertThat().body("list.entries", Matchers.notNullValue()); + } + + /** + * API post: + * { + * "stmt": "select SITE from alfresco", + * "locales" : ["en_US"], + * "format" : "solr", + * "timezone":"", + * "includeMetadata":true + * } + * Example Response: In Solr Format: Includes metadata: aliases, fields, isMetadata=true + * { + * "result-set": { + * "docs": [ + * { + * "aliases": { + * "SITE": "SITE" + * }, + * "isMetadata": true, + * "fields": [ + * "SITE" + * ] + * }, + * { + * "SITE": [ + * "swsdp" + * ] + * }, + * { + * "SITE": [ + * "swsdp" + * ] + * }, + * { + * "RESPONSE_TIME": 79, + * "EOF": true + * } + * ] + * } + * } + */ + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 03) + public void testWithSolrIncludeMetadata() throws Exception + { + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setFormat(solrFormat); + sqlRequest.setLocales(locales); + sqlRequest.setIncludeMetadata(true); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("result-set", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.SITE", equalToIgnoringCase("SITE")); + restClient.onResponse().assertThat().body("result-set.docs[0].isMetadata", is(true)); + restClient.onResponse().assertThat().body("result-set.docs[0].fields[0]", equalToIgnoringCase("SITE")); + } + + /** + * API post example: + * { + * "stmt": "select SITE from alfresco limit 2", + * "locales" : ["en_US"], + * "format" : "json", + * "timezone":"", + * "includeMetadata":false + * } + * Example Response: In json Format + * { + * "list": + * { + * "pagination": + * { + "count": 3, + "hasMoreItems": false, + "totalItems": 3, + "skipCount": 0, + "maxItems": 1000 + }, + * "entries": + * [ + * "entry": [ + { + "label": "aliases", + "value": "{\"SITE\":\"SITE\"}" + }, + { + "label": "isMetadata", + "value": "true" + }, + { + "label": "fields", + "value": "[\"SITE\"]" + } + ] + }, + * "entry": [ + { + "label": "SITE", + "value": "[\"swsdp\"]" + } + ], + "entry": [ + { + "label": "SITE", + "value": "[\"swsdp\"]" + } + ] + * ] + * } + */ + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 04) + public void testWithJsonIncludeMetadata() throws Exception + { + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setFormat(""); + sqlRequest.setLocales(locales); + sqlRequest.setIncludeMetadata(true); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("result-set", Matchers.nullValue()); + restClient.onResponse().assertThat().body("list.entries", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].label", equalToIgnoringCase("aliases")); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].value", equalToIgnoringCase("{\"SITE\":\"SITE\",\"cm_owner\":\"CM_OWNER\"}")); + restClient.onResponse().assertThat().body("list.entries.entry[0][1].label", equalToIgnoringCase("isMetadata")); + restClient.onResponse().assertThat().body("list.entries.entry[0][1].value", is("true")); + restClient.onResponse().assertThat().body("list.entries.entry[0][2].label", equalToIgnoringCase("fields")); + restClient.onResponse().assertThat().body("list.entries.entry[0][2].value", equalToIgnoringCase("[\"SITE\",\"cm_owner\"]")); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 05) + public void testIncludeMetadataFalse() throws Exception + { + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setFormat("solr"); // Format solr + sqlRequest.setIncludeMetadata(false); + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("result-set", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases", Matchers.nullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].isMetadata", Matchers.nullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].fields", Matchers.nullValue()); + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setIncludeMetadata(false); + sqlRequest.setFormat("json"); // Format json + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("result-set", Matchers.nullValue()); + restClient.onResponse().assertThat().body("list.entries", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].label", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].label", not("aliases")); + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setIncludeMetadata(false); // Format not set + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("result-set", Matchers.nullValue()); + restClient.onResponse().assertThat().body("list.entries", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].label", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].label", not("aliases")); + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setFormat(solrFormat); // IncludeMetadata = false when not specified + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("result-set", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases", Matchers.nullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].isMetadata", Matchers.nullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].fields", Matchers.nullValue()); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 06) + public void testLocales() throws Exception + { + String[] noLocales = {}; // Not specified + + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setLocales(noLocales); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + + String[] singleLocale = { "en-Uk" }; + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setLocales(singleLocale); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + + String[] multipleLocales = { "en-US", "ja" }; + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setLocales(multipleLocales); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 07) + public void testTimezone() throws Exception + { + String timezone = ""; // Not specified + + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setTimezone(timezone); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + + timezone = "UTC"; // UTC + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setTimezone(timezone); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + + timezone = "false"; // Invalid timezone is ignored + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setTimezone(timezone); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 8) + public void testAggregateSQL() throws Exception + { + String agSql = "select count(*) FROM alfresco where TYPE='cm:content'"; + + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(agSql); + + RestResponse response = searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + response.assertThat().body("list.entries", Matchers.notNullValue()); + response.assertThat().body("list.entries.entry[0][0].value", not("0")); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 9) + public void testLimit() throws Exception + { + Integer defaultLimit = 1000; + + Integer limit = null; // Limit defaults to the default setting when null + + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setLimit(limit); + + RestResponse response = searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + response.assertThat().body("list.pagination.maxItems", Matchers.equalTo(defaultLimit)); + + limit = 0; // Limit defaults to the default setting when 0 + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setLimit(limit); + + response = searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + response.assertThat().body("list.pagination.maxItems", Matchers.equalTo(defaultLimit)); + + limit = 100; // Limit is applied correctly, with maxItems = limit + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setLimit(limit); + + response = searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + response.assertThat().body("list.pagination.maxItems", Matchers.equalTo(limit)); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 10) + public void testAuthenticationError() throws Exception + { + UserModel dummyUser = dataUser.createRandomTestUser("UserSearchDummy"); + dummyUser.setPassword("incorrect-password"); + + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setLocales(locales); + + restClient.authenticateUser(dummyUser).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.UNAUTHORIZED); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 11) + public void testPermissions() throws Exception + { + UserModel userNoPerm = dataUser.createRandomTestUser("UserSearchNoPerm"); + UserModel userPerm = dataUser.createRandomTestUser("UserSearchPerm"); + + dataUser.addUserToSite(userPerm, siteModel, UserRole.SiteContributor); + + String siteSQL = "select SITE, CM_OWNER from alfresco where SITE='" + siteModel.getId() + "'"; + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(siteSQL); + + // 0 results expected for query as User does not have access to the private site + restClient.authenticateUser(userNoPerm).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("list.pagination.count", is(0)); + restClient.onResponse().assertThat().body("list.pagination.totalItems", is(0)); + restClient.onResponse().assertThat().body("list.pagination.hasMoreItems", is(false)); + restClient.onResponse().assertThat().body("list.entries", empty()); + + // Results expected for query as User does has access to the private site + restClient.authenticateUser(userPerm).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().body("list.pagination.count", greaterThan(0)); + restClient.onResponse().assertThat().body("list.pagination.totalItems", greaterThan(0)); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].label", equalToIgnoringCase("SITE")); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].value", equalToIgnoringCase("[\"" + siteModel.getId() + "\"]")); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 12) + public void testErrors() throws Exception + { + String incorrectSQL = ""; // Missing SQL + + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(incorrectSQL); + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST); + restClient.onResponse().assertThat().body("error.briefSummary", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("error.briefSummary", Matchers.containsString("Required stmt parameter is missing")); + + incorrectSQL = "select SITE from unknownTable"; // Wrong table name + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(incorrectSQL); + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST); + restClient.onResponse().assertThat().body("error.briefSummary", Matchers.notNullValue()); + // restClient.onResponse().assertThat().body("error.briefSummary", Matchers.containsString("Table 'unknownTable' not found")); + + incorrectSQL = "select Column1 from alfresco"; // Wrong ColumnName + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(incorrectSQL); + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST); + restClient.onResponse().assertThat().body("error.briefSummary", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("error.briefSummary", Matchers.containsString("Column 'Column1' not found")); + + incorrectSQL = "select SITE alfresco"; // BAD SQL Grammar: from missing + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(incorrectSQL); + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST); + restClient.onResponse().assertThat().body("error.briefSummary", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("error.briefSummary", Matchers.containsString("Unable to execute the query")); + + incorrectSQL = "select SITE, CM_OWNER from alfresco group by SITE"; // BAD SQL Grammar: CM_OWNER is not being grouped + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(incorrectSQL); + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST); + restClient.onResponse().assertThat().body("error.briefSummary", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("error.briefSummary", Matchers.containsString("Expression 'CM_OWNER' is not being grouped")); + + incorrectSQL = "delete SITE from alfresco"; // BAD SQL Grammar + + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(incorrectSQL); + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST); + restClient.onResponse().assertThat().body("error.briefSummary", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("error.briefSummary", Matchers.containsString("Was expecting one of")); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.NOT_INSIGHT_ENGINE }, priority = 13) + public void testErrorForSQLAPIWithASS() throws Exception + { + String acsVersion = serverHealth.getAlfrescoVersion(); + HttpStatus expectedStatus = HttpStatus.OK; + + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setLocales(locales); + + // Set expected result based on ACS Version and ASS Version + if (!acsVersion.startsWith("6")) + { + expectedStatus = HttpStatus.NOT_FOUND; + } + else + { + expectedStatus = HttpStatus.BAD_REQUEST; + } + + restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(sqlRequest); + restClient.assertStatusCodeIs(expectedStatus); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 14) + public void testSelectStar() throws Exception + { + // Select * with Limit, json format + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql("select * from alfresco"); + sqlRequest.setLimit(1); + + RestResponse response = searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + response.assertThat().body("list.pagination.maxItems", Matchers.equalTo(1)); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].label", equalToIgnoringCase("PATH")); + + // Select * with Limit, solr format: Also covered in JDBC + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql("select * from alfresco"); + sqlRequest.setFormat("solr"); + sqlRequest.setIncludeMetadata(true); + sqlRequest.setLimit(1); + + response = searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.cm_name", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.cm_created", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.cm_creator", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.cm_modified", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.cm_modifier", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.cm_owner", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.OWNER", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.TYPE", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.LID", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.DBID", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.cm_title", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.cm_description", Matchers.notNullValue()); + + // This type of assertion is required because of a '.' in the field name + Assert.assertTrue(response.getResponse().body().jsonPath().get("result-set.docs[0].aliases").toString().contains("cm_content.size=cm_content.size")); + Assert.assertTrue(response.getResponse().body().jsonPath().get("result-set.docs[0].aliases").toString().contains("cm_content.mimetype=cm_content.mimetype")); + Assert.assertTrue(response.getResponse().body().jsonPath().get("result-set.docs[0].aliases").toString().contains("cm_content.encoding=cm_content.encoding")); + Assert.assertTrue(response.getResponse().body().jsonPath().get("result-set.docs[0].aliases").toString().contains("cm_content.locale=cm_content.locale")); + + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.cm_lockOwner", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.SITE", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.PARENT", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.PATH", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.PRIMARYPARENT", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.ASPECT", Matchers.notNullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.QNAME", Matchers.notNullValue()); + + // Test that cm_content and any other random field does not appear in the response + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.cm_content", Matchers.nullValue()); + restClient.onResponse().assertThat().body("result-set.docs[0].aliases.RandomNonExistentField", Matchers.nullValue()); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 15) + public void testDistinct() throws Exception + { + // Select distinct site: json format + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql("select distinct Site from alfresco"); + sqlRequest.setLimit(10); + + RestResponse response = searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + response.assertThat().body("list.pagination.maxItems", Matchers.equalTo(10)); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].label", equalToIgnoringCase("site")); + + // Select distinct cm_name: solr format + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql("select distinct cm_name from alfresco limit 5"); + sqlRequest.setFormat("solr"); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("result-set.docs[0].cm_name", Matchers.notNullValue()); + } + + /** Check that a filter query can affect which results are included. */ + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_11 }, priority = 16) + public void testFilterQuery() throws Exception + { + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + String siteSQL = "select SITE, CM_OWNER from alfresco where SITE='" + siteModel.getId() + "'"; + sqlRequest.setSql(siteSQL); + // Add a filter query to only include results from inside the site (i.e. all results). + String[] filterQuery = { "SITE:'" + siteModel.getId() + "'" }; + sqlRequest.setFilterQuery(filterQuery); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("list.pagination.count", greaterThan(0)); + restClient.onResponse().assertThat().body("list.pagination.totalItems", greaterThan(0)); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].label", equalToIgnoringCase("SITE")); + restClient.onResponse().assertThat().body("list.entries.entry[0][0].value", equalToIgnoringCase("[\"" + siteModel.getId() + "\"]")); + + // Now try instead removing everything from the site (i.e. all results). + String[] inverseFilterQuery = { "-SITE:'" + siteModel.getId() + "'" }; + sqlRequest.setFilterQuery(inverseFilterQuery); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("list.pagination.count", is(0)); + restClient.onResponse().assertThat().body("list.pagination.totalItems", is(0)); + restClient.onResponse().assertThat().body("list.pagination.hasMoreItems", is(false)); + restClient.onResponse().assertThat().body("list.entries", empty()); + } + + /** Check that the combination of multiple filter queries produce the intersection of results. */ + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_11 }, priority = 17) + public void testCombiningFilterQueries() throws Exception + { + String siteSQL = "select cm_name from alfresco where SITE='" + siteModel.getId() + "'"; + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(siteSQL); + // Add a filter query to only include results from inside the site (i.e. all results). + String[] filterQueries = { "-cm_name:'cars'", "-cm_name:'pangram'" }; + sqlRequest.setFilterQuery(filterQueries); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("list.pagination.count", greaterThan(0)); + restClient.onResponse().assertThat().body("list.pagination.totalItems", greaterThan(0)); + // Check that pangram.txt and cars.txt were both filtered out. + restClient.onResponse().assertThat() + .body(CM_NAME_VALUES, everyItem(not(isIn(asList("pangram.txt", "cars.txt"))))); + } + + /** Check that an empty list of filter queries doesn't remove anything from the results. */ + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_11 }, priority = 18) + public void testEmptyFilterQuery() throws Exception + { + String siteSQL = "select cm_name from alfresco where SITE='" + siteModel.getId() + "'"; + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(siteSQL); + // Add an empty list of filter queries. + sqlRequest.setFilterQuery(new String[]{}); + + searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + // Check that the cm_names of all nodes in the site are returned. + restClient.onResponse().assertThat() + .body(CM_NAME_VALUES, containsInAnyOrder("documentLibrary", SEARCH_DATA_SAMPLE_FOLDER, "pangram.txt", "cars.txt", "alfresco.txt", unique_searchString + ".txt")); + } +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLPhraseTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLPhraseTest.java new file mode 100644 index 000000000..d379e0116 --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLPhraseTest.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2018 Alfresco Software Limited. + * This file is part of Alfresco + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.insightEngine.sql; + +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.alfresco.rest.core.RestResponse; +import org.alfresco.service.search.e2e.searchservices.AbstractSearchTest; +import org.alfresco.rest.search.SearchSqlJDBCRequest; +import org.alfresco.rest.search.SearchSqlRequest; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FileType; +import org.alfresco.utility.model.TestGroup; +import org.springframework.http.HttpStatus; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.hamcrest.Matchers; + +/** + * Tests for /sql end point Search API: Phrase Searching. + * + * @author Meenal Bhave + */ +public class SearchSQLPhraseTest extends AbstractSearchTest +{ + FileModel fileBanana, fileYellowBanana, fileBigYellowBanana, fileBigBananaBoat, fileYellowBananaBigBoat, fileBigYellowBoat; + + List expectedContent = new ArrayList(); + + @BeforeClass(alwaysRun = true) + public void dataPreparation() throws Exception + { + super.dataPreparation(); + + // Create files with different phrases + fileBanana = new FileModel(unique_searchString + "-1.txt", "banana", "phrase searching", FileType.TEXT_PLAIN, "banana"); + dataContent.usingUser(userModel).usingSite(siteModel).createContent(fileBanana); + + fileYellowBanana = new FileModel(unique_searchString + "-2.txt", "yellow banana", "phrase searching", FileType.TEXT_PLAIN, "yellow banana"); + dataContent.usingUser(userModel).usingSite(siteModel).createContent(fileYellowBanana); + + fileBigYellowBanana = new FileModel(unique_searchString + "-3.txt", "big yellow banana", "phrase searching", FileType.TEXT_PLAIN, "big yellow banana"); + dataContent.usingUser(userModel).usingSite(siteModel).createContent(fileBigYellowBanana); + + fileBigBananaBoat = new FileModel(unique_searchString + "-4.txt", "big boat", "phrase searching", FileType.TEXT_PLAIN, "big boat"); + dataContent.usingUser(userModel).usingSite(siteModel).createContent(fileBigBananaBoat); + + fileYellowBananaBigBoat = new FileModel(unique_searchString + "-5.txt", "yellow banana big boat", "phrase searching", FileType.TEXT_PLAIN, "yellow banana big boat"); + dataContent.usingUser(userModel).usingSite(siteModel).createContent(fileYellowBananaBigBoat); + + fileBigYellowBoat = new FileModel(unique_searchString + "-6.txt", "big yellow boat", "phrase searching", FileType.TEXT_PLAIN, "big yellow boat"); + dataContent.usingUser(userModel).usingSite(siteModel).createContent(fileBigYellowBoat); + + waitForIndexing(fileBigYellowBoat.getName(), true); + } + + @SuppressWarnings("unchecked") + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 1) + public void testPhraseQueries() throws Exception + { + // yellow banana: 5 results expected + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql("select cm_content from alfresco where cm_content = '(yellow banana)'"); + sqlRequest.setLimit(10); + + RestResponse response = searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + + // Set Expected Result + expectedContent = new ArrayList(); + expectedContent.add(Arrays.asList(fileBanana.getContent())); + expectedContent.add(Arrays.asList(fileYellowBanana.getContent())); + expectedContent.add(Arrays.asList(fileBigYellowBanana.getContent())); + expectedContent.add(Arrays.asList(fileYellowBananaBigBoat.getContent())); + expectedContent.add(Arrays.asList(fileBigYellowBoat.getContent())); + + // Check Result count matches + response.assertThat().body("list.pagination.count", Matchers.equalTo(expectedContent.size())); + + // Check Results match + Collection> actualContent = response.getResponse().body().jsonPath().get("list.entries.entry.value"); + Assert.assertTrue(actualContent.containsAll(expectedContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + Assert.assertTrue(expectedContent.containsAll(actualContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + + // yellow banana big boat: 6 results expected + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql("select cm_content from alfresco where cm_content = '(yellow banana big boat)'"); + sqlRequest.setLimit(10); + + response = searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + + // Set Expected Result + expectedContent = new ArrayList(); + expectedContent.add(Arrays.asList(fileBanana.getContent())); + expectedContent.add(Arrays.asList(fileYellowBanana.getContent())); + expectedContent.add(Arrays.asList(fileBigYellowBanana.getContent())); + expectedContent.add(Arrays.asList(fileYellowBananaBigBoat.getContent())); + expectedContent.add(Arrays.asList(fileBigYellowBoat.getContent())); + expectedContent.add(Arrays.asList(fileBigBananaBoat.getContent())); + + // Check Result count matches + response.assertThat().body("list.pagination.count", Matchers.equalTo(expectedContent.size())); + + // Check Results match + actualContent = response.getResponse().body().jsonPath().get("list.entries.entry.value"); + Assert.assertTrue(actualContent.containsAll(expectedContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + Assert.assertTrue(expectedContent.containsAll(actualContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + + // yellow banana big boat: 4 results expected + sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql("select cm_content from alfresco where cm_content = '(big boat)'"); + sqlRequest.setLimit(10); + + response = searchSql(sqlRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + + // Set Expected Result + expectedContent = new ArrayList(); + expectedContent.add(Arrays.asList(fileBigYellowBanana.getContent())); + expectedContent.add(Arrays.asList(fileYellowBananaBigBoat.getContent())); + expectedContent.add(Arrays.asList(fileBigYellowBoat.getContent())); + expectedContent.add(Arrays.asList(fileBigBananaBoat.getContent())); + + // Check Result count matches + response.assertThat().body("list.pagination.count", Matchers.equalTo(expectedContent.size())); + + // Check Results match + actualContent = response.getResponse().body().jsonPath().get("list.entries.entry.value"); + Assert.assertTrue(actualContent.containsAll(expectedContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + Assert.assertTrue(expectedContent.containsAll(actualContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + } + + @SuppressWarnings("unchecked") + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 2) + public void testPhraseQueriesViaJDBC() throws Exception + { + // yellow banana: 5 results expected + SearchSqlJDBCRequest sqlRequest = new SearchSqlJDBCRequest(); + String sql = "select cm_content from alfresco where cm_content = '(yellow banana)'"; + sqlRequest.setSql(sql); + sqlRequest.setAuthUser(userModel); + + ResultSet rs = restClient.withSearchSqlViaJDBC().executeQueryViaJDBC(sqlRequest); + Assert.assertNotNull(rs); + Assert.assertNull(sqlRequest.getErrorDetails()); + + // Set Expected Result + expectedContent = new ArrayList(); + expectedContent.add(Arrays.asList(fileBanana.getContent())); + expectedContent.add(Arrays.asList(fileYellowBanana.getContent())); + expectedContent.add(Arrays.asList(fileBigYellowBanana.getContent())); + expectedContent.add(Arrays.asList(fileYellowBananaBigBoat.getContent())); + expectedContent.add(Arrays.asList(fileBigYellowBoat.getContent())); + + Integer i = 0; + List actualContent = new ArrayList(); + + while (rs.next()) + { + // Field values are retrieved + Assert.assertNotNull(rs.getString("cm_content")); + actualContent.add(Arrays.asList(rs.getString("cm_content"))); + i++; + } + + Assert.assertTrue(i == expectedContent.size()); + Assert.assertTrue(actualContent.containsAll(expectedContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + Assert.assertTrue(expectedContent.containsAll(actualContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + + // yellow banana big boat: 6 results expected + sql = "select cm_content from alfresco where cm_content = '(yellow banana big boat)'"; + sqlRequest.setSql(sql); + sqlRequest.setAuthUser(userModel); + + rs = restClient.withSearchSqlViaJDBC().executeQueryViaJDBC(sqlRequest); + Assert.assertNotNull(rs); + Assert.assertNull(sqlRequest.getErrorDetails()); + + // Set Expected Result + expectedContent = new ArrayList(); + expectedContent.add(Arrays.asList(fileBanana.getContent())); + expectedContent.add(Arrays.asList(fileYellowBanana.getContent())); + expectedContent.add(Arrays.asList(fileBigYellowBanana.getContent())); + expectedContent.add(Arrays.asList(fileYellowBananaBigBoat.getContent())); + expectedContent.add(Arrays.asList(fileBigYellowBoat.getContent())); + expectedContent.add(Arrays.asList(fileBigBananaBoat.getContent())); + + i = 0; + actualContent = new ArrayList(); + + while (rs.next()) + { + // Field values are retrieved + Assert.assertNotNull(rs.getString("cm_content")); + actualContent.add(Arrays.asList(rs.getString("cm_content"))); + i++; + } + + Assert.assertTrue(i == expectedContent.size()); + Assert.assertTrue(actualContent.containsAll(expectedContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + Assert.assertTrue(expectedContent.containsAll(actualContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + + // big boat: 4 results expected + sql = "select cm_content from alfresco where cm_content = '(big boat)'"; + sqlRequest.setSql(sql); + sqlRequest.setAuthUser(userModel); + + rs = restClient.withSearchSqlViaJDBC().executeQueryViaJDBC(sqlRequest); + Assert.assertNotNull(rs); + Assert.assertNull(sqlRequest.getErrorDetails()); + + // Set Expected Result + expectedContent = new ArrayList(); + expectedContent.add(Arrays.asList(fileBigYellowBanana.getContent())); + expectedContent.add(Arrays.asList(fileYellowBananaBigBoat.getContent())); + expectedContent.add(Arrays.asList(fileBigYellowBoat.getContent())); + expectedContent.add(Arrays.asList(fileBigBananaBoat.getContent())); + + i = 0; + actualContent = new ArrayList(); + + while (rs.next()) + { + // Field values are retrieved + Assert.assertNotNull(rs.getString("cm_content")); + actualContent.add(Arrays.asList(rs.getString("cm_content"))); + i++; + } + + Assert.assertTrue(i == expectedContent.size()); + Assert.assertTrue(actualContent.containsAll(expectedContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + Assert.assertTrue(expectedContent.containsAll(actualContent), String.format("Phrase Search Results expected: %s Actual: %s", expectedContent.toString(), actualContent.toString())); + } +} \ No newline at end of file diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLViaJDBCTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLViaJDBCTest.java new file mode 100644 index 000000000..47219e4ad --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLViaJDBCTest.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2018 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.insightEngine.sql; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.dataprep.SiteService.Visibility; +import org.alfresco.rest.requests.search.SearchSQLJDBC; +import org.alfresco.service.search.e2e.searchservices.AbstractSearchTest; +import org.alfresco.rest.search.SearchSqlJDBCRequest; +import org.alfresco.utility.data.RandomData; +import org.alfresco.utility.model.SiteModel; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.model.UserModel; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; +import org.testng.Assert; + +/** + * Search SQL end point test via JDBC. + * @author MSuzuki + * @author Meenal Bhave + * + */ +public class SearchSQLViaJDBCTest extends AbstractSearchTest +{ + List sites = new ArrayList(); + SearchSQLJDBC searchSql; + SearchSqlJDBCRequest sqlRequest = new SearchSqlJDBCRequest(); + + @AfterMethod(alwaysRun=true) + public void cleanUp() throws SQLException + { + restClient.withSearchSqlViaJDBC().clearSearchQuery(sqlRequest); + sqlRequest = new SearchSqlJDBCRequest(); + sites = new ArrayList(); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority=01) + public void testQueryPublicSite() throws SQLException + { + + SiteModel publicSite = new SiteModel(RandomData.getRandomName("SiteSearch")); + publicSite.setVisibility(Visibility.PUBLIC); + + publicSite = dataSite.usingUser(adminUserModel).createSite(publicSite); + + String sql = "select SITE,CM_OWNER from alfresco where SITE ='" + publicSite.getTitle() + "' group by SITE,CM_OWNER"; + sqlRequest.setSql(sql); + sqlRequest.setAuthUser(userModel); + + ResultSet rs = restClient.withSearchSqlViaJDBC().executeQueryViaJDBC(sqlRequest); + + while (rs.next()) + { + // User can see the Public Site created by other user + Assert.assertNotNull(rs.getString("SITE")); + Assert.assertTrue(publicSite.getTitle().equalsIgnoreCase(rs.getString("SITE"))); + + Assert.assertNotNull(rs.getString("CM_OWNER")); + Assert.assertTrue(rs.getString("CM_OWNER").contains(adminUserModel.getUsername())); + } + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority=02) + public void testQueryMyCreatedPrivateSite() throws SQLException + { + String sql = "select distinct SITE from alfresco where SITE ='" + siteModel.getTitle() + "'"; + sqlRequest.setSql(sql); + sqlRequest.setAuthUser(userModel); + + ResultSet rs = restClient.withSearchSqlViaJDBC().executeQueryViaJDBC(sqlRequest); + + while (rs.next()) + { + // User can see Own Private Site + Assert.assertNotNull(rs.getString("SITE")); + Assert.assertTrue(siteModel.getTitle().equalsIgnoreCase(rs.getString("SITE"))); + } + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority=03) + public void testQueryPrivateSiteWithSuperUser() throws SQLException + { + String sql = "select distinct SITE from alfresco where SITE ='" + siteModel.getTitle() + "'"; + sqlRequest.setSql(sql); + sqlRequest.setAuthUser(adminUserModel); + + ResultSet rs = restClient.withSearchSqlViaJDBC().executeQueryViaJDBC(sqlRequest); + + while (rs.next()) + { + // Super user can see Private Site created by other user + Assert.assertNotNull(rs.getString("SITE")); + Assert.assertTrue(siteModel.getTitle().equalsIgnoreCase(rs.getString("SITE"))); + } + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority=04) + public void testQueryPrivateSiteWithSimpleUser() throws SQLException + { + UserModel managerUser = dataUser.createRandomTestUser("UserSearchMgr"); + + String sql = "select SITE from alfresco where SITE = '" + siteModel.getTitle() + "'"; + sqlRequest.setSql(sql); + sqlRequest.setAuthUser(managerUser); + + // Non admin user can NOT see Private Site created by other user + ResultSet rs = restClient.withSearchSqlViaJDBC().executeQueryViaJDBC(sqlRequest); + Assert.assertFalse(rs.next()); + } + + + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority=05) + public void testQueryErrorReturned() throws SQLException + { + String expectedError = "Column 'SITE1' not found"; + + String sql = "select SITE1 from alfresco"; + sqlRequest.setSql(sql); + sqlRequest.setAuthUser(userModel); + + // Appropriate error is retrieved when SQL is incorrect + ResultSet rs = restClient.withSearchSqlViaJDBC().executeQueryViaJDBC(sqlRequest); + + String error = sqlRequest.getErrorDetails(); + Assert.assertNotNull(error); + Assert.assertTrue(error.contains(expectedError), "Error shown: " + error + " Error expected: " + expectedError); + + // Record set is null + Assert.assertNull(rs); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 06) + public void testQuerySelectStar() throws SQLException + { + // Select * query with limit clause + String sql = "select * from alfresco limit 5"; + sqlRequest.setSql(sql); + sqlRequest.setAuthUser(userModel); + + // Select * with limit clause works: No error is retrieved + ResultSet rs = restClient.withSearchSqlViaJDBC().executeQueryViaJDBC(sqlRequest); + Assert.assertNotNull(rs); + Assert.assertNull(sqlRequest.getErrorDetails()); + + while (rs.next()) + { + // Field values are retrieved + Assert.assertNotNull(rs.getString("PATH")); + Assert.assertNotNull(rs.getString("DBID")); + Assert.assertNotNull(rs.getString("cm_name")); + } + + // Select * query Without limit clause: No error is retrieved + sql = "select * from alfresco"; + sqlRequest.setSql(sql); + sqlRequest.setAuthUser(userModel); + + // No error is retrieved when SQL is incorrect + rs = restClient.withSearchSqlViaJDBC().executeQueryViaJDBC(sqlRequest); + Assert.assertNotNull(rs); + Assert.assertNull(sqlRequest.getErrorDetails()); + + while (rs.next()) + { + // Field values are retrieved + Assert.assertNotNull(rs.getString("PATH")); + Assert.assertNotNull(rs.getString("DBID")); + Assert.assertNotNull(rs.getString("cm_name")); + } + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.INSIGHT_10 }, priority = 07) + public void testQuerySelectDistinct() throws SQLException + { + // Select distinct query with limit clause + String sql = "select distinct cm_name from alfresco limit 5"; + sqlRequest.setSql(sql); + sqlRequest.setAuthUser(adminUserModel); + + // Select distinct with limit clause works: No error is retrieved + ResultSet rs = restClient.withSearchSqlViaJDBC().executeQueryViaJDBC(sqlRequest); + Assert.assertNotNull(rs); + Assert.assertNull(sqlRequest.getErrorDetails()); + + while (rs.next()) + { + // Field values are retrieved + Assert.assertNotNull(rs.getString("cm_name")); + } + } +} \ No newline at end of file diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLWithQuotedIdentifiers.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLWithQuotedIdentifiers.java new file mode 100644 index 000000000..50077f086 --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/insightEngine/sql/SearchSQLWithQuotedIdentifiers.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2018 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.insightEngine.sql; + +import java.util.UUID; + +import org.alfresco.service.search.e2e.searchservices.AbstractSearchTest; +import org.alfresco.utility.constants.UserRole; +import org.alfresco.utility.data.CustomObjectTypeProperties; +import org.alfresco.utility.model.ContentModel; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FileType; +import org.alfresco.utility.model.FolderModel; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.report.Bug; +import org.hamcrest.Matchers; +import org.springframework.http.HttpStatus; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.alfresco.rest.RestTest; + +/** + * Tests for /sql end point Search API when a custom model contains identifiers that need to be quoted in queries. + * While it seems there is not an ANSI standard for naming database objects, on the other side the most popular + * databases don't like columns starting with a number. + * In those cases, the column identifier need to be quoted. How to quote a given object identifier (a column name, in + * this case) depends on the specific database lexicon: Oracle uses double quotes ("), MySQL uses back quotes(`). + * + * At time of writing, the lexicon used by the /sql endpoint is MySql so queries that use column names starting with + * a number need to be quoted with back quotes. + * + */ +public class SearchSQLWithQuotedIdentifiers extends AbstractSearchTest +{ + + private String songName; + private String genre; + private String coProducer; + + private String artistName; + private String voiceType; + + private String bassistName; + private String drummerName; + private String saxophonistName; + + private FileModel file5; + + /** + * Setup fixture for this test case. + * Overrides the superlayer method because the data preparation requires a bit different preconditions. + * The method uses a transient test site created in the {@link RestTest#checkServerHealth()} and on top of that: + * + *
    + *
  • It adds a user which is added to the site as contributor
  • + *
  • + * It deploys a custom model which declares a set of prefixes composed by a combination of digits, hyphens + * and underscores. Those prefixes are associated with four entities (song, artist, bassist, drummer and + * sax) with their corresponding attributes. + *
  • + *
  • + * It creates a folder with 5 files associated with the types declared in the model. + * That allows those files to have a value for the properties/attributes included in the model definition. + *
  • + *
+ * + * @see RestTest#checkServerHealth() + * @throws Exception hopefully never, otherwise the test fails. + */ + @BeforeClass(alwaysRun = true) + public void localDataPreparation() throws Exception + { + songName = "The Dry Cleaner from Des Moines "; + genre = "Jazz, vocal jazz "; + coProducer = "Roberta Joan Mitchell "; + + artistName = "Joni Mitchell " + UUID.randomUUID(); + voiceType = "Blue Mezzo (1965-1984) / Cloudy Contralto (1985-present) "; + + bassistName = "Jaco Pastorius"; + drummerName = "Peter Erskine"; + saxophonistName = "Wayne Shorter"; + + dataContent.usingAdmin().deployContentModel("model/search-1063-model.xml"); + + dataUser.addUserToSite(userModel, siteModel, UserRole.SiteContributor); + + testSite = siteModel; + + FolderModel testFolder = dataContent.usingSite(testSite).usingUser(userModel).createFolder(); + + file = FileModel.getRandomFileModel(FileType.TEXT_PLAIN); + file2 = FileModel.getRandomFileModel(FileType.TEXT_PLAIN); + file3 = FileModel.getRandomFileModel(FileType.TEXT_PLAIN); + file4 = FileModel.getRandomFileModel(FileType.TEXT_PLAIN); + file5 = FileModel.getRandomFileModel(FileType.TEXT_PLAIN); + + dataContent.usingUser(userModel) + .usingResource(testFolder) + .createCustomContent( + file, + "D:1:song", + new CustomObjectTypeProperties() + .addProperty("1:name", songName) + .addProperty("1:genre", genre) + .addProperty("1:co-producer", coProducer)); + + dataContent.usingUser(userModel) + .usingResource(testFolder) + .createCustomContent( + file2, + "D:123:artist", + new CustomObjectTypeProperties() + .addProperty("123:name", artistName) + .addProperty("123:voice_type", voiceType)); + + dataContent.usingUser(userModel) + .usingResource(testFolder) + .createCustomContent( + file3, + "D:1_2_3:bassist", + new CustomObjectTypeProperties() + .addProperty("1_2_3:name", bassistName)); + + dataContent.usingUser(userModel) + .usingResource(testFolder) + .createCustomContent( + file4, + "D:1-2-3:drummer", + new CustomObjectTypeProperties() + .addProperty("1-2-3:name", drummerName)); + + ContentModel content = + dataContent.usingUser(userModel) + .usingResource(testFolder) + .createCustomContent( + file5, + "D:1-2_3:saxophonist", + new CustomObjectTypeProperties() + .addProperty("1-2_3:name", saxophonistName)); + + waitForIndexing(content.getName(), true); + } + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1}) + @Bug(id = "SEARCH-1063") + public void prefixIsComposedByOneNumber() throws Exception + { + executeSqlAsSolr("select cm_name, `1_name`, `1_genre`, `1_co-producer` from alfresco where TYPE='1:song' and SITE='" + testSite.getId() + "'"); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("result-set.docs[0].cm_name", Matchers.equalTo(file.getName())); + restClient.onResponse().assertThat().body("result-set.docs[0].1_name", Matchers.equalTo(songName)); + restClient.onResponse().assertThat().body("result-set.docs[0].1_genre", Matchers.equalTo(genre)); + restClient.onResponse().assertThat().body("result-set.docs[0].1_co-producer", Matchers.equalTo(coProducer)); + } + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1}) + @Bug(id = "SEARCH-1063") + public void prefixIsComposedByMultipleNumbers() throws Exception + { + executeSqlAsSolr("select cm_name, `123_name`, `123_voice_type` from alfresco where TYPE='123:artist' and SITE='" + testSite.getId() + "'"); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("result-set.docs[0].cm_name", Matchers.equalTo(file2.getName())); + restClient.onResponse().assertThat().body("result-set.docs[0].123_name", Matchers.equalTo(artistName)); + restClient.onResponse().assertThat().body("result-set.docs[0].123_voice_type", Matchers.equalTo(voiceType)); + } + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1}) + @Bug(id = "SEARCH-1063") + public void prefixIncludesUnderscore() throws Exception + { + executeSqlAsSolr("select cm_name, `1_2_3_name` from alfresco where TYPE='1_2_3:bassist' and SITE='" + testSite.getId() + "'"); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("result-set.docs[0].cm_name", Matchers.equalTo(file3.getName())); + restClient.onResponse().assertThat().body("result-set.docs[0].1_2_3_name", Matchers.equalTo(bassistName)); + } + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1}) + @Bug(id = "SEARCH-1063") + public void prefixIncludesHyphen() throws Exception + { + executeSqlAsSolr("select cm_name, `1-2-3_name` from alfresco where TYPE='1-2-3:drummer' and SITE='" + testSite.getId() + "'"); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("result-set.docs[0].cm_name", Matchers.equalTo(file4.getName())); + restClient.onResponse().assertThat().body("result-set.docs[0].1-2-3_name", Matchers.equalTo(drummerName)); + } + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1}) + @Bug(id = "SEARCH-1063") + public void prefixIncludesHyphenAndUnderscore() throws Exception + { + executeSqlAsSolr("select cm_name, `1-2_3_name` from alfresco where TYPE='1-2_3:saxophonist' and SITE='" + testSite.getId() + "'"); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("result-set.docs[0].cm_name", Matchers.equalTo(file5.getName())); + restClient.onResponse().assertThat().body("result-set.docs[0].1-2_3_name", Matchers.equalTo(saxophonistName)); + } +} \ No newline at end of file diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/AbstractSearchTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/AbstractSearchTest.java new file mode 100644 index 000000000..5e2597d49 --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/AbstractSearchTest.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2018 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import org.alfresco.dataprep.SiteService.Visibility; +import org.alfresco.rest.core.RestResponse; +import org.alfresco.rest.search.RestRequestHighlightModel; +import org.alfresco.rest.search.RestRequestQueryModel; +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.rest.search.SearchSqlRequest; +import org.alfresco.utility.Utility; +import org.alfresco.utility.data.RandomData; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FileType; +import org.alfresco.utility.model.FolderModel; +import org.alfresco.utility.model.SiteModel; +import org.alfresco.utility.model.UserModel; +import org.springframework.http.HttpStatus; +import org.testng.annotations.BeforeClass; +import org.alfresco.rest.RestTest; + +import javax.naming.AuthenticationException; + +/** + * Abstract Search test class that contains useful methods + * such as: + *
    + *
  • Preparing the data to index. + *
  • Preparing search requests. + * + * @author Michael Suzuki + * @author Meenal Bhave + * + */ +public class AbstractSearchTest extends RestTest +{ + + protected static final String SEARCH_DATA_SAMPLE_FOLDER = "FolderSearch"; + protected UserModel userModel, adminUserModel; + protected SiteModel siteModel; + protected UserModel searchedUser; + protected FileModel file, file2, file3, file4; + + protected static String unique_searchString; + + @BeforeClass(alwaysRun = true) + public void dataPreparation() throws Exception + { + adminUserModel = dataUser.getAdminUser(); + userModel = dataUser.createRandomTestUser("UserSearch"); + + siteModel = new SiteModel(RandomData.getRandomName("SiteSearch")); + siteModel.setVisibility(Visibility.PRIVATE); + + siteModel = dataSite.usingUser(userModel).createSite(siteModel); + + unique_searchString = siteModel.getTitle().replace("SiteSearch", "Unique"); + + /* + * Create the following file structure for preconditions : + * |- folder + * |-- pangram.txt + * |-- cars.txt + * |-- alfresco.txt + * |-- + */ + + FolderModel folder = new FolderModel(SEARCH_DATA_SAMPLE_FOLDER); + dataContent.usingUser(userModel).usingSite(siteModel).createFolder(folder); + + //Create files + String title = "Title: " + unique_searchString; + String description = "Description: File is created for search tests by Author: " + unique_searchString + " . "; + + file = new FileModel("pangram.txt", "pangram" + title, description, FileType.TEXT_PLAIN, description + " The quick brown fox jumps over the lazy dog"); + + file2 = new FileModel("cars.txt", "cars" + title, description, FileType.TEXT_PLAIN, "The landrover discovery is not a sports car "); + + file3 = new FileModel("alfresco.txt", "alfresco", "alfresco", FileType.TEXT_PLAIN, "Alfresco text file for search "); + + file4 = new FileModel(unique_searchString + ".txt", "uniquee" + title, description, FileType.TEXT_PLAIN, "Unique text file for search "); + + dataContent.usingUser(userModel).usingSite(siteModel).usingResource(folder).createContent(file); + dataContent.usingUser(userModel).usingSite(siteModel).usingResource(folder).createContent(file2); + dataContent.usingUser(userModel).usingSite(siteModel).usingResource(folder).createContent(file3); + dataContent.usingUser(userModel).usingSite(siteModel).usingResource(folder).createContent(file4); + + waitForMetadataIndexing(file4.getName(), true); + } + + /** + * Helper method which create an http post request to Search API end point. + * @param term String search term + * @return {@link SearchResponse} response. + * @throws Exception if error + * + */ + protected SearchResponse query(String term) throws Exception + { + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setLanguage("afts"); + queryReq.setQuery(term); + SearchRequest query = new SearchRequest(queryReq); + return restClient.authenticateUser(userModel).withSearchAPI().search(query); + } + /** + * Helper method which create an http post request to Search API end point. + * + * @return {@link SearchResponse} response. + * @throws Exception if error + * + */ + protected SearchResponse query(RestRequestQueryModel queryReq, RestRequestHighlightModel highlight) throws Exception + { + SearchRequest query = new SearchRequest(queryReq); + query.setHighlight(highlight); + return restClient.authenticateUser(userModel).withSearchAPI().search(query); + } + /** + * + * Helper method which create an http post request to Search API end point. + * Executes the given search request without throwing checked exceptions (a {@link RuntimeException} will be thrown in case). + * @param query the search request. + * @return {@link SearchResponse} response. + * + */ + protected SearchResponse query(SearchRequest query) + { + try + { + return restClient.authenticateUser(userModel).withSearchAPI().search(query); + } + catch (final Exception exception) + { + throw new RuntimeException(exception); + } + } + + /** + * Executes an SQL Query using "solr" as output format. + * + * @param sql the SQL statement. + */ + protected RestResponse executeSqlAsSolr(String sql) throws Exception + { + SearchSqlRequest sqlRequest = new SearchSqlRequest(); + sqlRequest.setSql(sql); + sqlRequest.setFormat("solr"); + return searchSql(sqlRequest); + } + + protected SearchRequest createQuery(String term) + { + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery(term); + query.setQuery(queryReq); + return query; + } + + protected SearchRequest carsQuery() + { + return createQuery("cars"); + } + + protected RestResponse searchSql(SearchSqlRequest searchSqlRequest) throws Exception + { + return restClient.authenticateUser(userModel).withSearchSqlAPI().searchSql(searchSqlRequest); + } + + /** + * Wait for Solr to finish indexing: Indexing has caught up = true if search returns appropriate results + * + * @param userQuery: search query, this can include the fieldname, unique search string will guarantee accurate results + * @param expectedInResults, true if entry is expected in the results set + * @return true (indexing is finished) if search returns appropriate results + * @throws Exception + */ + public boolean waitForIndexing(String userQuery, boolean expectedInResults) throws Exception + { + // Use the search query as is: fieldname(s) may or may not be specified within the userQuery + return waitForIndexing(null, userQuery, expectedInResults); + } + + /** + * waitForIndexing method that matches / waits for filename, metadata to be indexed. + * @param userQuery + * @param expectedInResults + * @return + * @throws Exception + */ + public boolean waitForMetadataIndexing(String userQuery, boolean expectedInResults) throws Exception + { + return waitForIndexing("name", userQuery, expectedInResults); + } + + /** + * waitForIndexing method that matches / waits for content to be indexed, this can take longer than metadata indexing. + * Since Metadata is indexed first, use this method where tests, queries need content to be indexed too. + * @param userQuery + * @param expectedInResults + * @return + * @throws Exception + */ + public boolean waitForContentIndexing(String userQuery, boolean expectedInResults) throws Exception + { + return waitForIndexing("cm:content", userQuery, expectedInResults); + } + + /** + * Wait for Solr to finish indexing: Indexing has caught up = true if search returns appropriate results + * + * @param fieldName: specific field to search for, e.g. name. When specified, the query will become: name:'userQuery' + * @param userQuery: search string, unique search string will guarantee accurate results + * @param expectedInResults, true if entry is expected in the results set + * @return true (indexing is finished) if search returns appropriate results + * @throws Exception + */ + private boolean waitForIndexing(String fieldName, String userQuery, boolean expectedInResults) throws Exception + { + boolean resultAsExpected = false; + String expectedStatusCode = HttpStatus.OK.toString(); + String query = (fieldName == null)? userQuery: String.format("%s:'%s'", fieldName, userQuery); + + // Repeat search until the query results are as expected or Search Retry count is hit + for (int searchCount = 1; searchCount <= 3; searchCount++) + { + SearchRequest searchRequest = createQuery(query); + SearchResponse response = query(searchRequest); + + if (restClient.getStatusCode().matches(expectedStatusCode)) + { + boolean found = !response.getEntries().isEmpty(); + + // Loop again if result is not as expected: To cater for solr lag: eventual consistency + resultAsExpected = (expectedInResults == found); + if (resultAsExpected) + { + break; + } + else + { + // Wait for the solr indexing. + Utility.waitToLoopTime(properties.getSolrWaitTimeInSeconds(), "Wait For Indexing"); + } + } + else + { + throw new AuthenticationException("API returned status code:" + restClient.getStatusCode() + " Expected: " + expectedStatusCode); + } + } + + return resultAsExpected; + } +} \ No newline at end of file diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetFieldsSearchTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetFieldsSearchTest.java new file mode 100644 index 000000000..85d613a8c --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetFieldsSearchTest.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2019 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import java.util.ArrayList; +import java.util.List; + +import javax.json.Json; +import javax.json.JsonObject; + +import org.alfresco.dataprep.SiteService.Visibility; +import org.alfresco.rest.search.FacetFieldBucket; +import org.alfresco.rest.search.RestRequestFacetFieldModel; +import org.alfresco.rest.search.RestRequestFacetFieldsModel; +import org.alfresco.rest.search.RestRequestQueryModel; +import org.alfresco.rest.search.RestResultBucketsModel; +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.utility.data.RandomData; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FileType; +import org.alfresco.utility.model.FolderModel; +import org.alfresco.utility.model.SiteModel; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.model.UserModel; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Faceted search test with FacetFields + * @author Meenal Bhave + * + */ +public class FacetFieldsSearchTest extends AbstractSearchTest +{ + private UserModel userWithNoAccess, userCanAccessTextFile; + private SiteModel testSite; + private FolderModel testFolder; + private FileModel textFile, htmlFile; + private String fname; + + @BeforeClass(alwaysRun = true) + public void setupEnvironment() throws Exception + { + serverHealth.assertServerIsOnline(); + + fname = unique_searchString + "facet"; + + testSite = new SiteModel(RandomData.getRandomName("SiteSearch")); + testSite.setVisibility(Visibility.PRIVATE); + + testSite = dataSite.usingUser(userModel).createSite(testSite); + + // Create another user who would not have access to the Private Site created by userModel + userWithNoAccess = dataUser.createRandomTestUser("UserSearch2"); + userCanAccessTextFile = dataUser.createRandomTestUser("UserSearch3"); + + // Create a folder and a file as test User + testFolder = new FolderModel(fname); + dataContent.usingUser(userModel).usingSite(testSite).createFolder(testFolder); + + textFile = new FileModel(fname + "-1.txt", fname, fname, FileType.TEXT_PLAIN, fname + " file for search "); + dataContent.usingUser(userModel).usingSite(testSite).createContent(textFile); + + htmlFile = new FileModel(fname + "-2.html", FileType.HTML, fname + " file 2 for search "); + dataContent.usingUser(userModel).usingSite(testSite).createContent(htmlFile); + + // Set Node Permissions to allow access for user for text File + JsonObject userPermission = Json.createObjectBuilder() + .add("permissions", Json.createObjectBuilder().add("isInheritanceEnabled", false) + .add("locallySet",Json.createObjectBuilder().add("authorityId", userCanAccessTextFile.getUsername()) + .add("name", "SiteConsumer").add("accessStatus", "ALLOWED"))) + .build(); + String putBody = userPermission.toString(); + + restClient.authenticateUser(userModel).withCoreAPI().usingNode(textFile).updateNode(putBody); + + // Wait for the file to be indexed + waitForIndexing(htmlFile.getName(), true); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_121 }) + public void testSearchFacetFieldsBucketExcludedWhenMinCount2() throws Exception + { + // Create Query with FacetFields: Site and Content MimeType + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("name:" + fname); + query.setQuery(queryReq); + + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List facets = new ArrayList(); + + facets.add(new RestRequestFacetFieldModel("SITE", "SEARCH.FACET_FIELDS.SITE", 0)); // MinCount = 0 + facets.add(new RestRequestFacetFieldModel("cm:content.mimetype", "Mimetype", 2)); // MinCount = 2 + + facetFields.setFacets(facets); + query.setFacetFields(facetFields); + + // Search query using user who created site + SearchResponse response = query(query); + + // Expect MimeType bucket with size 1 is excluded as minCount = 2 won't be reached + Assert.assertEquals(response.getContext().getFacetsFields().size(), 1); + + // Expect SITE bucket is included and MinCount defaults to 1 + RestResultBucketsModel facetFieldList = response.getContext().getFacetsFields().get(0); + Assert.assertEquals(facetFieldList.getLabel(), "SEARCH.FACET_FIELDS.SITE"); + Assert.assertEquals(facetFieldList.getBuckets().size(), 1); + + FacetFieldBucket bucket1 = facetFieldList.getBuckets().get(0); + bucket1.assertThat().field("label").is(testSite.getId()); + bucket1.assertThat().field("filterQuery").contains(testSite.getId()); + bucket1.assertThat().field("count").is(3); // One folder and 2 files created above + + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_121 }) + public void testSearchWithFacetFieldsMinCountChecks() throws Exception + { + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("name:" + fname); + query.setQuery(queryReq); + + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List facets = new ArrayList(); + + // MinCount not set + facets.add(new RestRequestFacetFieldModel("SITE", "SEARCH.FACET_FIELD1.SITE", null)); // MinCount Not set + facets.add(new RestRequestFacetFieldModel("cm:content.mimetype", "SEARCH.FACET_FIELD2.Mimetype", 1)); // MinCount = 1 + + facetFields.setFacets(facets); + query.setFacetFields(facetFields); + + // Search query using user who created site + SearchResponse response = query(query); + + // Expect 2 Facet buckets to be retrieved: MinCount when not set defaults to 1 + List facetFieldBucketsList = response.getContext().getFacetsFields(); + Assert.assertEquals(facetFieldBucketsList.size(), 2, "FacetField"); + + RestResultBucketsModel facetFieldList = facetFieldBucketsList.get(0); + Assert.assertEquals(facetFieldList.getLabel(), "SEARCH.FACET_FIELD1.SITE"); + + FacetFieldBucket bucket1 = facetFieldList.getBuckets().get(0); + bucket1.assertThat().field("label").is(testSite.getId()); + bucket1.assertThat().field("filterQuery").contains(testSite.getId()); + bucket1.assertThat().field("count").is(3); + + // MimeType bucket will be shown with 2 buckets + facetFieldList = facetFieldBucketsList.get(1); + Assert.assertEquals(facetFieldList.getLabel(), "SEARCH.FACET_FIELD2.Mimetype"); + Assert.assertEquals(facetFieldList.getBuckets().size(), 2); + + bucket1 = facetFieldList.getBuckets().get(0); + bucket1.assertThat().field("label").is("text/html"); + bucket1.assertThat().field("filterQuery").contains("text/html"); + bucket1.assertThat().field("count").is(1); + bucket1.assertThat().field("display").is("HTML"); + + bucket1 = facetFieldList.getBuckets().get(1); + bucket1.assertThat().field("label").is("text/plain"); + bucket1.assertThat().field("filterQuery").contains("text/plain"); + bucket1.assertThat().field("count").is(1); + bucket1.assertThat().field("display").is("Plain Text"); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_121 }) + public void testSearchWithFacetFieldsNoFacetsWhenNoAccess() throws Exception + { + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("name:" + fname); + query.setQuery(queryReq); + + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List facets = new ArrayList(); + + facets.add(new RestRequestFacetFieldModel("SITE", "SEARCH.FACET_FIELD1.SITE", null)); // MinCount Not set + facets.add(new RestRequestFacetFieldModel("cm:content.mimetype", "SEARCH.FACET_FIELD2.Mimetype", 1)); // MinCount = 1 + + facetFields.setFacets(facets); + query.setFacetFields(facetFields); + + // Search query using other user + SearchResponse response = restClient.authenticateUser(userWithNoAccess).withSearchAPI().search(query); + + // User has No access to matching content hence no buckets expected + Assert.assertNull(response.getContext().getFacetsFields()); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_121 }) + public void testSearchWithFacetFieldsOnlyFacetsWhereAccess() throws Exception + { + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("name:" + fname); + query.setQuery(queryReq); + + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List facets = new ArrayList(); + + facets.add(new RestRequestFacetFieldModel("SITE", "SEARCH.FACET_FIELD1.SITE", null)); // MinCount Not set + facets.add(new RestRequestFacetFieldModel("cm:content.mimetype", "SEARCH.FACET_FIELD2.Mimetype", 1)); // MinCount = 1 + + facetFields.setFacets(facets); + query.setFacetFields(facetFields); + + // Search query using user that can access 1 text file + SearchResponse response = restClient.authenticateUser(userCanAccessTextFile).withSearchAPI().search(query); + + List facetFieldBucketsList = response.getContext().getFacetsFields(); + Assert.assertEquals(facetFieldBucketsList.size(), 2); + + // Check FacetField 1 + RestResultBucketsModel facetFieldList = facetFieldBucketsList.get(0); + + // User has granular permissions to content within this private site, so expect the Site bucket + Assert.assertEquals(facetFieldList.getLabel(), "SEARCH.FACET_FIELD1.SITE"); + + FacetFieldBucket bucket1 = facetFieldList.getBuckets().get(0); + bucket1.assertThat().field("label").is(testSite.getId()); + bucket1.assertThat().field("filterQuery").contains(testSite.getId()); + bucket1.assertThat().field("count").is(1); + + // Expect MimeType bucket shows only 1 bucket where user has access to content + facetFieldList = facetFieldBucketsList.get(1); + Assert.assertEquals(facetFieldList.getLabel(), "SEARCH.FACET_FIELD2.Mimetype"); + Assert.assertEquals(facetFieldList.getBuckets().size(), 1); + + // User has access to text file alone, so expect bucket for text/plain and not for html content. + bucket1 = facetFieldList.getBuckets().get(0); + bucket1.assertThat().field("label").is("text/plain"); + bucket1.assertThat().field("label").isNot("text/html"); + bucket1.assertThat().field("filterQuery").contains("text/plain"); + bucket1.assertThat().field("count").is(1); + bucket1.assertThat().field("display").is("Plain Text"); + } +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetIntervalSearchTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetIntervalSearchTest.java new file mode 100644 index 000000000..3c473467c --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetIntervalSearchTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2017 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import org.alfresco.rest.model.RestErrorModel; +import org.alfresco.rest.search.FacetInterval; +import org.alfresco.rest.search.RestGenericBucketModel; +import org.alfresco.rest.search.RestGenericFacetResponseModel; +import org.alfresco.rest.search.RestRequestFacetIntervalsModel; +import org.alfresco.rest.search.RestRequestFacetSetModel; +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.testrail.ExecutionType; +import org.alfresco.utility.testrail.annotation.TestRail; +import org.springframework.http.HttpStatus; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.Arrays; + +/** + * Faceted Intervals Search Test + * @author Gethin James + */ +public class FacetIntervalSearchTest extends AbstractSearchTest +{ + @BeforeClass(alwaysRun = true) + public void setupEnvironment() throws Exception + { + waitForContentIndexing(file4.getContent(), true); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Check facet intervals mandatory fields") + public void checkingFacetsMandatoryErrorMessages()throws Exception + { + SearchRequest query = carsQuery(); + + RestRequestFacetIntervalsModel facetIntervalsModel = new RestRequestFacetIntervalsModel(); + FacetInterval facetInterval = new FacetInterval(null, null, null); + facetIntervalsModel.setIntervals(Arrays.asList(facetInterval)); + query.setFacetIntervals(facetIntervalsModel); + + query(query); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "facetIntervals intervals field")); + facetInterval.setField("created"); + query(query); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary(String.format(RestErrorModel.MANDATORY_COLLECTION, "facetIntervals intervals sets")); + + RestRequestFacetSetModel restFacetSetModel = new RestRequestFacetSetModel(); + restFacetSetModel.setLabel("theRest"); + facetInterval.setSets(Arrays.asList(restFacetSetModel)); + query(query); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "facetIntervals intervals created sets start")); + + restFacetSetModel.setStart("A"); + query(query); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "facetIntervals intervals created sets end")); + + restFacetSetModel.setEnd("B"); + RestRequestFacetSetModel duplicate = new RestRequestFacetSetModel(); + facetInterval.setSets(Arrays.asList(restFacetSetModel,duplicate)); + duplicate.setLabel("theRest"); + duplicate.setStart("A"); + duplicate.setEnd("C"); + + query(query); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary("duplicate set interval label [theRest=2]"); + + facetInterval.setSets(Arrays.asList(restFacetSetModel)); + facetInterval.setLabel("thesame"); + FacetInterval duplicateLabel = new FacetInterval("creator", "thesame", Arrays.asList(duplicate)); + facetIntervalsModel.setIntervals(Arrays.asList(facetInterval, duplicateLabel)); + query.setFacetIntervals(facetIntervalsModel); + + query(query); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary("duplicate interval label [thesame=2]"); + + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Check basic facet intervals search api") + public void searchWithBasicInterval()throws Exception + { + SearchRequest query = carsQuery(); + + RestRequestFacetIntervalsModel facetIntervalsModel = new RestRequestFacetIntervalsModel(); + RestRequestFacetSetModel restRequestFacetSetModel = new RestRequestFacetSetModel(); + restRequestFacetSetModel.setStart("a"); + restRequestFacetSetModel.setEnd("user"); + restRequestFacetSetModel.setLabel("aUser"); + + RestRequestFacetSetModel restFacetSetModel = new RestRequestFacetSetModel(); + restFacetSetModel.setStart("user"); + restFacetSetModel.setEnd("z"); + restFacetSetModel.setStartInclusive(false); + restFacetSetModel.setLabel("theRest"); + + FacetInterval facetInterval = new FacetInterval("creator", null, Arrays.asList(restRequestFacetSetModel, restFacetSetModel)); + facetIntervalsModel.setIntervals(Arrays.asList(facetInterval)); + query.setFacetIntervals(facetIntervalsModel); + + SearchResponse response = query(query); + response.assertThat().entriesListIsNotEmpty(); + response.getContext().assertThat().field("facets").isNotEmpty(); + RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(0); + + RestGenericBucketModel bucket = facetResponseModel.getBuckets().get(0); + Assert.assertEquals(facetResponseModel.getBuckets().size(), 2); + bucket.assertThat().field("label").is("aUser"); + bucket.assertThat().field("filterQuery").is("creator:[\"a\" TO \"user\"]"); + bucket.getMetrics().get(0).assertThat().field("type").is("count"); + bucket.getMetrics().get(0).assertThat().field("value").contains("{count="); + + bucket = facetResponseModel.getBuckets().get(1); + + bucket.assertThat().field("label").is("theRest"); + bucket.assertThat().field("filterQuery").is("creator:<\"user\" TO \"z\"]"); + bucket.getMetrics().get(0).assertThat().field("type").is("count"); + bucket.getMetrics().get(0).assertThat().field("value").is("{count=0}"); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Check date facet intervals search api") + public void searchWithDates() throws Exception + { + SearchRequest query = carsQuery(); + + RestRequestFacetIntervalsModel facetIntervalsModel = new RestRequestFacetIntervalsModel(); + RestRequestFacetSetModel restRequestFacetSetModel = new RestRequestFacetSetModel(); + restRequestFacetSetModel.setStart("*"); + restRequestFacetSetModel.setEnd("2016"); + restRequestFacetSetModel.setEndInclusive(false); + restRequestFacetSetModel.setLabel("Before2016"); + + RestRequestFacetSetModel restFacetSetModel = new RestRequestFacetSetModel(); + restFacetSetModel.setStart("2016"); + restFacetSetModel.setEnd("now"); + restFacetSetModel.setLabel("From2016"); + + FacetInterval facetInterval = new FacetInterval("cm:modified", "modified", Arrays.asList(restRequestFacetSetModel, restFacetSetModel)); + facetIntervalsModel.setIntervals(Arrays.asList(facetInterval)); + query.setFacetIntervals(facetIntervalsModel); + + SearchResponse response = query(query); + response.assertThat().entriesListIsNotEmpty(); + response.getContext().assertThat().field("facets").isNotEmpty(); + RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(0); + + facetResponseModel.assertThat().field("label").is("modified"); + RestGenericBucketModel bucket = facetResponseModel.getBuckets().get(0); + Assert.assertEquals(facetResponseModel.getBuckets().size(), 2); + + bucket.assertThat().field("label").is("From2016"); + bucket.assertThat().field("filterQuery").is("cm:modified:[\"2016\" TO \"now\"]"); + bucket.getMetrics().get(0).assertThat().field("type").is("count"); + bucket.getMetrics().get(0).assertThat().field("value").contains("{count="); + + + bucket = facetResponseModel.getBuckets().get(1); + + bucket.assertThat().field("label").is("Before2016"); + bucket.assertThat().field("filterQuery").is("cm:modified:[\"*\" TO \"2016\">"); + bucket.getMetrics().get(0).assertThat().field("type").is("count"); + bucket.getMetrics().get(0).assertThat().field("value").is("{count=0}"); + } + +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetRangeSearchTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetRangeSearchTest.java new file mode 100644 index 000000000..14a65b0cb --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetRangeSearchTest.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2017 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.testng.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.alfresco.rest.model.RestErrorModel; +import org.alfresco.rest.model.RestRequestRangesModel; +import org.alfresco.rest.search.RestGenericBucketModel; +import org.alfresco.rest.search.RestGenericFacetResponseModel; +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.testrail.ExecutionType; +import org.alfresco.utility.testrail.annotation.TestRail; +import org.springframework.http.HttpStatus; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Faceted Range Search Query for numeric range + * { + * "query": { + * "query": "name:A*" + * }, + * "range": { + * "field": "content.size", + * "start": "0", + * "end": "400", + * "gap": "100" + * } + * } + * Date range query: + * { + * "query": { + * "query": "name:A*" + * }, + * "range": { + * "field": "created", + * "start": "2015-09-29T10:45:15.729Z", + * "end": "2016-09-29T10:45:15.729Z", + * "gap": "+100DAY" + * } + * } + * @author Michael Suzuki + * + */ +public class FacetRangeSearchTest extends AbstractSearchTest +{ + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Check facet intervals mandatory fields") + public void checkingFacetsMandatoryErrorMessages() + { + SearchRequest query = carsQuery(); + List ranges = new ArrayList<>(); + RestRequestRangesModel facetRangeModel = new RestRequestRangesModel(); + ranges.add(facetRangeModel); + query.setRanges(ranges); + query(query); + + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "field")); + ranges.clear(); + facetRangeModel.setField("content.size"); + ranges.add(facetRangeModel); + query.setRanges(ranges); + + query(query); + + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "start")); + facetRangeModel.setStart("0"); + ranges.clear(); + ranges.add(facetRangeModel); + query.setRanges(ranges); + + query(query); + + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "end")); + facetRangeModel.setEnd("400"); + query.setRanges(ranges); + ranges.clear(); + ranges.add(facetRangeModel); + + query(query); + + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "gap")); + + facetRangeModel.setGap("100"); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Check basic facet range search api") + public void searchWithRange() + { + SearchRequest query = createQuery("* AND SITE:'" + siteModel.getId() + "'"); + + RestRequestRangesModel facetRangeModel = new RestRequestRangesModel(); + facetRangeModel.setField("content.size"); + facetRangeModel.setStart("0"); + facetRangeModel.setEnd("200"); + facetRangeModel.setGap("20"); + List ranges = new ArrayList<>(); + ranges.add(facetRangeModel); + query.setRanges(ranges); + SearchResponse response = query(query); + response.assertThat().entriesListIsNotEmpty(); + response.getContext().assertThat().field("facets").isNotEmpty(); + RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(0); + + RestGenericBucketModel bucket = facetResponseModel.getBuckets().get(0); + bucket.assertThat().field("label").is("[20 - 40)"); + bucket.assertThat().field("filterQuery").is("content.size:[\"20\" TO \"40\">"); + Map metric = (Map) bucket.getMetrics().get(0).getValue(); + assertEquals(Integer.valueOf(metric.get("count")).intValue(), 2, "Unexpected count for first bucket."); + Map info = (Map) bucket.getBucketInfo(); + assertEquals(info.get("start"),"20"); + assertEquals(info.get("end"),"40"); + Assert.assertNull(info.get("count")); + assertEquals(info.get("startInclusive"),"true"); + assertEquals(info.get("endInclusive"),"false"); + + bucket = facetResponseModel.getBuckets().get(1); + bucket.assertThat().field("label").is("[40 - 120)"); + bucket.assertThat().field("filterQuery").is("content.size:[\"40\" TO \"120\">"); + metric = (Map) bucket.getMetrics().get(0).getValue(); + assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for second bucket."); + info = (Map) bucket.getBucketInfo(); + assertEquals(info.get("start"),"40"); + assertEquals(info.get("end"),"120"); + assertEquals(info.get("startInclusive"),"true"); + assertEquals(info.get("endInclusive"),"false"); + + bucket = facetResponseModel.getBuckets().get(2); + bucket.assertThat().field("label").is("[120 - 200]"); + bucket.assertThat().field("filterQuery").is("content.size:[\"120\" TO \"200\"]"); + assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for third bucket."); + info = (Map) bucket.getBucketInfo(); + assertEquals(info.get("start"),"120"); + assertEquals(info.get("end"),"200"); + assertEquals(info.get("startInclusive"),"true"); + assertEquals(info.get("endInclusive"),"true"); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Check date facet intervals search api") + public void searchWithRangeHardend() + { + SearchRequest query = createQuery("* AND SITE:'" + siteModel.getId() + "'"); + + RestRequestRangesModel facetRangeModel = new RestRequestRangesModel(); + facetRangeModel.setField("content.size"); + facetRangeModel.setStart("0"); + facetRangeModel.setEnd("200"); + facetRangeModel.setGap("20"); + facetRangeModel.setHardend(true); + List ranges = new ArrayList<>(); + ranges.add(facetRangeModel); + query.setRanges(ranges); + SearchResponse response = query(query); + response.assertThat().entriesListIsNotEmpty(); + response.getContext().assertThat().field("facets").isNotEmpty(); + RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(0); + + RestGenericBucketModel bucket = facetResponseModel.getBuckets().get(0); + bucket.assertThat().field("label").is("[20 - 40)"); + bucket.assertThat().field("filterQuery").is("content.size:[\"20\" TO \"40\">"); + Map metric = (Map) bucket.getMetrics().get(0).getValue(); + assertEquals(Integer.valueOf(metric.get("count")).intValue(), 2, "Unexpected count for first bucket."); + Map info = (Map) bucket.getBucketInfo(); + assertEquals(info.get("start"),"20"); + assertEquals(info.get("end"),"40"); + assertEquals(info.get("startInclusive"),"true"); + assertEquals(info.get("endInclusive"),"false"); + Assert.assertNull(info.get("count")); + + bucket = facetResponseModel.getBuckets().get(1); + bucket.assertThat().field("label").is("[40 - 120)"); + bucket.assertThat().field("filterQuery").is("content.size:[\"40\" TO \"120\">"); + info = (Map) bucket.getBucketInfo(); + assertEquals(info.get("start"),"40"); + assertEquals(info.get("end"),"120"); + metric = (Map) bucket.getMetrics().get(0).getValue(); + assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for second bucket."); + Assert.assertNull(info.get("count")); + assertEquals(info.get("startInclusive"),"true"); + assertEquals(info.get("endInclusive"),"false"); + + bucket = facetResponseModel.getBuckets().get(2); + bucket.assertThat().field("label").is("[120 - 200]"); + bucket.assertThat().field("filterQuery").is("content.size:[\"120\" TO \"200\"]"); + metric = (Map) bucket.getMetrics().get(0).getValue(); + assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for third bucket."); + info = (Map) bucket.getBucketInfo(); + assertEquals(info.get("start"),"120"); + assertEquals(info.get("end"),"200"); + Assert.assertNull(info.get("count")); + assertEquals(info.get("startInclusive"),"true"); + assertEquals(info.get("endInclusive"),"true"); + } + + /** This test relies on a document created in 2015 existing, probably part of the sample site. */ + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_121 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_121 }, executionType = ExecutionType.REGRESSION, + description = "Check date facet intervals search api") + public void searchDateRange() + { + SearchRequest query = createQuery("name:A*"); + + RestRequestRangesModel facetRangeModel = new RestRequestRangesModel(); + facetRangeModel.setField("created"); + facetRangeModel.setStart("2015-09-29T10:45:15.729Z"); + facetRangeModel.setEnd("2016-09-29T10:45:15.729Z"); + facetRangeModel.setGap("+280DAY"); + List ranges = new ArrayList<>(); + ranges.add(facetRangeModel); + query.setRanges(ranges); + SearchResponse response = query(query); + response.assertThat().entriesListIsNotEmpty(); + response.getContext().assertThat().field("facets").isNotEmpty(); + RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(0); + + List buckets = facetResponseModel.getBuckets(); + assertThat(buckets.size(),is(1)); + + RestGenericBucketModel bucket = buckets.get(0); + bucket.assertThat().field("label").is("[2015-09-29T10:45:15.729Z - 2017-04-11T10:45:15.729Z]"); + bucket.assertThat().field("filterQuery").is("created:[\"2015-09-29T10:45:15.729Z\" TO \"2017-04-11T10:45:15.729Z\"]"); + bucket.getMetrics().get(0).assertThat().field("value").is("{count=1}"); + Map info = (Map) bucket.getBucketInfo(); + assertEquals(info.get("start"),"2015-09-29T10:45:15.729Z"); + assertEquals(info.get("end"),"2017-04-11T10:45:15.729Z"); + Assert.assertNull(info.get("count"),"1"); + assertEquals(info.get("startInclusive"),"true"); + assertEquals(info.get("endInclusive"),"true"); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Check date facet intervals search api") + public void searchDateAndSizeRanges() + { + SearchRequest query = createQuery("* AND SITE:'" + siteModel.getId() + "'"); + List ranges = new ArrayList<>(); + RestRequestRangesModel facetRangeModel = new RestRequestRangesModel(); + facetRangeModel.setField("created"); + facetRangeModel.setStart("2015-09-29T10:45:15.729Z"); + facetRangeModel.setEnd("2016-09-29T10:45:15.729Z"); + facetRangeModel.setGap("+280DAY"); + ranges.add(facetRangeModel); + RestRequestRangesModel facetCountRangeModel = new RestRequestRangesModel(); + facetCountRangeModel.setField("content.size"); + facetCountRangeModel.setStart("0"); + facetCountRangeModel.setEnd("500"); + facetCountRangeModel.setGap("200"); + ranges.add(facetCountRangeModel); + query.setRanges(ranges); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Check basic facet range search api") + public void searchWithRangeAndIncludeUpperBound() + { + SearchRequest query = createQuery("* AND SITE:'" + siteModel.getId() + "'"); + + RestRequestRangesModel facetRangeModel = new RestRequestRangesModel(); + facetRangeModel.setField("content.size"); + facetRangeModel.setStart("0"); + facetRangeModel.setEnd("200"); + facetRangeModel.setGap("20"); + List include = new ArrayList<>(); + include.add("upper"); + facetRangeModel.setInclude(include); + List ranges = new ArrayList<>(); + ranges.add(facetRangeModel); + query.setRanges(ranges); + SearchResponse response = query(query); + response.assertThat().entriesListIsNotEmpty(); + response.getContext().assertThat().field("facets").isNotEmpty(); + RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(0); + + RestGenericBucketModel bucket = facetResponseModel.getBuckets().get(0); + bucket.assertThat().field("label").is("(20 - 40]"); + bucket.assertThat().field("filterQuery").is("content.size:<\"20\" TO \"40\"]"); + Map metric = (Map) bucket.getMetrics().get(0).getValue(); + assertEquals(Integer.valueOf(metric.get("count")).intValue(), 2, "Unexpected count for first bucket."); + Map info = (Map) bucket.getBucketInfo(); + assertEquals(info.get("start"),"20"); + assertEquals(info.get("end"),"40"); + Assert.assertNull(info.get("count")); + assertEquals(info.get("startInclusive"),"false"); + assertEquals(info.get("endInclusive"),"true"); + + bucket = facetResponseModel.getBuckets().get(1); + bucket.assertThat().field("label").is("(40 - 120]"); + bucket.assertThat().field("filterQuery").is("content.size:<\"40\" TO \"120\"]"); + metric = (Map) bucket.getMetrics().get(0).getValue(); + assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for second bucket."); + info = (Map) bucket.getBucketInfo(); + assertEquals(info.get("start"),"40"); + assertEquals(info.get("end"),"120"); + assertEquals(info.get("startInclusive"),"false"); + assertEquals(info.get("endInclusive"),"true"); + + bucket = facetResponseModel.getBuckets().get(2); + bucket.assertThat().field("label").is("(120 - 200]"); + bucket.assertThat().field("filterQuery").is("content.size:<\"120\" TO \"200\"]"); + metric = (Map) bucket.getMetrics().get(0).getValue(); + assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for third bucket."); + info = (Map) bucket.getBucketInfo(); + assertEquals(info.get("start"),"120"); + assertEquals(info.get("end"),"200"); + assertEquals(info.get("startInclusive"),"false"); + assertEquals(info.get("endInclusive"),"true"); + } +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetedSearchTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetedSearchTest.java new file mode 100644 index 000000000..3d51cfccd --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FacetedSearchTest.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2018 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.rest.search.FacetFieldBucket; +import org.alfresco.rest.search.FacetQuery; +import org.alfresco.rest.search.RestGenericBucketModel; +import org.alfresco.rest.search.RestGenericFacetResponseModel; +import org.alfresco.rest.search.RestRequestFacetFieldModel; +import org.alfresco.rest.search.RestRequestFacetFieldsModel; +import org.alfresco.rest.search.RestRequestQueryModel; +import org.alfresco.rest.search.RestResultBucketsModel; +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.testrail.ExecutionType; +import org.alfresco.utility.testrail.annotation.TestRail; +import org.testng.Assert; +import org.testng.TestException; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Faceted search test. + * @author Michael Suzuki + * + */ +public class FacetedSearchTest extends AbstractSearchTest +{ + + /** + * Perform the below facet query. + * { + * "query": { + * "query": "cars", + * "language": "afts" + * }, + * "facetQueries": [ + * {"query": "content.size:[o TO 102400]", "label": "small"}, + * {"query": "content.size:[102400 TO 1048576]", "label": "medium"}, + * {"query": "content.size:[1048576 TO 16777216]", "label": "large"} + * ], + * "facetFields": {"facets": [{"field": "'content.size'"}]} + * } + * + * Expected response + * {"list": { + * "entries": [... All the results], + * "pagination": { + * "maxItems": 100, + * "hasMoreItems": false, + * "totalItems": 61, + * "count": 61, + * "skipCount": 0 + * }, + * "facetsFields": [ + * { + * "type": "query", + * "label": "foo", + * "buckets": [ + * { + * "label": "small", + * "filterQuery": "content.size:[0 TO 102400]", + * "display": 1 + * }, + * { + * "label": "large", + * "filterQuery": "content.size:[1048576 TO 16777216]", + * "metrics": [ + * { + * "type": "count", + * "value": { + * "count": 0 + * } + * } + * ] + * }, + * }} + * @throws Exception + */ + + @BeforeClass(alwaysRun = true) + public void setupEnvironment() throws Exception + { + waitForContentIndexing(file4.getContent(), true); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = { TestGroup.REST_API, TestGroup.SEARCH, + TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, description = "Checks facet queries for the Search api") + public void searchWithQueryFaceting() throws Exception + { + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("cars"); + query.setQuery(queryReq); + + List facets = new ArrayList(); + facets.add(new FacetQuery("content.size:[0 TO 102400]", "small")); + facets.add(new FacetQuery("content.size:[102400 TO 1048576]", "medium")); + facets.add(new FacetQuery("content.size:[1048576 TO 16777216]", "large")); + query.setFacetQueries(facets); + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List list = new ArrayList<>(); + list.add(new RestRequestFacetFieldModel("'content.size'")); + facetFields.setFacets(list); + query.setFacetFields(facetFields); + query.setIncludeRequest(true); + + SearchResponse response = query(query); + + response.assertThat().entriesListIsNotEmpty(); + response.getContext().assertThat().field("facetQueries").isNotEmpty(); + + FacetFieldBucket facet = response.getContext().getFacetQueries().get(0); + facet.assertThat().field("label").contains("small").and().field("count").isGreaterThan(0); + facet.assertThat().field("label").contains("small").and().field("filterQuery").is("content.size:[0 TO 102400]"); + response.getContext().getFacetQueries().get(1).assertThat().field("label").contains("large") + .and().field("count").isLessThan(1) + .and().field("filterQuery").is("content.size:[1048576 TO 16777216]"); + response.getContext().getFacetQueries().get(2).assertThat().field("label").contains("medium") + .and().field("count").isLessThan(1) + .and().field("filterQuery").is("content.size:[102400 TO 1048576]"); + //We don't expect to see the FacetFields if group is being used. + Assert.assertNull(response.getContext().getFacetsFields()); + Assert.assertNull(response.getContext().getFacets()); + } + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH , TestGroup.ASS_1}) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH ,TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Checks facet queries for the Search api") + /** + * * Perform a group by faceting, below test groups the facet by group name foo. + * { + * "query": { + * "query": "cars", + * "language": "afts" + * }, + * "facetQueries": [ + * {"query": "content.size:[o TO 102400]", "label": "small","group":"foo"}, + * {"query": "content.size:[102400 TO 1048576]", "label": "medium","group":"foo"}, + * {"query": "content.size:[1048576 TO 16777216]", "label": "large","group":"foo"} + * ], + * "facetFields": {"facets": [{"field": "'content.size'"}]} + * } + * + * Expected response + * {"list": { + * "entries": [... All the results], + * "pagination": { + * "maxItems": 100, + * "hasMoreItems": false, + * "totalItems": 61, + * "count": 61, + * "skipCount": 0 + * }, + * "context": { + * "consistency": {"lastTxId": 512}, + * //Added below as part of SEARCH-374 + * "facets": [ + * { "label": "foo", + * "buckets": [ + * { "label": "small", "count": 61, "filterQuery": "content.size:[o TO 102400]"}, + * { "label": "large", "count": 0, "filterQuery": "content.size:[1048576 TO 16777216]"}, + * { "label": "medium", "count": 61, "filterQuery": "content.size:[102400 TO 1048576]"} + * ] + * } + * } + * }} + * + * + * @throws Exception + */ + public void searchQueryFacetingWithGroup() throws Exception + { + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("cars"); + query.setQuery(queryReq); + + List facets = new ArrayList(); + facets.add(new FacetQuery("content.size:[0 TO 102400]", "small", "foo")); + facets.add(new FacetQuery("content.size:[102400 TO 1048576]", "medium", "foo")); + facets.add(new FacetQuery("content.size:[1048576 TO 16777216]", "large", "foo")); + query.setFacetQueries(facets); + + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List list = new ArrayList<>(); + list.add(new RestRequestFacetFieldModel("'content.size'")); + facetFields.setFacets(list); + query.setFacetFields(facetFields); + + SearchResponse response = query(query); + + // We don't expect to see the FacetQueries if group is being used. + Assert.assertTrue(response.getContext().getFacetQueries() == null); + // Validate the facet field structure is correct. + Assert.assertFalse(response.getContext().getFacets().isEmpty()); + Assert.assertEquals(response.getContext().getFacets().get(0).getLabel(), "foo"); + + RestGenericBucketModel bucket = response.getContext().getFacets().get(0).getBuckets().get(0); + bucket.assertThat().field("label").isNotEmpty(); + Assert.assertEquals(bucket.getMetrics().get(0).getType(), "count"); + Assert.assertNotEquals(bucket.getMetrics().get(0).getValue(), ""); + bucket.assertThat().field("filterQuery").isNotEmpty(); + response.getContext().getFacets().get(0).getBuckets().forEach(action -> { + switch (action.getLabel()) + { + case "small": + Assert.assertEquals(action.getFilterQuery(), "content.size:[0 TO 102400]"); + break; + case "medium": + Assert.assertEquals(action.getFilterQuery(), "content.size:[102400 TO 1048576]"); + break; + case "large": + Assert.assertEquals(action.getFilterQuery(), "content.size:[1048576 TO 16777216]"); + break; + + default: + throw new TestException("Unexpected value returned"); + } + }); + + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH , TestGroup.ASS_1}) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH ,TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Checks facet queries for the Search api") + /** + * { + * "query": { + * "query": "*" + * }, + * "facetFields": { + * "facets": [{"field": "cm:mimetype"},{"field": "modifier"}] + * } + * } + */ + public void searchWithFactedFields() throws Exception + { + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery(unique_searchString); + query.setQuery(queryReq); + + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List facets = new ArrayList(); + facets.add(new RestRequestFacetFieldModel("cm:mimetype")); + facets.add(new RestRequestFacetFieldModel("modifier")); + facetFields.setFacets(facets); + query.setFacetFields(facetFields); + + SearchResponse response = query(query); + + Assert.assertFalse(response.getContext().getFacetsFields().isEmpty()); + Assert.assertNull(response.getContext().getFacetQueries()); + Assert.assertNull(response.getContext().getFacets()); + + RestResultBucketsModel model = response.getContext().getFacetsFields().get(0); + Assert.assertEquals(model.getLabel(), "modifier"); + + model.assertThat().field("label").is("modifier"); + FacetFieldBucket bucket1 = model.getBuckets().get(0); + bucket1.assertThat().field("label").is(userModel.getUsername()); + bucket1.assertThat().field("display").is(userModel.getUsername() + " FirstName LN-" + userModel.getUsername()); + bucket1.assertThat().field("filterQuery").is("modifier:\"" + userModel.getUsername() + "\""); + bucket1.assertThat().field("count").is(1); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH , TestGroup.ASS_1}) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH ,TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Checks facet queries for the Search api") + /** + * Test that items returned are in the format of generic facets. + * { + * "query": { + * "query": "*" + * }, + * "facetFields": { + * "facets": [{"field": "cm:mimetype"},{"field": "modifier"}] + * }, + * "facetFormat":"V2" + * } + */ + public void searchWithFactedFieldsFacetFormatV2() throws Exception + { + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery(unique_searchString); + query.setQuery(queryReq); + query.setFacetFormat("V2"); + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List facets = new ArrayList(); + facets.add(new RestRequestFacetFieldModel("cm:mimetype")); + facets.add(new RestRequestFacetFieldModel("modifier")); + facetFields.setFacets(facets); + query.setFacetFields(facetFields); + + SearchResponse response = query(query); + + Assert.assertNull(response.getContext().getFacetsFields()); + Assert.assertNull(response.getContext().getFacetQueries()); + Assert.assertFalse(response.getContext().getFacets().isEmpty()); + RestGenericFacetResponseModel model = response.getContext().getFacets().get(0); + Assert.assertEquals(model.getLabel(), "modifier"); + + model.assertThat().field("label").is("modifier"); + RestGenericBucketModel bucket1 = model.getBuckets().get(0); + bucket1.assertThat().field("label").is(userModel.getUsername()); + bucket1.assertThat().field("display").is(userModel.getUsername() + " FirstName LN-" + userModel.getUsername()); + bucket1.assertThat().field("filterQuery").is("modifier:\"" + userModel.getUsername() + "\""); + bucket1.assertThat().field("metrics").is("[{entry=null, type=count, value={count=1}}]"); + } +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FingerPrintTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FingerPrintTest.java new file mode 100644 index 000000000..6cad2b28f --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/FingerPrintTest.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2018 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import org.alfresco.rest.search.SearchNodeModel; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FileType; +import org.alfresco.utility.model.FolderModel; +import org.alfresco.utility.model.TestGroup; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Search end point Public API test with finger print. + * @author Michael Suzuki + * + */ +public class FingerPrintTest extends AbstractSearchTest +{ + private FileModel file1, file2, file3, file4; + + @BeforeClass(alwaysRun = true) + public void indexSimilarFile() throws Exception + { + + /* + * Create the following file structure in the same Site : In addition to the preconditions created in dataPreparation + * |- folder + * |-- pangram-banana.txt + * |-- pangram-taco.txt + * |-- pangram-cat.txt + * |-- dog.txt + */ + + FolderModel folder = new FolderModel("The quick brown fox jumps over"); + dataContent.usingUser(userModel).usingSite(siteModel).createFolder(folder); + + file1 = new FileModel("pangram-banana.txt", FileType.TEXT_PLAIN, "The quick brown fox jumps over the lazy banana"); + file2 = new FileModel("pangram-taco.txt", FileType.TEXT_PLAIN, "The quick brown fox jumps over the lazy dog that ate the taco"); + file3 = new FileModel("pangram-cat.txt", FileType.TEXT_PLAIN, "The quick brown fox jumps over the lazy cat"); + file4 = new FileModel("dog.txt", FileType.TEXT_PLAIN, "The quick brown fox ate the lazy dog"); + + dataContent.usingUser(userModel).usingSite(siteModel).usingResource(folder).createContent(file1); + dataContent.usingUser(userModel).usingSite(siteModel).usingResource(folder).createContent(file2); + dataContent.usingUser(userModel).usingSite(siteModel).usingResource(folder).createContent(file3); + dataContent.usingUser(userModel).usingSite(siteModel).usingResource(folder).createContent(file4); + + waitForContentIndexing(file4.getContent(), true); + // Additional wait implemented to remove inconsistent failures. Ref: Search-1438 for details + waitForIndexing("FINGERPRINT:" + file2.getNodeRefWithoutVersion(), true); + waitForIndexing("FINGERPRINT:" + file3.getNodeRefWithoutVersion(), true); + } + + /** + * Search similar document based on document finger print. + * The data prep should have loaded 2 files which one is similar + * to the files loaded as part of this test. + * Note that for fingerprint to work it need a 5 word sequence. + * + * @throws Exception + */ + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + public void search() throws Exception + { + String uuid = file1.getNodeRefWithoutVersion(); + Assert.assertNotNull(uuid); + String fingerprint = String.format("FINGERPRINT:%s", uuid); + SearchResponse response = query(fingerprint); + int count = response.getEntries().size(); + Assert.assertTrue(count > 1); + for (SearchNodeModel m : response.getEntries()) + { + String match = m.getModel().getName(); + switch (match) + { + case "pangram.txt": + break; + case "pangram-banana.txt": + break; + case "pangram-taco.txt": + break; + case "pangram-cat.txt": + break; + default: + throw new AssertionError("Not a match to an expected file: " + m.getModel().getName()); + } + m.getModel().assertThat().field("name").isNot("dog.txt"); + m.getModel().assertThat().field("name").isNot("cars.txt"); + } + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + public void searchSimilar() throws Exception + { + String uuid = file2.getNodeRefWithoutVersion(); + Assert.assertNotNull(uuid); + + // In the response entity there is a score of each doc, change below threshold to bring more like or less. + String fingerprint = String.format("FINGERPRINT:%s_68", uuid); + SearchResponse response = query(fingerprint); + + int count = response.getEntries().size(); + Assert.assertTrue(count > 1); + + for (SearchNodeModel m : response.getEntries()) + { + switch (m.getModel().getName()) + { + case "pangram.txt": + break; + case "pangram-taco.txt": + break; + default: + throw new AssertionError("Not a match to an expected file: " + m.getModel().getName()); + } + m.getModel().assertThat().field("name").isNot("pangram-banana.txt"); + m.getModel().assertThat().field("name").isNot("pangram-cat.txt"); + m.getModel().assertThat().field("name").isNot("dog.txt"); + m.getModel().assertThat().field("name").isNot("cars.txt"); + } + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + public void searchSimilar67Percent() throws Exception + { + String uuid = file2.getNodeRefWithoutVersion(); + Assert.assertNotNull(uuid); + String fingerprint = String.format("FINGERPRINT:%s_68", uuid); + SearchResponse response = query(fingerprint); + int count = response.getEntries().size(); + Assert.assertTrue(count > 1); + for (SearchNodeModel m : response.getEntries()) + { + switch (m.getModel().getName()) + { + case "pangram.txt": + break; + case "pangram-taco.txt": + break; + case "pangram-cat.txt": + break; + default: + throw new AssertionError("Not a match to an expected file: " + m.getModel().getName()); + } + m.getModel().assertThat().field("name").isNot("pangram-banana.txt"); + m.getModel().assertThat().field("name").isNot("dog.txt"); + m.getModel().assertThat().field("name").isNot("cars.txt"); + } + } +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/PivotFacetedSearchTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/PivotFacetedSearchTest.java new file mode 100644 index 000000000..4a438b20b --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/PivotFacetedSearchTest.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2017 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.alfresco.rest.model.RestErrorModel; +import org.alfresco.rest.model.RestRequestRangesModel; +import org.alfresco.rest.search.Pagination; +import org.alfresco.rest.search.RestGenericBucketModel; +import org.alfresco.rest.search.RestGenericFacetResponseModel; +import org.alfresco.rest.search.RestRequestFacetFieldModel; +import org.alfresco.rest.search.RestRequestFacetFieldsModel; +import org.alfresco.rest.search.RestRequestPivotModel; +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.testrail.ExecutionType; +import org.alfresco.utility.testrail.annotation.TestRail; +import org.springframework.http.HttpStatus; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Faceted search test. + * @author Gethin James + * + */ +public class PivotFacetedSearchTest extends AbstractSearchTest +{ + @BeforeClass(alwaysRun = true) + public void setupEnvironment() throws Exception + { + waitForContentIndexing(file4.getContent(), true); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, + executionType = ExecutionType.REGRESSION, + description = "Checks errors with pivot using Search api") + public void searchWithPivotingErrors() throws Exception + { + SearchRequest query = carsQuery(); + + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List list = new ArrayList<>(); + list.add(new RestRequestFacetFieldModel("'creator'")); + facetFields.setFacets(list); + query.setFacetFields(facetFields); + query.setIncludeRequest(false); + List pivotModelList = new ArrayList<>(); + RestRequestPivotModel pivots = new RestRequestPivotModel(); + pivotModelList.add(pivots); + query.setPivots(pivotModelList); + + query(query); + + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "pivot key")); + + pivots.setKey("none_like_this"); + + query(query); + + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST) + .assertLastError().containsSummary("invalid argument was received") + .containsSummary("Pivot parameter none_like_this does not reference"); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, + executionType = ExecutionType.REGRESSION, + description = "Checks with pivot using Search api") + public void searchWithPivoting() throws Exception + { + SearchRequest query = carsQuery(); + + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List list = new ArrayList<>(); + list.add(new RestRequestFacetFieldModel("creator")); + facetFields.setFacets(list); + query.setFacetFields(facetFields); + query.setIncludeRequest(false); + + SearchResponse response = query(query); + response.getContext().assertThat().field("facetsFields").isNotNull(); + + List pivotModelList = new ArrayList<>(); + RestRequestPivotModel pivots = new RestRequestPivotModel(); + pivots.setKey("creator"); + pivotModelList.add(pivots); + query.setPivots(pivotModelList); + response = query(query); + + //Pivot key has matched facet field so there is no longer a facet fields response + assertPivotResponse(response, "creator", null); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, + executionType = ExecutionType.REGRESSION, + description = "Checks nested pivot using Search api") + public void searchWithNestedPivoting() throws Exception + { + SearchRequest query = carsQuery(); + + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List list = new ArrayList<>(); + RestRequestFacetFieldModel restRequestFacetFieldModel = new RestRequestFacetFieldModel("SITE"); + restRequestFacetFieldModel.setLabel("site"); + list.add(restRequestFacetFieldModel); + list.add(new RestRequestFacetFieldModel("creator")); + list.add(new RestRequestFacetFieldModel("modifier")); + facetFields.setFacets(list); + query.setFacetFields(facetFields); + query.setIncludeRequest(false); + + List pivotModelList = new ArrayList<>(); + RestRequestPivotModel sitepivots = new RestRequestPivotModel(); + sitepivots.setKey("site"); + RestRequestPivotModel creatorpivot = new RestRequestPivotModel(); + creatorpivot.setKey("creator"); + RestRequestPivotModel modifierpivot = new RestRequestPivotModel(); + modifierpivot.setKey("modifier"); + sitepivots.setPivots(Arrays.asList(creatorpivot, modifierpivot)); + pivotModelList.add(sitepivots); + query.setPivots(pivotModelList); + + SearchResponse response = query(query); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST) + .assertLastError().containsSummary("invalid argument was received") + .containsSummary("Currently only 1 nested pivot is supported, you have 2"); + + pivotModelList = new ArrayList<>(); + sitepivots = new RestRequestPivotModel(); + sitepivots.setKey("site"); + sitepivots.setPivots(Arrays.asList(modifierpivot)); + pivotModelList.add(sitepivots); + pivotModelList.add(creatorpivot); + query.setPivots(pivotModelList); + + response = query(query); + assertPivotResponse(response, "SITE", "site"); + + RestGenericFacetResponseModel siteResponse = response.getContext().getFacets().get(0); + RestGenericBucketModel bucket = siteResponse.getBuckets().get(0); + RestGenericFacetResponseModel modifiedResponse = bucket.getFacets().get(0); + modifiedResponse.assertThat().field("type").is("pivot"); + modifiedResponse.assertThat().field("label").is("modifier"); + + RestGenericFacetResponseModel creatorResponse = response.getContext().getFacets().get(1); + creatorResponse.assertThat().field("type").is("pivot"); + creatorResponse.assertThat().field("label").is("creator"); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, + executionType = ExecutionType.REGRESSION, + description = "Checks range pivots using Search api") + public void searchWithRangePivoting() throws Exception + { + SearchRequest query = carsQuery(); + + String endDate = LocalDateTime.now() + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")); //the car document is created at runtime, so to include it in range facets end date must be now + + Pagination pagination = new Pagination(); + pagination.setMaxItems(2); + query.setPaging(pagination); + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List list = new ArrayList<>(); + list.add(new RestRequestFacetFieldModel("creator")); + facetFields.setFacets(list); + query.setFacetFields(facetFields); + query.setIncludeRequest(false); + + RestRequestRangesModel facetRangeModel = new RestRequestRangesModel(); + facetRangeModel.setField("created"); + facetRangeModel.setStart("2015-09-29T10:45:15.729Z"); + facetRangeModel.setEnd(endDate); + facetRangeModel.setGap("+280DAY"); + facetRangeModel.setLabel("aRange"); + List ranges = new ArrayList(); + ranges.add(facetRangeModel); + query.setRanges(ranges); + + List pivotModelList = new ArrayList<>(); + RestRequestPivotModel creatorpivot = new RestRequestPivotModel(); + creatorpivot.setKey("creator"); + RestRequestPivotModel rangepivot = new RestRequestPivotModel(); + rangepivot.setKey("aRange"); + creatorpivot.setPivots(Arrays.asList(rangepivot)); + pivotModelList.add(creatorpivot); + query.setPivots(pivotModelList); + + SearchResponse response = query(query); + RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(1); + facetResponseModel.assertThat().field("type").is("pivot"); + facetResponseModel.assertThat().field("label").is("creator"); + RestGenericBucketModel bucket = facetResponseModel.getBuckets().get(0); + RestGenericFacetResponseModel rangeResponse = bucket.getFacets().get(0); + rangeResponse.assertThat().field("type").is("range"); + } + + private void assertPivotResponse(SearchResponse response, String field, String alabel) throws Exception + { + String label = alabel!=null?alabel:field; + response.getContext().assertThat().field("facetsFields").isNull(); + response.getContext().assertThat().field("facets").isNotEmpty(); + RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(0); + facetResponseModel.assertThat().field("type").is("pivot"); + facetResponseModel.assertThat().field("label").is(label); + RestGenericBucketModel bucket = facetResponseModel.getBuckets().get(0); + bucket.assertThat().field("label").isNotEmpty(); + bucket.assertThat().field("filterQuery").is(field+":\""+bucket.getLabel()+"\""); + Assert.assertEquals("count", bucket.getMetrics().get(0).getType()); + Assert.assertTrue(bucket.getMetrics().get(0).getValue().toString().contains("{count=")); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, + executionType = ExecutionType.REGRESSION, + description = "Checks with pivot using Search api and a label as a key") + public void searchWithPivotingUsingLabel() throws Exception + { + SearchRequest query = carsQuery(); + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List list = new ArrayList<>(); + RestRequestFacetFieldModel creatorFacetFieldModel = new RestRequestFacetFieldModel("creator"); + creatorFacetFieldModel.setLabel("create"); + list.add(creatorFacetFieldModel); + RestRequestFacetFieldModel restRequestFacetFieldModel = new RestRequestFacetFieldModel("modifier"); + restRequestFacetFieldModel.setLabel("aLabel"); + list.add(restRequestFacetFieldModel); + facetFields.setFacets(list); + query.setFacetFields(facetFields); + query.setIncludeRequest(false); + RestRequestPivotModel pivots = new RestRequestPivotModel(); + pivots.setKey("create"); + RestRequestPivotModel pivotmod = new RestRequestPivotModel(); + pivotmod.setKey("aLabel"); + + List pivotModelList = new ArrayList<>(); + pivotModelList.add(pivots); + pivotModelList.add(pivotmod); + query.setPivots(pivotModelList); + SearchResponse response = query(query); + assertPivotResponse(response, "creator", "create"); + + //Now check the nesting + RestGenericFacetResponseModel labelResponseModel = response.getContext().getFacets().get(1); + labelResponseModel.assertThat().field("type").is("pivot"); + labelResponseModel.assertThat().field("label").isNotEmpty(); + labelResponseModel.assertThat().field("label").is("aLabel"); + RestGenericBucketModel bucket = labelResponseModel.getBuckets().get(0); + bucket.assertThat().field("filterQuery").is("modifier:\""+bucket.getLabel()+"\""); + Assert.assertEquals("count", bucket.getMetrics().get(0).getType()); + Assert.assertTrue(bucket.getMetrics().get(0).getValue().toString().contains("{count=")); + } +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchAPATHTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchAPATHTest.java new file mode 100644 index 000000000..dbf6556cf --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchAPATHTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2018 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + + +import java.util.Collections; +import java.util.List; + +import org.alfresco.rest.search.FacetFieldBucket; +import org.alfresco.rest.search.RestRequestFacetFieldModel; +import org.alfresco.rest.search.RestRequestFacetFieldsModel; +import org.alfresco.rest.search.RestRequestQueryModel; +import org.alfresco.rest.search.RestResultBucketsModel; +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.utility.model.TestGroup; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Tests the search functionality using an ancestor path. + * Using the category as an example, it is a node that is located + * in root/rootCategory/classifiable. We now provide the ability to search by path + * so that we can return all the child elements of our target path. + * A search on root/rootCategory/classifiable should return Regions, Languages + * as they are the child elements of the given path. + * + * @author Michael Suzuki + * + */ +public class SearchAPATHTest extends AbstractSearchTest +{ + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1}) + /** + * { + * "query": { + * "query": "name:*" + * }, + * "facetFields": { + * "facets": [ + * {"field": "APATH", "prefix": "0" } + * ] + * } + * } + * Expected result + * entries[], + * "pagination": { + * "maxItems": 100, + * "hasMoreItems": true, + * "totalItems": 914, + * "count": 100, + * "skipCount": 0 + * }, + * "context": { + * "facetsFields": [{ + * "buckets": [ + * { + * "count": 913, + * "label": "0/5c09534f-3ca2-4272-bc25-064a7c1762b4" + * }, + * { + * "count": 2, + * "label": "0/" + * } + * ], + * "label": "APATH" + * }], + * "consistency": {"lastTxId": 89} + * } + *}} + * + */ + public void searchLevel0() throws Exception + { + SearchRequest searchQuery = searchRequestWithAPATHFacet("name:*", "0"); + SearchResponse response = query(searchQuery); + + List buckets = getBuckets(response); + Assert.assertEquals(2, buckets.size()); + + buckets.forEach(bucket -> bucket.assertThat().field("label").contains("0/")); + } + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1}) + public void searchLevel0andIncludeSubLevel1() throws Exception + { + SearchRequest searchQuery = searchRequestWithAPATHFacet("name:*", "1/"); + SearchResponse response = query(searchQuery); + + List buckets = getBuckets(response); + Assert.assertEquals(4, buckets.size()); + + getFirstBucket(response).assertThat().field("label").contains("1/"); + } + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1}) + public void searchLevel2() throws Exception + { + String queryString = "name:cars"; + + SearchRequest l1Request = searchRequestWithAPATHFacet(queryString, "1/"); + SearchResponse l1Response = query(l1Request); + + String l2Prefix = getFirstBucket(l1Response).getLabel().replaceFirst("^1/", "2/"); + + SearchRequest l2Request = searchRequestWithAPATHFacet(queryString, l2Prefix); + SearchResponse l2Response = query(l2Request); + + FacetFieldBucket bucket = getFirstBucket(l2Response); + bucket.assertThat().field("label").contains(l2Prefix); + + String l3Prefix = bucket.getLabel().replaceFirst("^2/", "3/"); + + SearchRequest l3Request = searchRequestWithAPATHFacet(queryString, l3Prefix); + SearchResponse l3response = query(l3Request); + + List buckets = getBuckets(l3response); + Assert.assertEquals(1, buckets.size()); + + getFirstBucket(l3response).assertThat().field("label").contains(l3Prefix); + } + + /** + * Creates a new {@link SearchRequest} for this test case. + * + * @param queryString the query string. + * @param facetPrefix the facet prefix. + * @return a new {@link SearchRequest} for this test case. + */ + private SearchRequest searchRequestWithAPATHFacet(String queryString, String facetPrefix) + { + SearchRequest searchRequest = new SearchRequest(); + + RestRequestQueryModel query = new RestRequestQueryModel(); + query.setQuery(queryString); + searchRequest.setQuery(query); + + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + facetFields.setFacets(Collections.singletonList(new RestRequestFacetFieldModel("APATH", facetPrefix))); + searchRequest.setFacetFields(facetFields); + + return searchRequest; + } + + /** + * Extracts the first bucket from the given response. + * + * @param response the results of a query execution. + * @return the first bucket included in the search response. + */ + private FacetFieldBucket getFirstBucket(SearchResponse response) + { + return getBuckets(response).iterator().next(); + } + + /** + * Extracts the buckets from the given response. + * The method also makes sure the buckets list is not empty in the input response. + * + * @param response the results of a query execution. + * @return the getBuckets included in the search response. + */ + private List getBuckets(SearchResponse response) + { + List facetFields = response.getContext().getFacetsFields(); + Assert.assertNotNull(facetFields); + Assert.assertFalse(facetFields.isEmpty()); + + List buckets = facetFields.iterator().next().getBuckets(); + Assert.assertNotNull(buckets); + Assert.assertFalse(buckets.isEmpty()); + + return buckets; + } +} \ No newline at end of file diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchHighLightTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchHighLightTest.java new file mode 100644 index 000000000..83907d43e --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchHighLightTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.rest.search.ResponseHighLightModel; +import org.alfresco.rest.search.RestRequestFieldsModel; +import org.alfresco.rest.search.RestRequestHighlightModel; +import org.alfresco.rest.search.RestRequestQueryModel; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.report.Bug; +import org.testng.annotations.Test; + +/** + * Search high lighting test. + * @author Michael Suzuki + * + */ +public class SearchHighLightTest extends AbstractSearchTest +{ + @Test(groups={TestGroup.SEARCH,TestGroup.REST_API}) + @Bug(id = "TAS-3220") + public void searchWithHighLight() throws Exception + { + waitForContentIndexing(file2.getContent(), true); + + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("cm:content:cars"); + queryReq.setUserQuery("cars"); + + RestRequestHighlightModel highlight = new RestRequestHighlightModel(); + highlight.setPrefix("¿"); + highlight.setPostfix("?"); + highlight.setMergeContiguous(true); + List fields = new ArrayList(); + fields.add(new RestRequestFieldsModel("cm:content")); + highlight.setFields(fields); + SearchResponse nodes = query(queryReq, highlight); + nodes.assertThat().entriesListIsNotEmpty(); + ResponseHighLightModel hl = nodes.getEntryByIndex(0).getSearch().getHighlight().get(0); + hl.assertThat().field("snippets").contains( "The landrover discovery is not a sports ¿car?"); + } + + @Test(groups={TestGroup.SEARCH,TestGroup.REST_API}) + public void searchNonIndexedData() throws Exception + { + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("cm:title"); + queryReq.setUserQuery("zoro"); + + RestRequestHighlightModel highlight = new RestRequestHighlightModel(); + highlight.setPrefix("¿"); + highlight.setPostfix("?"); + highlight.setMergeContiguous(true); + List fields = new ArrayList(); + fields.add(new RestRequestFieldsModel("cm:title")); + highlight.setFields(fields); + SearchResponse nodes = query(queryReq, highlight); + nodes.assertThat().entriesListDoesNotContain("highlight"); + } +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchSpellCheckTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchSpellCheckTest.java new file mode 100644 index 000000000..0e54d5690 --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchSpellCheckTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2018 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import org.alfresco.rest.model.RestRequestSpellcheckModel; +import org.alfresco.rest.search.RestRequestQueryModel; +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FileType; +import org.alfresco.utility.model.TestGroup; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Search end point Public API test with spell checking enabled. + * @author Michael Suzuki + * @author Meenal Bhave + */ +public class SearchSpellCheckTest extends AbstractSearchTest +{ + + /** + * Perform the below query + * { + * "spellcheck" : { }, + * "query" : { + * "userQuery" : "alfrezco", + * "query" : "cm:title:alfrezco" + * } + * to yeild the following result. + * { + * "list": { + * "pagination": { + * "count": 22, + * "hasMoreItems": false, + * "totalItems": 22, + * "skipCount": 0, + * "maxItems": 100 + * }, + * "context": { + * "spellCheck": { + * "type": "searchInsteadFor", + * "suggestions": [ + * "alfresco" + * ] + * } + * }, + * "entries": [...] + * } + * + * @throws Exception + */ + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ACS_60n}, priority=1) + public void testSearchMissSpelled() throws Exception + { + waitForContentIndexing(file4.getContent(), true); + + // Name + SearchRequest searchReq = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("cm:name:alfrezco"); + queryReq.setUserQuery("alfrezco"); + searchReq.setQuery(queryReq); + searchReq.setSpellcheck(new RestRequestSpellcheckModel()); + assertResponse(query(searchReq)); + + // Title + queryReq.setQuery("cm:title:alfrezco"); + queryReq.setUserQuery("alfrezco"); + searchReq.setQuery(queryReq); + searchReq.setSpellcheck(new RestRequestSpellcheckModel()); + assertResponse(query(searchReq)); + + // Description + queryReq.setQuery("cm:description:alfrezco"); + queryReq.setUserQuery("alfrezco"); + searchReq.setQuery(queryReq); + searchReq.setSpellcheck(new RestRequestSpellcheckModel()); + assertResponse(query(searchReq)); + + // Content + queryReq.setQuery("cm:content:alfrezco"); + queryReq.setUserQuery("alfrezco"); + searchReq.setQuery(queryReq); + searchReq.setSpellcheck(new RestRequestSpellcheckModel()); + assertResponse(query(searchReq)); + } + + private void assertResponse(SearchResponse nodes) throws Exception + { + nodes.assertThat().entriesListIsNotEmpty(); + nodes.getContext().assertThat().field("spellCheck").isNotEmpty(); + nodes.getContext().getSpellCheck().assertThat().field("suggestions").contains("alfresco"); + nodes.getContext().getSpellCheck().assertThat().field("type").is("searchInsteadFor"); + } + + /** + * Perform alternative way by setting the value in spellcheck object. + * + * { + * "query": { + * "query": "cm:title:alfrezco", + * "language": "afts" + * }, + * "spellcheck": {"query": "alfrezco"} + * } + * @throws Exception + */ + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ACS_60n}, priority=2) + public void testSearchMissSpelledVersion2() throws Exception + { + SearchRequest searchReq = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("cm:title:alfrezco"); + searchReq.setQuery(queryReq); + + RestRequestSpellcheckModel spellCheck = new RestRequestSpellcheckModel(); + spellCheck.setQuery("alfrezco"); + searchReq.setSpellcheck(spellCheck); + assertResponse(query(searchReq)); + } + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ACS_60n}, priority=3) + public void testSearchWithSpellcheckerAndCorrectSpelling() throws Exception + { + SearchRequest searchReq = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("cm:title:alfresco"); + queryReq.setUserQuery("alfresco"); + searchReq.setQuery(queryReq); + searchReq.setSpellcheck(new RestRequestSpellcheckModel()); + SearchResponse res = query(searchReq); + Assert.assertNull(res.getContext().getSpellCheck()); + res.assertThat().entriesListIsNotEmpty(); + } + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ACS_60n}, priority=4) + public void testSpellCheckType() throws Exception + { + // Create a file with mis-spelt name, expect spellcheck type = didYouMean + FileModel file = new FileModel(unique_searchString + "-1.txt", "uniquee" + "uniquee", "uniquee", FileType.TEXT_PLAIN, "Unique text file for search "); + dataContent.usingUser(userModel).usingSite(siteModel).createContent(file); + + waitForIndexing(file.getName(), true); + + // Search + SearchRequest searchReq = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("cm:title:uniquee"); + queryReq.setUserQuery("uniquee"); + searchReq.setQuery(queryReq); + searchReq.setSpellcheck(new RestRequestSpellcheckModel()); + SearchResponse nodes = query(searchReq); + + nodes.assertThat().entriesListIsNotEmpty(); + nodes.getContext().assertThat().field("spellCheck").isNotEmpty(); + nodes.getContext().getSpellCheck().assertThat().field("suggestions").contains("unique"); + nodes.getContext().getSpellCheck().assertThat().field("type").is("didYouMean"); + } +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchTest.java new file mode 100644 index 000000000..e2f081b9f --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/SearchTest.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2018 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Collections.reverse; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.testng.AssertJUnit.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.alfresco.rest.model.body.RestNodeLockBodyModel; +import org.alfresco.rest.search.RestRequestFilterQueryModel; +import org.alfresco.rest.search.RestRequestQueryModel; +import org.alfresco.rest.search.SearchNodeModel; +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FileType; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.testrail.ExecutionType; +import org.alfresco.utility.testrail.annotation.TestRail; +import org.hamcrest.Matchers; +import org.springframework.http.HttpStatus; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Search end point Public API test. + * @author Michael Suzuki + * + */ +public class SearchTest extends AbstractSearchTest +{ + @BeforeClass(alwaysRun = true) + public void setupEnvironment() throws Exception + { + waitForContentIndexing(file4.getContent(), true); + } + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API}) + public void searchOnIndexedData() throws Exception + { + SearchResponse nodes = query("cm:content:" + unique_searchString); + restClient.assertStatusCodeIs(HttpStatus.OK); + nodes.assertThat().entriesListIsNotEmpty(); + + SearchNodeModel entity = nodes.getEntryByIndex(0); + entity.assertThat().field("search").contains("score"); + entity.getSearch().assertThat().field("score").isNotEmpty(); + Assert.assertEquals(entity.getName(),"pangram.txt"); + } + + @Test(groups={TestGroup.SEARCH,TestGroup.REST_API}) + public void searchNonIndexedData() throws Exception + { + SearchResponse nodes = query("yeti"); + restClient.assertStatusCodeIs(HttpStatus.OK); + nodes.assertThat().entriesListIsEmpty(); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Checks its possible to include the original request in the response") + public void searchWithRequest() throws Exception + { + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("fox"); + query.setQuery(queryReq); + query.setIncludeRequest(true); + + SearchResponse response = query(query); + restClient.assertStatusCodeIs(HttpStatus.OK); + + response.getContext().assertThat().field("request").isNotEmpty(); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Tests a search request containing a sort clause.") + public void searchWithOneSortClause() throws Exception + { + // Tests the ascending order first + List expectedOrder = asList("alfresco.txt", "cars.txt", "pangram.txt"); + + SearchRequest searchRequest = createQuery("cm_name:alfresco\\.txt cm_name:cars\\.txt cm_name:pangram\\.txt"); + searchRequest.addSortClause("FIELD", "name", true); + + RestRequestFilterQueryModel filters = new RestRequestFilterQueryModel(); + filters.setQuery("SITE:'" + siteModel.getId() + "'"); + searchRequest.setFilterQueries(filters); + + SearchResponse responseWithAscendingOrder = query(searchRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + + assertEquals( + expectedOrder, + responseWithAscendingOrder.getEntries().stream() + .map(SearchNodeModel::getModel) + .map(SearchNodeModel::getName) + .collect(Collectors.toList())); + + // Reverts the expected order... + reverse(expectedOrder); + + // ...and test the descending order + searchRequest.getSort().clear(); + searchRequest.addSortClause("FIELD", "name", false); + + SearchResponse responseWithDescendingOrder = query(searchRequest); + assertEquals( + expectedOrder, + responseWithDescendingOrder.getEntries().stream() + .map(SearchNodeModel::getModel) + .map(SearchNodeModel::getName) + .collect(Collectors.toList())); + } + + /** + * Tests the query execution with two sort clauses. + * The first clause has always the same value for all matches so the test makes sure the request is correctly + * processed and the returned order is determined by the second clause. + */ + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, executionType = ExecutionType.REGRESSION, + description = "Tests a search request containing a sort clause.") + public void searchWithTwoSortClauses() throws Exception + { + // Tests the ascending order first + List expectedOrder = asList("alfresco.txt", "cars.txt", "pangram.txt"); + + SearchRequest searchRequest = createQuery("cm_name:alfresco\\.txt cm_name:cars\\.txt cm_name:pangram\\.txt"); + searchRequest.addSortClause("FIELD", "name", true); + searchRequest.addSortClause("FIELD", "createdByUser.id", true); + + RestRequestFilterQueryModel filters = new RestRequestFilterQueryModel(); + filters.setQuery("SITE:'" + siteModel.getId() + "'"); + searchRequest.setFilterQueries(filters); + + SearchResponse responseWithAscendingOrder = query(searchRequest); + + restClient.assertStatusCodeIs(HttpStatus.OK); + + assertEquals( + expectedOrder, + responseWithAscendingOrder.getEntries().stream() + .map(SearchNodeModel::getModel) + .map(SearchNodeModel::getName) + .collect(Collectors.toList())); + + // Reverts the expected order... + reverse(expectedOrder); + + // ...and test the descending order + searchRequest.getSort().clear(); + searchRequest.addSortClause("FIELD", "name", false); + searchRequest.addSortClause("FIELD", "createdByUser.id", true); + + SearchResponse responseWithDescendingOrder = query(searchRequest); + assertEquals( + expectedOrder, + responseWithDescendingOrder.getEntries().stream() + .map(SearchNodeModel::getModel) + .map(SearchNodeModel::getName) + .collect(Collectors.toList())); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ACS_61n }) @TestRail(section = { + TestGroup.REST_API, TestGroup.SEARCH, + TestGroup.ACS_61n }, executionType = ExecutionType.REGRESSION, description = "Checks the \"include\" request parameter support the 'permissions' option") public void searchQuery_includePermissions_shouldReturnNodeWithPermissionsInformation() + throws Exception + { + String query = "fox"; + String include = "permissions"; + + SearchRequest retrievalQueryIncludingPermissionsInformation = createQuery(query); + retrievalQueryIncludingPermissionsInformation.setInclude(singletonList(include)); + + query(retrievalQueryIncludingPermissionsInformation); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("list.entries[0].entry.permissions", notNullValue()); + + SearchRequest retrievalQueryNotIncludingLockInformation = createQuery(query); + + query(retrievalQueryNotIncludingLockInformation); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("list.entries[0].entry.permissions", nullValue()); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ACS_61n }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ACS_61n }, executionType = ExecutionType.REGRESSION, + description = "Checks the \"include\" request parameter support the 'isLocked' option") + public void searchQuery_includeIsLocked_shouldReturnNodeWithLockInformation() throws Exception { + String query = "fox"; + String include = "isLocked"; + + SearchRequest retrievalQueryIncludingLockInformation = createQuery(query); + retrievalQueryIncludingLockInformation.setInclude(singletonList(include)); + + query(retrievalQueryIncludingLockInformation); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("list.entries[0].entry.isLocked", equalTo(false)); + + RestNodeLockBodyModel lockBodyModel = new RestNodeLockBodyModel(); + lockBodyModel.setLifetime("EPHEMERAL"); + lockBodyModel.setTimeToExpire(20); + lockBodyModel.setType("FULL"); + restClient.authenticateUser(userModel).withCoreAPI().usingNode(file).usingParams("include=isLocked").lockNode(lockBodyModel); + + query(retrievalQueryIncludingLockInformation); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("list.entries[0].entry.isLocked", equalTo(true)); + + SearchRequest retrievalQueryNotIncludingLockInformation = createQuery(query); + + query(retrievalQueryNotIncludingLockInformation); + + restClient.assertStatusCodeIs(HttpStatus.OK); + restClient.onResponse().assertThat().body("list.entries[0].entry.isLocked", nullValue()); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ACS_61n }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ACS_61n }, executionType = ExecutionType.REGRESSION, + description = "Checks the \"include\" request parameter does not support the 'notValid' option") + public void searchQuery_includeInvalid_shouldReturnBadResponse() throws Exception + { + String query = "fox"; + String notValidInclude = "notValid"; + SearchRequest permissionsRetrieval = createQuery(query); + permissionsRetrieval.setInclude(singletonList(notValidInclude)); + + query(permissionsRetrieval); + + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST); + restClient.onResponse() + .assertThat() + .body("error.briefSummary", containsString("An invalid argument was received "+notValidInclude)); + } + + // Test that when fields parameter is set, only restricted fields appear in the response + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + public void searchWithFields() throws Exception + { + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("alfresco"); + query.setQuery(queryReq); + + // Restrict to fields: parentId + List fields = new ArrayList<>(); + fields.add("parentId"); + query.setFields(fields); + + query(query); + restClient.assertStatusCodeIs(HttpStatus.OK); + + // Only Field parentId is included in the response + restClient.onResponse().assertThat().body("list.entries.entry[0].parentId", Matchers.notNullValue()); + // Usual Fields such as 'name, id' aren't included in the response + restClient.onResponse().assertThat().body("list.entries.entry[0].name", Matchers.nullValue()); + restClient.onResponse().assertThat().body("list.entries.entry[0].id", Matchers.nullValue()); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API }) + public void searchSpecialCharacters() throws Exception + { + // Create a file with Special Characters + String specialCharfileName = "è¥äæ§ç§-åæ.pdf"; + FileModel file = new FileModel(specialCharfileName, "è¥äæ§ç§-忬¯¸" + "è¥äæ§ç§-忬¯¸", "è¥äæ§ç§-忬¯¸", FileType.TEXT_PLAIN, + "Text file with Special Characters: " + specialCharfileName); + dataContent.usingUser(userModel).usingSite(siteModel).createContent(file); + + waitForIndexing(file.getName(), true); + + // Search + SearchRequest searchReq = createQuery("name:'" + specialCharfileName + "'"); + SearchResponse nodes = query(searchReq); + + restClient.assertStatusCodeIs(HttpStatus.OK); + nodes.assertThat().entriesListIsNotEmpty(); + + restClient.onResponse().assertThat().body("list.entries.entry[0].name", Matchers.equalToIgnoringCase(specialCharfileName)); + } +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/ShardInfoTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/ShardInfoTest.java new file mode 100644 index 000000000..31eff2577 --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/ShardInfoTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2018 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import org.alfresco.rest.exception.EmptyRestModelCollectionException; +import org.alfresco.rest.search.RestInstanceModel; +import org.alfresco.rest.search.RestShardInfoModel; +import org.alfresco.rest.search.RestShardInfoModelCollection; +import org.alfresco.rest.search.RestShardModel; +import org.alfresco.utility.model.TestGroup; +import org.springframework.http.HttpStatus; +import org.testng.annotations.Test; + +/** + * Shard info end point REST API test. + * + * @author Tuna Aksoy + */ +public class ShardInfoTest extends AbstractSearchTest +{ + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ACS_60n}) + public void getShardInfoWithAdminAuthority() throws JsonProcessingException, EmptyRestModelCollectionException + { + RestShardInfoModelCollection info = restClient.authenticateUser(dataUser.getAdminUser()).withShardInfoAPI().getInfo(); + restClient.assertStatusCodeIs(HttpStatus.OK); + info.assertThat().entriesListIsNotEmpty(); + assertEquals(info.getPagination().getTotalItems().intValue(), 2); + + List stores = Arrays.asList("workspace://SpacesStore", "archive://SpacesStore"); + List baseUrls = Arrays.asList("/solr/alfresco", "/solr/archive"); + + List entries = info.getEntries(); + for (RestShardInfoModel shardInfoModel : entries) + { + RestShardInfoModel model = shardInfoModel.getModel(); + assertEquals(model.getTemplate(), "rerank"); + assertEquals(model.getMode(), "MASTER"); + assertEquals(model.getShardMethod(), "DB_ID"); + assertTrue(model.getHasContent()); + stores.contains(model.getStores()); + List shards = model.getShards(); + assertNotNull(shards); + RestShardModel shard = shards.iterator().next(); + assertNotNull(shard); + List instances = shard.getInstances(); + assertNotNull(instances); + RestInstanceModel instance = instances.iterator().next(); + assertNotNull(instance); + baseUrls.contains(instance.getBaseUrl()); + // TODO: Ideally Solr Host and Port should be Parameterised + assertEquals(instance.getHost(), "search"); + assertEquals(instance.getPort().intValue(), 8983); + assertEquals(instance.getState(), "ACTIVE"); + assertEquals(instance.getMode(), "MASTER"); + } + } + + @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ACS_60n}) + public void getShardInfoWithoutAdminAuthority() throws Exception + { + restClient.authenticateUser(dataUser.createRandomTestUser()).withShardInfoAPI().getInfo(); + restClient.assertStatusCodeIs(HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/StatsSearchTest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/StatsSearchTest.java new file mode 100644 index 000000000..39fa79a9b --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/StatsSearchTest.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2017 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.alfresco.rest.model.RestErrorModel; +import org.alfresco.rest.search.Pagination; +import org.alfresco.rest.search.RestGenericBucketModel; +import org.alfresco.rest.search.RestGenericFacetResponseModel; +import org.alfresco.rest.search.RestGenericMetricModel; +import org.alfresco.rest.search.RestRequestFacetFieldModel; +import org.alfresco.rest.search.RestRequestFacetFieldsModel; +import org.alfresco.rest.search.RestRequestFilterQueryModel; +import org.alfresco.rest.search.RestRequestPivotModel; +import org.alfresco.rest.search.RestRequestQueryModel; +import org.alfresco.rest.search.RestRequestStatsModel; +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.rest.search.SearchResponse; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.testrail.ExecutionType; +import org.alfresco.utility.testrail.annotation.TestRail; +import org.springframework.http.HttpStatus; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Stats search test. + * @author Gethin James + * + */ +public class StatsSearchTest extends AbstractSearchTest +{ + @BeforeClass(alwaysRun = true) + public void setupEnvironment() throws Exception + { + waitForContentIndexing(file2.getContent(), true); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, + executionType = ExecutionType.REGRESSION, + description = "Checks errors with stats using Search api") + public void searchWithBasicStats() throws Exception + { + SearchRequest query = carsQuery(); + Pagination pagination = new Pagination(); + pagination.setMaxItems(2); + List statsModels = new ArrayList<>(); + RestRequestStatsModel statsModel1 = new RestRequestStatsModel(); + statsModels.add(statsModel1); + query.setStats(statsModels); + query.setPaging(pagination); + + SearchResponse response = query(query); + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError() + .containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "stats field")); + + statsModel1.setField("DBID"); + response = query(query); + //8 metrics by default for a numeric field + Set metricTypes = assertStatsFacetedResponse(response, "DBID", 8); + assertTrue(metricTypes.containsAll(Arrays.asList("missing", "countValues", "sum","min","max", "sumOfSquares", "mean", "stddev"))); + + statsModel1.setField("creator"); + response = query(query); + //4 metrics by default for a string field + metricTypes = assertStatsFacetedResponse(response, "creator", 4); + assertTrue(metricTypes.containsAll(Arrays.asList("missing", "countValues", "min","max"))); + + statsModel1.setField("modified"); + response = query(query); + //8 metrics by default for a date field + metricTypes = assertStatsFacetedResponse(response, "modified", 8); + assertTrue(metricTypes.containsAll(Arrays.asList("missing", "countValues", "sum","min","max", "sumOfSquares", "mean", "stddev"))); + + statsModel1.setField("modifier"); + statsModel1.setMin(false); + statsModel1.setMax(false); + statsModel1.setMissing(false); + response = query(query); + metricTypes = assertStatsFacetedResponse(response, "modifier", 1); + assertFalse(metricTypes.containsAll(Arrays.asList("missing", "min","max"))); + + statsModel1.setField("DBID"); + statsModel1.setMin(true); + statsModel1.setMax(true); + statsModel1.setCountValues(false); + statsModel1.setMissing(false); + statsModel1.setSum(false); + statsModel1.setSumOfSquares(false); + statsModel1.setMean(false); + statsModel1.setStddev(false); + response = query(query); + metricTypes = assertStatsFacetedResponse(response, "DBID", 2); + assertTrue(metricTypes.containsAll(Arrays.asList("min","max"))); + assertFalse(metricTypes.containsAll(Arrays.asList("missing", "countValues", "sum","sumOfSquares", "mean", "stddev"))); + + statsModel1.setField("modifier"); + statsModel1.setMin(false); + statsModel1.setMax(false); + statsModel1.setCountDistinct(true); + statsModel1.setDistinctValues(true); + statsModel1.setCardinality(true); + response = query(query); + metricTypes = assertStatsFacetedResponse(response, "modifier", 3); + assertTrue(metricTypes.containsAll(Arrays.asList("countDistinct", "distinctValues", "cardinality"))); + + statsModel1.setField("DBID"); + statsModel1.setPercentiles(Arrays.asList(1,75,99,99.9)); + statsModel1.setCountDistinct(false); + statsModel1.setDistinctValues(false); + statsModel1.setCardinality(false); + response = query(query); + assertStatsFacetedResponse(response, "DBID", 1); + RestGenericMetricModel percMetric = response.getContext().getFacets().get(0).getBuckets().get(0).getMetrics().get(0); + assertEquals(percMetric.getType(),"percentiles"); + Map percVal = (Map) percMetric.getValue(); + Map percentiles = (Map) percVal.get("percentiles"); + assertEquals(percentiles.size(),4); + assertTrue(percentiles.keySet().containsAll(Arrays.asList("1.0","75.0","99.0","99.9"))); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, + executionType = ExecutionType.REGRESSION, + description = "Checks errors with stats labels using Search api") + public void searchWithStatsLabel() throws Exception + { + SearchRequest query = carsQuery(); + Pagination pagination = new Pagination(); + pagination.setMaxItems(2); + List statsModels = new ArrayList<>(); + RestRequestStatsModel statsModel1 = new RestRequestStatsModel(); + statsModel1.setField("modified"); + statsModel1.setLabel("DateChanged"); + statsModels.add(statsModel1); + query.setStats(statsModels); + query.setPaging(pagination); + SearchResponse response = query(query); + assertStatsFacetedResponse(response, "DateChanged", 8); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, + executionType = ExecutionType.REGRESSION, + description = "Checks errors with stats fitlers using Search api") + public void searchWithStatsFilters() throws Exception + { + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("content.mimetype:text/plain"); + query.setQuery(queryReq); + List statsModels = new ArrayList<>(); + RestRequestStatsModel statsModel1 = new RestRequestStatsModel(); + statsModel1.setField("creator"); + statsModel1.setMin(false); + statsModel1.setMax(false); + statsModel1.setMissing(false); + statsModels.add(statsModel1); + query.setFilterQueries(new RestRequestFilterQueryModel("cars", Arrays.asList("justCars"))); + query.setStats(statsModels); + SearchResponse response = query(query); + assertStatsFacetedResponse(response, "creator", 1); + RestGenericMetricModel countMetric = response.getContext().getFacets().get(0).getBuckets().get(0).getMetrics().get(0); + Integer count = response.getPagination().getTotalItems(); + Map metricCount = (Map) countMetric.getValue(); + assertEquals(count, metricCount.get("countValues")); + + statsModel1.setExcludeFilters(Arrays.asList("justCars")); + response = query(query); + assertStatsFacetedResponse(response, "creator", 1); + countMetric = response.getContext().getFacets().get(0).getBuckets().get(0).getMetrics().get(0); + count = response.getEntries().size(); + metricCount = (Map) countMetric.getValue(); + assertTrue((Integer)metricCount.get("countValues") > count, "With the exclude filter there will be more documents than returned"); + } + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, + executionType = ExecutionType.REGRESSION, + description = "Checks errors with stats with Pivot using Search api") + public void searchWithStatsAndMutlilevelPivot() throws Exception + { + SearchRequest query = carsQuery(); + + Pagination pagination = new Pagination(); + pagination.setMaxItems(1); + query.setPaging(pagination); + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List list = new ArrayList<>(); + RestRequestFacetFieldModel b0 = new RestRequestFacetFieldModel("SITE"); + b0.setLabel("b0"); + list.add(b0); + list.add(new RestRequestFacetFieldModel("created")); + RestRequestFacetFieldModel b2 = new RestRequestFacetFieldModel("modifier"); + b2.setLabel("b2"); + list.add(b2); + facetFields.setFacets(list); + query.setFacetFields(facetFields); + query.setIncludeRequest(false); + + List pivotModelList = new ArrayList<>(); + RestRequestPivotModel pivots = new RestRequestPivotModel(); + pivots.setKey("b0"); + RestRequestPivotModel pivotn = new RestRequestPivotModel(); + pivotn.setKey("created"); + RestRequestPivotModel pivot2 = new RestRequestPivotModel(); + pivot2.setKey("aNumber"); + + pivotn.setPivots(Arrays.asList(pivot2)); + pivots.setPivots(Arrays.asList(pivotn)); + pivotModelList.add(pivots); + query.setPivots(pivotModelList); + + List statsModels = new ArrayList<>(); + RestRequestStatsModel statsModel1 = new RestRequestStatsModel(); + statsModels.add(statsModel1); + query.setStats(statsModels); + statsModel1.setField("DBID"); + statsModel1.setLabel("aNumber"); + + SearchResponse response = query(query); + response.getContext().assertThat().field("facetsFields").isNotNull(); + response.getContext().assertThat().field("facets").isNotEmpty(); + assertEquals(response.getContext().getFacetsFields().size(), 1, "There should be 1 facet field for modifier"); + assertEquals(response.getContext().getFacets().size(), 2, "There should be 1 pivot facet with stats on the end and a high level stats facet"); + + RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(0); + facetResponseModel.assertThat().field("type").is("pivot"); + facetResponseModel.assertThat().field("label").is("b0"); + + //pivot created + RestGenericFacetResponseModel created = facetResponseModel.getBuckets().get(0).getFacets().get(0); + created.assertThat().field("type").is("pivot"); + created.assertThat().field("label").is("created"); + //Another nested stats + assertEquals(created.getBuckets().get(0).getMetrics().size(), 9, "Metrics are on the end of a pivot bucket"); + + } + + + @Test(groups = { TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }) + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH, TestGroup.ASS_1 }, + executionType = ExecutionType.REGRESSION, + description = "Checks errors with stats with Pivot using Search api") + public void searchWithStatsAndPivot() throws Exception + { + SearchRequest query = carsQuery(); + + Pagination pagination = new Pagination(); + pagination.setMaxItems(2); + query.setPaging(pagination); + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List list = new ArrayList<>(); + list.add(new RestRequestFacetFieldModel("creator")); + facetFields.setFacets(list); + query.setFacetFields(facetFields); + query.setIncludeRequest(false); + + SearchResponse response = query(query); + response.getContext().assertThat().field("facetsFields").isNotNull(); + + List pivotModelList = new ArrayList<>(); + RestRequestPivotModel pivots = new RestRequestPivotModel(); + pivots.setKey("creator"); + RestRequestPivotModel pivotn = new RestRequestPivotModel(); + pivotn.setKey("numericId"); + + pivots.setPivots(Arrays.asList(pivotn)); + pivotModelList.add(pivots); + query.setPivots(pivotModelList); + + List statsModels = new ArrayList<>(); + RestRequestStatsModel statsModel1 = new RestRequestStatsModel(); + statsModels.add(statsModel1); + query.setStats(statsModels); + statsModel1.setField("DBID"); + statsModel1.setLabel("numericId"); + + response = query(query); + + response.getContext().assertThat().field("facets").isNotEmpty(); + assertEquals(response.getContext().getFacets().size(), 2, "There should be 1 pivot facet with stats on the end and a high level stats facet"); + RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(0); + facetResponseModel.assertThat().field("type").is("pivot"); + facetResponseModel.assertThat().field("label").is("creator"); + assertEquals(facetResponseModel.getBuckets().get(0).getMetrics().size(), 9, "Metrics are on the end of a pivot bucket"); + RestGenericFacetResponseModel statsFacet = response.getContext().getFacets().get(1); + statsFacet.assertThat().field("type").is("stats"); + statsFacet.assertThat().field("label").is("numericId"); + + } + + private Set assertStatsFacetedResponse(SearchResponse response, String label, int metricsCount) throws Exception + { + response.getContext().assertThat().field("facets").isNotEmpty(); + RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(0); + facetResponseModel.assertThat().field("type").is("stats"); + facetResponseModel.assertThat().field("label").is(label); + RestGenericBucketModel bucket = facetResponseModel.getBuckets().get(0); + List metrics = bucket.getMetrics(); + assertEquals(metrics.size(),metricsCount); + return metrics.stream().map(m -> m.getType()).collect(Collectors.toSet()); + } + +} diff --git a/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/solr/SearchSolrAPITest.java b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/solr/SearchSolrAPITest.java new file mode 100644 index 000000000..281e24fc4 --- /dev/null +++ b/e2e-test/src/test/java/org/alfresco/service/search/e2e/searchservices/solr/SearchSolrAPITest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2018 Alfresco Software Limited. + * This file is part of Alfresco + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.search.e2e.searchservices.solr; + +import java.net.URLEncoder; + +import javax.json.JsonArrayBuilder; + +import org.alfresco.rest.core.JsonBodyGenerator; +import org.alfresco.rest.model.RestTextResponse; +import org.alfresco.service.search.e2e.searchservices.AbstractSearchTest; +import org.alfresco.utility.model.TestGroup; +import org.hamcrest.Matchers; +import org.springframework.http.HttpStatus; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Tests for solr/alfresco Solr API. + * + * @author Meenal Bhave + */ +public class SearchSolrAPITest extends AbstractSearchTest +{ + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_112 }, priority = 01) + public void testGetSolrConfig() throws Exception + { + RestTextResponse response = restClient.authenticateUser(adminUserModel).withSolrAPI().getConfig(); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().content(Matchers.containsString("config")); + Assert.assertNotNull(response.getJsonValueByPath("config.requestHandler")); + Assert.assertNotNull(response.getJsonObjectByPath("config.requestHandler")); + + // TODO: Following asserts fail with error: + /* + * java.lang.IllegalStateException: Expected response body to be verified as JSON, HTML or XML but content-type 'text/plain' is not supported out of the box. + * Try registering a custom parser using: RestAssured.registerParser("text/plain", ); + */ + + // response.assertThat().body("config.requestHandler", Matchers.notNullValue()); + // restClient.onResponse().assertThat().body("config.requestHandler",Matchers.notNullValue()); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_112 }, priority = 02) + public void testEditSolrConfig() throws Exception + { + String expectedError = "solrconfig editing is not enabled due to disable.configEdit"; + + JsonArrayBuilder argsArray = JsonBodyGenerator.defineJSONArray(); + argsArray.add("ANYARGS"); + + String postBody = JsonBodyGenerator.defineJSON() + .add("add-listener", JsonBodyGenerator.defineJSON() + .add("event", "postCommit") + .add("name", "newlistener") + .add("class", "solr.RunExecutableListener") + .add("exe", "ANYCOMMAND") + .add("dir", "/usr/bin/") + .add("args", argsArray)).build().toString(); + + // RestTextResponse response = + restClient.authenticateUser(adminUserModel).withSolrAPI().postConfig(postBody); + restClient.assertStatusCodeIs(HttpStatus.FORBIDDEN); + + restClient.onResponse().assertThat().content(Matchers.containsString(expectedError)); + + // TODO: Following asserts fail with error: + /* + * java.lang.IllegalStateException: Expected response body to be verified as JSON, HTML or XML but content-type 'text/plain' is not supported out of the box. + * Try registering a custom parser using: RestAssured.registerParser("text/plain", ); + */ + + // response.assertThat().body("error.msg", Matchers.contains(expectedError)); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_112 }, priority = 03) + public void testGetSolrConfigOverlay() throws Exception + { + restClient.authenticateUser(adminUserModel).withSolrAPI().getConfigOverlay(); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().content(Matchers.containsString("overlay")); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_112 }, priority = 04) + public void testGetSolrConfigParams() throws Exception + { + restClient.authenticateUser(adminUserModel).withSolrAPI().getConfigParams(); + restClient.assertStatusCodeIs(HttpStatus.OK); + + restClient.onResponse().assertThat().content(Matchers.containsString("response")); + } + + @Test(groups = { TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_112 }, priority = 05) + public void testGetSolrSelect() throws Exception + { + String queryParams = "{!xmlparser v=''}"; + + String encodedQueryParams = URLEncoder.encode(queryParams, "UTF-8"); + + restClient.authenticateUser(dataContent.getAdminUser()).withParams(encodedQueryParams).withSolrAPI().getSelectQuery(); + + restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST); + String errorMsg = "No QueryObjectBuilder defined for node a in {q={!xmlparser"; + Assert.assertTrue(restClient.onResponse().getResponse().body().xmlPath().getString("response").contains(errorMsg)); + } +} \ No newline at end of file diff --git a/e2e-test/src/test/resources/SearchSuite.xml b/e2e-test/src/test/resources/SearchSuite.xml index 3eb4d7745..63e01a9eb 100644 --- a/e2e-test/src/test/resources/SearchSuite.xml +++ b/e2e-test/src/test/resources/SearchSuite.xml @@ -2,38 +2,71 @@ - - - + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/e2e-test/src/test/resources/default.properties b/e2e-test/src/test/resources/default.properties index 82e2c2e03..72d142995 100644 --- a/e2e-test/src/test/resources/default.properties +++ b/e2e-test/src/test/resources/default.properties @@ -8,26 +8,61 @@ sync.scheme=http sync.server=localhost sync.port=9090 -# solr service related -solr.port=8083 - - #CMIS Related: Set CMIS binding to 'browser' or 'atom' cmis.binding=browser cmis.basePath=/alfresco/api/-default-/public/cmis/versions/1.1/${cmis.binding} -#Solr Indexing Time -solrWaitTimeInSeconds=20 - #RM Specific rest.rmPath=alfresco/api/-default-/public/gs/versions/1 -# Administrator Credentials +# Solr Server Settings +solr.scheme=http +solr.server=localhost +solr.port=8083 + +#Solr Indexing Time +solrWaitTimeInSeconds=20 + +# credentials admin.user=admin admin.password=admin +# in containers we cannot access directly JMX, so we will use http://jolokia.org agent +# disabling this we will use direct JMX calls to server jmx.useJolokiaAgent=false +# Server Health section +# in ServerHealth#isServerReachable() - could also be shown. +# enable this option to view if on server there are tenants or not +serverHealth.showTenants=false + +# TEST MANAGEMENT SECTION - Test Rail +# +# (currently supporting Test Rail v5.2.1.3472 integration) +# +# Example of configuration: +# ------------------------------------------------------ +# if testManagement.enabled=true we enabled TestRailExecutorListener (if used in your suite xml file) +# testManagement.updateTestExecutionResultsOnly=true (this will just update the results of a test: no step will be updated - good for performance) +# testManagement.endPoint=https://alfresco.testrail.com/ +# testManagement.username= +# testManagement.apiKey= +# testManagement.project= +# testManagement.includeOnlyTestCasesExecuted=true #if you want to include in your run ONLY the test cases that you run, then set this value to false +# testManagement.rateLimitInSeconds=1 #is the default rate limit after what minimum time, should we upload the next request. http://docs.gurock.com/testrail-api2/introduction #Rate Limit +# testManagement.suiteId=23 (the id of the Master suite) +# ------------------------------------------------------ +testManagement.enabled=false +testManagement.endPoint=https://alfresco.testrail.com/ +testManagement.username=tas.alfresco@gmail.com +testManagement.apiKey=EYpY7.fV0AoMGWbmyuVC-k5u.nzwHy6a.QWzJq8.S +testManagement.project=7 +testManagement.includeOnlyTestCasesExecuted=true +testManagement.rateLimitInSeconds=1 +testManagement.testRun=MyTestRunInTestRail +testManagement.suiteId=12 + # The location of the reports path reports.path=./target/reports diff --git a/e2e-test/src/test/resources/log4j.properties b/e2e-test/src/test/resources/log4j.properties index a7c0104f0..268d957c0 100644 --- a/e2e-test/src/test/resources/log4j.properties +++ b/e2e-test/src/test/resources/log4j.properties @@ -12,4 +12,16 @@ log4j.appender.file.layout.ConversionPattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - % log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %m%n \ No newline at end of file +log4j.appender.stdout.layout.ConversionPattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %m%n +log4j.appender.stdout.layout.ConversionPattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %m%n + +# TestRail particular log file +# Direct log messages to a log file +log4j.appender.testrailLog=org.apache.log4j.RollingFileAppender +log4j.appender.testrailLog.File=./reports/alfresco-testrail.log +log4j.appender.testrailLog.MaxBackupIndex=10 +log4j.appender.testrailLog.layout=org.apache.log4j.PatternLayout +log4j.appender.testrailLog.layout.ConversionPattern=%d{HH:mm:ss} %-5p %c{1}:%L - %m%n + +log4j.category.testrail=INFO, testrailLog +log4j.additivity.testrail=false \ No newline at end of file