diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efca45aa93..ae3b38e5b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,6 +69,9 @@ jobs: !contains(github.event.head_commit.message, '[skip tests]') steps: - uses: Alfresco/ya-pmd-scan@v2.0.5 + with: + pmd-version: "7.0.0-rc4" + pmd-sha256-digest: "81c15cb9e875f0c788e4246e5f0041b1df1b33983b9f65550ee4e8c4b285570b" tas_tests: name: ${{ matrix.testSuite }} TAS tests diff --git a/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/ElasticsearchLiveIndexingTests.java b/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/ElasticsearchLiveIndexingTests.java index 169ea2328a..45218588dc 100644 --- a/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/ElasticsearchLiveIndexingTests.java +++ b/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/ElasticsearchLiveIndexingTests.java @@ -207,7 +207,7 @@ public void wildcardWorksWithoutQuotes() @TestRail (section = TestGroup.SEARCH, executionType = ExecutionType.REGRESSION, description = "Verify that wildcard queries work against noderefs.") - @Test (groups = TestGroup.SEARCH) + @Test(groups = TestGroup.SEARCH, enabled = false) //Test should be re-enabled within: ACS-6068 public void wildcardNodeRefQuery() { searchQueryService.expectResultsFromQuery(req("ANCESTOR:\"" + siteModel2.getGuid().substring(0, 10) + "*\""), userMultiSite, "documentLibrary", FILE_2_NAME); diff --git a/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryAncestorIndexingTests.java b/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryAncestorIndexingTests.java new file mode 100644 index 0000000000..1269063de8 --- /dev/null +++ b/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryAncestorIndexingTests.java @@ -0,0 +1,337 @@ +package org.alfresco.elasticsearch.reindexing; + +import static org.alfresco.elasticsearch.SearchQueryService.req; +import static org.alfresco.utility.report.log.Step.STEP; + +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.TestGroup; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Tests verifying live indexing of secondary children and ANCESTOR index in Elasticsearch. + */ +@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"}) // these are TAS E2E tests and use searchQueryService.expectResultsFromQuery for assertion +public class NodesSecondaryAncestorIndexingTests extends NodesSecondaryChildrenRelatedTests +{ + + private FileModel fileInP; + + /** + * Creates a user and a private site containing below hierarchy of folders. + *
+     * Site
+     * DL (Document Library)
+     *  += fA += fB += fC (folderC)
+     *         /     / |
+     *        +     +  +
+     *  += fK += fL += fM
+     *     |     +
+     *     +     |
+     *  += fX += fY += fZ
+     *  += fP += file -+ fA
+     *  += fQ
+     *  += fR
+     *  += fS
+     * 
+ * Parent += Child - primary parent-child relationship + * Parent +- Child - secondary parent-child relationship + */ + @BeforeClass(alwaysRun = true) + @Override + public void dataPreparation() + { + super.dataPreparation(); + + // given + STEP("Create few sets of nested folders in site's Document Library."); + folders().createNestedFolders(A, B, C); + folders().createNestedFolders(K, L, M); + folders().createNestedFolders(X, Y, Z); + folders().createFolder(P); + folders().createFolder(Q); + folders().createFolder(R); + folders().createFolder(S); + fileInP = folders(P).createRandomDocument(); + + STEP("Create few secondary parent-child relationships."); + folders(K).addSecondaryChild(folders(B)); + folders(X).addSecondaryChild(folders(K)); + folders(L).addSecondaryChildren(folders(C), folders(Y)); + folders(M).addSecondaryChild(folders(C)); + folders(A).addSecondaryChild(fileInP); + } + + @Test(groups = TestGroup.SEARCH) + public void testSecondaryAncestorWithNodeHavingOneSecondaryChild() + { + // then + STEP("Verify that searching by ANCESTOR and folderM will find one descendant node: folderC."); + SearchRequest query = req("ANCESTOR:" + folders(M).getNodeRef()); + searchQueryService.expectResultsFromQuery(query, testUser, + folders(C).getName()); + } + + @Test(groups = TestGroup.SEARCH) + public void testSecondaryAncestorWithNodeHavingTwoSecondaryChildren() + { + // then + STEP("Verify that searching by ANCESTOR and folderL will find nodes: folderM, folderC, folderY and folderZ."); + SearchRequest queryAncestorL = req("ANCESTOR:" + folders(L).getNodeRef()); + searchQueryService.expectResultsFromQuery(queryAncestorL, testUser, + // primary descendant + folders(M).getName(), + // secondary descendants + folders(C).getName(), + folders(Y).getName(), + folders(Z).getName()); + } + + @Test(groups = TestGroup.SEARCH) + public void testSecondaryAncestorWithDocumentAsSecondaryChild() + { + // then + STEP("Verify that searching by ANCESTOR and folderA will find nodes: folderB, folderC and fileInP."); + SearchRequest query = req("ANCESTOR:" + folders(A).getNodeRef()); + searchQueryService.expectResultsFromQuery(query, testUser, + // primary descendants + folders(B).getName(), + folders(C).getName(), + // secondary descendant + fileInP.getName()); + } + + @Test(groups = TestGroup.SEARCH) + public void testSecondaryAncestorWithNodeHavingComplexSecondaryRelationship() + { + // then + STEP("Verify that all descendant of folderX can be found."); + SearchRequest query = req("ANCESTOR:" + folders(X).getNodeRef()); + searchQueryService.expectResultsFromQuery(query, testUser, + // primary descendants + folders(Y).getName(), + folders(Z).getName(), + // secondary descendants + folders(B).getName(), + folders(C).getName(), + folders(K).getName(), + folders(L).getName(), + folders(M).getName() + ); + } + + /** + * Verify that removing secondary parent-child relationship will result in updating ES index: ANCESTOR. + * Test changes below folders hierarchy: + *
+     * DL
+     *  += fQ
+     *     +
+     *     |
+     *  += fR
+     * 
+ * into: + *
+     * DL
+     *  += fQ
+     *  += fR
+     * 
+ */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryAncestorWithDeletedSecondaryRelationship() + { + // given + STEP("Add to folderQ a secondary child folderR and verify if it can be found using ANCESTOR index and secondary child node reference."); + folders(Q).addSecondaryChild(folders(R)); + + STEP("Verify that searching by ANCESTOR and folderQ will find secondary descendant node: folderR."); + SearchRequest query = req("ANCESTOR:" + folders(Q).getNodeRef()); + searchQueryService.expectResultsFromQuery(query, testUser, + // secondary descendant + folders(R).getName()); + + // when + STEP("Delete the secondary parent-child relationship between folderQ and FolderR."); + folders(Q).removeSecondaryChild(folders(R)); + + // then + STEP("Verify that folderQ cannot be found by ANCESTOR and folderQ anymore."); + searchQueryService.expectNoResultsFromQuery(query, testUser); + } + + /** + * Verify that removing a node D (fD) having a secondary children relationship will remove the relationships and update ANCESTOR index in ES. + * Test changes below folders hierarchy from: + *
+     * DL
+     *  += fQ
+     *     +
+     *     |
+     *  += fE += fF
+     *     +
+     *     |
+     *  += fR
+     * 
+ * into: + *
+     * DL
+     *  += fQ
+     *  += fR
+     * 
+ */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryAncestorWithDeletedSecondaryParentNode() + { + // given + STEP("Create two nested folders (E and F) in Document Library."); + Folder folderE = folders().createFolder( "E"); + Folder folderF = folderE.createNestedFolder( "F"); + STEP("Make folderE a secondary children of folderQ and folderR a secondary children of folderE."); + folders(Q).addSecondaryChild(folderE); + folderE.addSecondaryChild(folders(R)); + + STEP("Verify that searching by ANCESTOR and folderQ will find its secondary descendant: folderE, folderF and folderR."); + SearchRequest queryAncestorQ = req("ANCESTOR:" + folders(Q).getNodeRef()); + searchQueryService.expectResultsFromQuery(queryAncestorQ, testUser, + // secondary descendants + folderE.getName(), + folderF.getName(), + folders(R).getName()); + + // when + STEP("Delete folderE with its content."); + folders().delete(folderE); + + // then + STEP("Verify that searching by ANCESTOR and folderQ will not find any nodes."); + searchQueryService.expectNoResultsFromQuery(queryAncestorQ, testUser); + } + + /** + * Verify that moving folderD (fD) containing secondary children from hierarchy: + *
+     * DL
+     *  += fQ += fD +- fP += file
+     *  += fR
+     * 
+ * to: + *
+     * DL
+     *  += fQ
+     *  += fR += fD +- fP += file
+     * 
+ * will update ANCESTOR index in ES. + */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryAncestorWithMovedSecondaryParentNode() + { + // given + STEP("Create folderD inside folderQ, and add folderP to D as a secondary child."); + Folder folderD = folders(Q).createNestedFolder( "D"); + folderD.addSecondaryChild(folders(P)); + + STEP("Verify that searching by ANCESTOR and folderQ will find its primary and secondary descendant nodes: folderD, folderP and file."); + SearchRequest queryAncestorQ = req("ANCESTOR:" + folders(Q).getNodeRef()); + searchQueryService.expectResultsFromQuery(queryAncestorQ, testUser, + // primary descendant + folderD.getName(), + // secondary descendants + folders(P).getName(), + fileInP.getName()); + STEP("Verify that searching by ANCESTOR and folderR will not find any descendant nodes."); + SearchRequest queryAncestorR = req("ANCESTOR:" + folders(R).getNodeRef()); + searchQueryService.expectNoResultsFromQuery(queryAncestorR, testUser); + + // when + STEP("Move folderD from folderQ to folderR."); + folderD.moveTo(folders(R)); + + // then + STEP("Verify that search result for ANCESTOR and folderQ will not find any descendant anymore."); + searchQueryService.expectNoResultsFromQuery(queryAncestorQ, testUser); + STEP("Verify that searching by ANCESTOR and folderR will find its primary and secondary descendant nodes: folderD, folderP and file."); + searchQueryService.expectResultsFromQuery(queryAncestorR, testUser, + // primary descendant + folderD.getName(), + // secondary descendants + folders(P).getName(), + fileInP.getName()); + + STEP("Clean-up - delete folderD."); + folders().delete(folderD); + } + + /** + * Verify that copying folder will also result in copying folder's secondary children and update ANCESTOR index in ES. + * Test changes below folders hierarchy: + *
+     * DL
+     *  += fS += fG += fH
+     *         +
+     *        /
+     *  += fP += file
+     *  += fT
+     * 
+ * into: + *
+     * DL
+     *  += fS += fG += fH
+     *         +
+     *        /
+     *  += fP += file
+     *        \
+     *         +
+     *  += fT += fG-c += fH-c
+     *  
+ */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryAncestorWithCopiedSecondaryParentNode() + { + // given + STEP("Create nested folders (G and H) inside folderS and folderT in Document Library. Make folderP a secondary child of folderG."); + Folder folderG = folders(S).createNestedFolder( "G"); + Folder folderH = folderG.createNestedFolder("H"); + Folder folderT = folders().createFolder("T"); + folderG.addSecondaryChild(folders(P)); + + STEP("Verify that searching by ANCESTOR and folderS will find its descendant nodes: folderG, folderH, folderP and file in P."); + SearchRequest queryAncestorS = req("ANCESTOR:" + folders(S).getNodeRef()); + searchQueryService.expectResultsFromQuery(queryAncestorS, testUser, + // primary descendants + folderG.getName(), + folderH.getName(), + // secondary descendants + folders(P).getName(), + fileInP.getName()); + STEP("Verify that searching by ANCESTOR and folderT will not find any nodes."); + SearchRequest queryAncestorT = req("ANCESTOR:" + folderT.getNodeRef()); + searchQueryService.expectNoResultsFromQuery(queryAncestorT, testUser); + + // when + STEP("Copy folderG with its content to folderT."); + Folder folderGCopy = folderG.copyTo(folderT); + + // then + STEP("Verify that searching by ANCESTOR and folderS will find its descendant nodes: folderG, folderH, folderP and file in P."); + searchQueryService.expectResultsFromQuery(queryAncestorS, testUser, + // primary descendants + folderG.getName(), + folderH.getName(), + // secondary descendants + folders(P).getName(), + fileInP.getName()); + STEP("Verify that searching by ANCESTOR and folderT will find its descendant nodes: folderG-copy, folderH-copy, folderP, file."); + searchQueryService.expectResultsFromQuery(queryAncestorT, testUser, + // primary descendants + folderGCopy.getName(), + folderH.getName(), // the same name as folderH-copy + // secondary descendants + folders(P).getName(), + fileInP.getName()); + + STEP("Clean-up - delete folderG and folderT (with G's copy)."); + folders().delete(folderG); + folders().delete(folderT); + } +} diff --git a/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryChildrenRelatedTests.java b/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryChildrenRelatedTests.java new file mode 100644 index 0000000000..45fa739402 --- /dev/null +++ b/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryChildrenRelatedTests.java @@ -0,0 +1,286 @@ +package org.alfresco.elasticsearch.reindexing; + +import static org.alfresco.utility.report.log.Step.STEP; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.alfresco.elasticsearch.SearchQueryService; +import org.alfresco.rest.core.RestWrapper; +import org.alfresco.rest.model.RestNodeBodyMoveCopyModel; +import org.alfresco.rest.model.RestNodeModel; +import org.alfresco.tas.AlfrescoStackInitializer; +import org.alfresco.utility.data.DataContent; +import org.alfresco.utility.data.DataSite; +import org.alfresco.utility.data.DataUser; +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.SiteModel; +import org.alfresco.utility.model.UserModel; +import org.alfresco.utility.network.ServerHealth; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; + +@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml", + initializers = AlfrescoStackInitializer.class) +public abstract class NodesSecondaryChildrenRelatedTests extends AbstractTestNGSpringContextTests +{ + @SuppressWarnings("PMD.OneDeclarationPerLine") + protected static final String A = "A", B = "B", C = "C", K = "K", L = "L", M = "M", P = "P", Q = "Q", R = "R", S = "S", X = "X", Y = "Y", Z = "Z"; + + @Autowired + private ServerHealth serverHealth; + @Autowired + private DataUser dataUser; + @Autowired + private DataSite dataSite; + @Autowired + private DataContent dataContent; + @Autowired + private RestWrapper restClient; + @Autowired + protected SearchQueryService searchQueryService; + + protected UserModel testUser; + private SiteModel testSite; + private final Folders folders = new TestFolders(); + + @BeforeClass(alwaysRun = true) + public void dataPreparation() + { + STEP("Verify environment health."); + serverHealth.isServerReachable(); + serverHealth.assertServerIsOnline(); + + STEP("Use the reindexing component to index the system nodes."); + AlfrescoStackInitializer.reindexEverything(); + + STEP("Create a test user and private site."); + testUser = dataUser.createRandomTestUser(); + testSite = dataSite.usingUser(testUser).createPrivateRandomSite(); + } + + @AfterClass(alwaysRun = true) + public void dataCleanUp() + { + STEP("Clean up data after tests."); + dataSite.usingUser(testUser).deleteSite(testSite); + dataUser.usingAdmin().deleteUser(testUser); + } + + protected Folders folders() + { + return folders; + } + + protected Folder folders(String alias) + { + return folders.get(alias); + } + + /** Helper class allowing easy folder management. */ + public class Folder extends FolderModel + { + private final UserModel user; + + private Folder() + { + super(); + this.user = testUser; + } + + /** Object construction in fact creates a new folder in system. */ + private Folder(SiteModel site, Folder parent, String name) + { + this(); + if (site != null) + { + dataContent.usingSite(site); + } + if (parent != null) + { + dataContent.usingResource(parent); + } + FolderModel folderModel = dataContent.usingUser(user).createFolder(new FolderModel(name)); + this.setName(folderModel.getName()); + this.setNodeRef(folderModel.getNodeRef()); + this.setCmisLocation(folderModel.getCmisLocation()); + } + + /** Adds one secondary child to this folder. */ + protected void addSecondaryChild(ContentModel secondaryChild) + { + restClient.authenticateUser(user).withCoreAPI().usingNode(this).addSecondaryChild(secondaryChild); + } + + /** Adds multiple secondary children to this folder. */ + protected void addSecondaryChildren(ContentModel... secondaryChildren) + { + restClient.authenticateUser(user).withCoreAPI().usingNode(this).addSecondaryChildren(secondaryChildren); + } + + /** Removes secondary parent-child association. */ + protected void removeSecondaryChild(ContentModel secondaryChild) + { + restClient.authenticateUser(user).withCoreAPI().usingNode(this).removeSecondaryChild(secondaryChild); + } + + /** Creates nested folder in this folder. */ + protected Folder createNestedFolder(String folderSuffix) + { + Folder createdFolder = new Folder(testSite, this, generateRandomFolderNameWith(folderSuffix)); + folders.put(folderSuffix, createdFolder); + return createdFolder; + } + + /** Creates multiple nested folders in this folder. */ + protected Map createNestedFolders(String... folderSuffixes) + { + Map createdFolders = createNestedFolders(this, folderSuffixes); + folders.putAll(createdFolders); + return createdFolders; + } + + private Map createNestedFolders(Folder node, String... folderSuffixes) + { + Map createdFolders = new TestFolders(); + Stream.of(folderSuffixes).findFirst().ifPresent(folderSuffix -> { + Folder createdFolder = new Folder(testSite, node, generateRandomFolderNameWith(folderSuffix)); + createdFolders.put(folderSuffix, createdFolder); + String[] remainingSuffixes = Stream.of(folderSuffixes).skip(1).toArray(String[]::new); + if (remainingSuffixes.length > 0) + { + createdFolders.putAll(createNestedFolders(createdFolder, remainingSuffixes)); + } + }); + + return createdFolders; + } + + /** Moves this folder with its content to different folder. */ + protected void moveTo(Folder target) + { + RestNodeBodyMoveCopyModel moveModel = new RestNodeBodyMoveCopyModel(); + moveModel.setTargetParentId(target.getNodeRef()); + moveModel.setName(this.getName()); + + RestNodeModel movedNode = restClient.authenticateUser(user).withCoreAPI().usingNode(this).include("path").move(moveModel); + this.setCmisLocation(getCmisLocation(movedNode.getPath(), movedNode.getName())); + } + + /** Copies this folder with its content to different folder. */ + protected Folder copyTo(Folder target) + { + RestNodeBodyMoveCopyModel copyModel = new RestNodeBodyMoveCopyModel(); + copyModel.setTargetParentId(target.getNodeRef()); + copyModel.setName(this.getName()); + + RestNodeModel nodeCopy = restClient.authenticateUser(user).withCoreAPI().usingNode(this).include("path").copy(copyModel); + Folder folderCopy = new Folder(); + folderCopy.setName(nodeCopy.getName()); + folderCopy.setNodeRef(nodeCopy.getId()); + folderCopy.setCmisLocation(getCmisLocation(nodeCopy.getPath(), nodeCopy.getName())); + return folderCopy; + } + + private void delete() + { + dataContent.usingUser(user).usingResource(this).deleteContent(); + } + + /** Creates random file in this folder. */ + protected FileModel createRandomDocument() + { + return createDocument(generateRandomFileName()); + } + + protected FileModel createDocument(String filename) + { + return dataContent.usingUser(user) + .usingResource(this) + .createContent(new FileModel(filename, FileType.TEXT_PLAIN, "content")); + } + + private static String generateRandomFolderNameWith(String folderSuffix) + { + return "folder" + folderSuffix + "_" + UUID.randomUUID(); + } + + private static String generateRandomFileName() + { + return "TestFile" + UUID.randomUUID() + ".txt"; + } + + private static String getCmisLocation(Object pathMap, String name) + { + return Stream.concat( + Stream.of(pathMap) + .filter(Objects::nonNull) + .filter(path -> path instanceof Map) + .map(Map.class::cast) + .map(path -> path.get("elements")) + .filter(Objects::nonNull) + .filter(elements -> elements instanceof List) + .map(List.class::cast) + .flatMap(elements -> (Stream) elements.stream()) + .skip(1) + .filter(element -> element instanceof Map) + .map(Map.class::cast) + .map(element -> element.get("name")) + .filter(Objects::nonNull) + .filter(elementName -> elementName instanceof String) + .map(String.class::cast), + Stream.of(name)) + .collect(Collectors.joining("/", "/", "/")); + } + } + + /** Helper {@link Map} containing all created folders and allowing basic operations like creating and deleting a folder. */ + @SuppressWarnings("PMD.TestClassWithoutTestCases") + public class TestFolders extends HashMap implements Folders + { + /** Creates a folder in site's Document Library. */ + @Override + public Folder createFolder(String folderSuffix) + { + Folder createdFolder = new Folder().createNestedFolders((Folder) null, folderSuffix).get(folderSuffix); + this.put(folderSuffix, createdFolder); + return createdFolder; + } + + /** Creates multiple nested folders in site's Document Library. */ + @Override + public Map createNestedFolders(String... folderSuffixes) + { + Map createdFolders = new Folder().createNestedFolders(null, folderSuffixes); + this.putAll(createdFolders); + return createdFolders; + } + + @Override + public void delete(Folder folder) + { + folder.delete(); + this.remove(folder); + } + } + + public interface Folders extends Map + { + Folder createFolder(String folderSuffix); + + Map createNestedFolders(String... folderSuffixes); + + void delete(Folder folder); + } +} diff --git a/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryParentIndexingTests.java b/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryParentIndexingTests.java new file mode 100644 index 0000000000..7fb478a29f --- /dev/null +++ b/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryParentIndexingTests.java @@ -0,0 +1,330 @@ +package org.alfresco.elasticsearch.reindexing; + +import static org.alfresco.elasticsearch.SearchQueryService.req; +import static org.alfresco.utility.report.log.Step.STEP; + +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.TestGroup; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Tests verifying live indexing of secondary children and PARENT index in Elasticsearch. + */ +@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"}) // these are TAS E2E tests and use searchQueryService.expectResultsFromQuery for assertion +public class NodesSecondaryParentIndexingTests extends NodesSecondaryChildrenRelatedTests +{ + + private FileModel fileInP; + + /** + * Creates a user and a private site containing below hierarchy of folders. + *
+     * Site
+     * DL (Document Library)
+     *  += fA += fB += fC (folderC)
+     *         /     / |
+     *        +     +  +
+     *  += fK += fL += fM
+     *     |     +
+     *     +     |
+     *  += fX += fY += fZ
+     *  += fP += file -+ fA
+     *  += fQ
+     *  += fR
+     *  += fS
+     * 
+ * Parent += Child - primary parent-child relationship + * Parent +- Child - secondary parent-child relationship + */ + @BeforeClass(alwaysRun = true) + @Override + public void dataPreparation() + { + super.dataPreparation(); + + // given + STEP("Create few sets of nested folders in site's Document Library."); + folders().createNestedFolders(A, B, C); + folders().createNestedFolders(K, L, M); + folders().createNestedFolders(X, Y, Z); + folders().createFolder(P); + folders().createFolder(Q); + folders().createFolder(R); + folders().createFolder(S); + fileInP = folders(P).createRandomDocument(); + + STEP("Create few secondary parent-child relationships."); + folders(K).addSecondaryChild(folders(B)); + folders(X).addSecondaryChild(folders(K)); + folders(L).addSecondaryChildren(folders(C), folders(Y)); + folders(M).addSecondaryChild(folders(C)); + folders(A).addSecondaryChild(fileInP); + } + + @Test(groups = TestGroup.SEARCH) + public void testSecondaryParentWithNodeHavingOneSecondaryChild() + { + // then + STEP("Verify that searching rby PARENT and folderM will find node folderC."); + SearchRequest query = req("PARENT:" + folders(M).getNodeRef()); + searchQueryService.expectResultsFromQuery(query, testUser, + folders(C).getName()); + } + + @Test(groups = TestGroup.SEARCH) + public void testSecondaryParentWithNodeHavingTwoSecondaryChildren() + { + // then + STEP("Verify that searching by PARENT and folderL will find nodes: folderM, FolderC and folderY."); + SearchRequest query = req("PARENT:" + folders(L).getNodeRef()); + searchQueryService.expectResultsFromQuery(query, testUser, + // primary child + folders(M).getName(), + // secondary children + folders(C).getName(), + folders(Y).getName()); + } + + @Test(groups = TestGroup.SEARCH) + public void testSecondaryParentWithDocumentAsSecondaryChild() + { + // then + STEP("Verify that searching by PARENT and folderA will find nodes: folderB and file."); + SearchRequest query = req("PARENT:" + folders(A).getNodeRef()); + searchQueryService.expectResultsFromQuery(query, testUser, + // primary child + folders(B).getName(), + // secondary child + fileInP.getName()); + } + + /** + * Verify that removing secondary parent-child relationship will result in updating ES index: PARENT. + * Test changes below folders hierarchy: + *
+     * DL
+     *  += fQ
+     *     +
+     *     |
+     *  += fR
+     * 
+ * into: + *
+     * DL
+     *  += fQ
+     *  += fR
+     * 
+ */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryParentWithDeletedSecondaryRelationship() + { + // given + STEP("Add to folderQ a secondary child folderR and verify if it can be found using PARENT index and secondary parent node reference."); + folders(Q).addSecondaryChild(folders(R)); + + SearchRequest query = req("PARENT:" + folders(Q).getNodeRef()); + searchQueryService.expectResultsFromQuery(query, testUser, + // secondary child + folders(R).getName()); + + // when + STEP("Delete the secondary parent relationship between folderQ and FolderR and verify that folderR cannot be found by PARENT and folderQ anymore."); + folders(Q).removeSecondaryChild(folders(R)); + + // then + searchQueryService.expectNoResultsFromQuery(query, testUser); + } + + /** + * Verify that removing a node D (fD) having a secondary children relationship will remove the relationships and update PARENT index in ES. + * Test changes below folders hierarchy from: + *
+     * DL
+     *  += fQ
+     *     +
+     *     |
+     *  += fE += fF
+     *     +
+     *     |
+     *  += fR
+     * 
+ * into: + *
+     * DL
+     *  += fQ
+     *  += fR
+     * 
+ */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryParentWithDeletedSecondaryParentNode() + { + // given + STEP("Create two nested folders (E and F) in Document Library."); + Folder folderE = folders().createFolder( "E"); + Folder folderF = folderE.createNestedFolder( "F"); + STEP("Make folderE a secondary children of folderQ and folderR a secondary children of folderE."); + folders(Q).addSecondaryChild(folderE); + folderE.addSecondaryChild(folders(R)); + + STEP("Verify that searching by PARENT and folderQ will find its secondary child: folderE."); + SearchRequest queryParentQ = req("PARENT:" + folders(Q).getNodeRef()); + searchQueryService.expectResultsFromQuery(queryParentQ, testUser, + // secondary child + folderE.getName()); + STEP("Verify that searching by PARENT and folderE will find its primary and secondary children: folderF and folderR."); + SearchRequest queryParentD = req("PARENT:" + folderE.getNodeRef()); + searchQueryService.expectResultsFromQuery(queryParentD, testUser, + // primary child + folderF.getName(), + // secondary child + folders(R).getName()); + + // when + STEP("Delete folderE and verify that PARENT was updated for nodes folderQ and folderR."); + folders().delete(folderE); + + // then + searchQueryService.expectNoResultsFromQuery(queryParentQ, testUser); + searchQueryService.expectNoResultsFromQuery(queryParentD, testUser); + } + + /** + * Verify that moving folder D (fD) containing secondary children from hierarchy: + *
+     * DL
+     *  += fQ += fD +- fP += file
+     *  += fR
+     * 
+ * to: + *
+     * DL
+     *  += fQ
+     *  += fR += fD +- fP += file
+     * 
+ * will update PARENT index in ES. + */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryParentWithMovedSecondaryParentNode() + { + // given + STEP("Create folderD inside folderQ, and add folderP to D as a secondary child."); + Folder folderD = folders(Q).createNestedFolder( "D"); + folderD.addSecondaryChild(folders(P)); + + STEP("Verify that searching by PARENT and folderD will find node folderP."); + SearchRequest queryParentD = req("PARENT:" + folderD.getNodeRef()); + searchQueryService.expectResultsFromQuery(queryParentD, testUser, + // secondary child + folders(P).getName()); + STEP("Verify that searching by PARENT and folderQ will find node folderD."); + SearchRequest queryParentQ = req("PARENT:" + folders(Q).getNodeRef()); + searchQueryService.expectResultsFromQuery(queryParentQ, testUser, + // primary child + folderD.getName()); + + // when + STEP("Move folderD from folderQ to folderR."); + folderD.moveTo(folders(R)); + + // then + STEP("Verify that search result for PARENT and folderD didn't change."); + searchQueryService.expectResultsFromQuery(queryParentD, testUser, + // secondary child + folders(P).getName()); + STEP("Verify that searching by PARENT and folderQ doesn't return any node anymore."); + searchQueryService.expectNoResultsFromQuery(queryParentQ, testUser); + STEP("Verify that searching by PARENT and folderR will find node folderD."); + SearchRequest queryParentR = req("PARENT:" + folders(R).getNodeRef()); + searchQueryService.expectResultsFromQuery(queryParentR, testUser, + // primary child + folderD.getName()); + + STEP("Clean-up - delete folderD."); + folders().delete(folderD); + } + + /** + * Verify that copying folder will also result in copying folder's secondary children and update PARENT index in ES. + * Test changes below folders hierarchy: + *
+     * DL
+     *  += fS += fG += fH
+     *         +
+     *        /
+     *  += fP += file
+     *  += fT
+     * 
+ * into: + *
+     * DL
+     *  += fS += fG += fH
+     *         +
+     *        /
+     *  += fP += file
+     *        \
+     *         +
+     *  += fT += fG-c += fH-c
+     *  
+ */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryParentWithCopiedSecondaryParentNode() + { + // given + STEP("Create nested folders (G and H) inside folderS and folderT in Document Library. Make folderP a secondary child of folderG."); + Folder folderG = folders(S).createNestedFolder( "G"); + Folder folderH = folderG.createNestedFolder("H"); + Folder folderT = folders().createFolder("T"); + folderG.addSecondaryChild(folders(P)); + + STEP("Verify that searching by PARENT and folderG will find nodes: folderH, folderP and file."); + SearchRequest queryParentG = req("PARENT:" + folderG.getNodeRef()); + searchQueryService.expectResultsFromQuery(queryParentG, testUser, + // primary child + folderH.getName(), + // secondary child + folders(P).getName()); + STEP("Verify that searching by PARENT and folderS will find nodes: folderG, folderH, folderP and file."); + SearchRequest queryParentS = req("PARENT:" + folders(S).getNodeRef()); + searchQueryService.expectResultsFromQuery(queryParentS, testUser, + // primary child + folderG.getName()); + STEP("Verify that searching by PARENT and folderP will find node: file."); + SearchRequest queryParentP = req("PARENT:" + folders(P).getNodeRef()); + searchQueryService.expectResultsFromQuery(queryParentP, testUser, + // primary child + fileInP.getName()); + + // when + STEP("Copy folderG with its content to folderT."); + Folder folderGCopy = folderG.copyTo(folderT); + + // then + STEP("Verify that search result for PARENT and folderS didn't change."); + searchQueryService.expectResultsFromQuery(queryParentS, testUser, + // primary child + folderG.getName()); + STEP("Verify that searching by PARENT and folderS/folderG will find nodes: folderH, folderP and file in P."); + searchQueryService.expectResultsFromQuery(queryParentG, testUser, + // primary child + folderH.getName(), + // secondary child + folders(P).getName()); + STEP("Verify that folderG was copied with secondary parent-child relationship and PARENT reflects that - search by folderT/folderGCopy should find nodes: folderH, folderP and file in P."); + SearchRequest queryParentGCopy = req("PARENT:" + folderGCopy.getNodeRef()); + searchQueryService.expectResultsFromQuery(queryParentGCopy, testUser, + // primary child + folderH.getName(), // name is the same as folderH-copy + // secondary child + folders(P).getName()); + STEP("Verify that this time searching by PARENT and folderP will find node: file."); + searchQueryService.expectResultsFromQuery(queryParentP, testUser, + // primary child + fileInP.getName()); + + STEP("Clean-up - delete folderG and folderT (with G's copy)."); + folders().delete(folderG); + folders().delete(folderT); + } +} diff --git a/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryPathIndexingTests.java b/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryPathIndexingTests.java new file mode 100644 index 0000000000..5c7e2a9129 --- /dev/null +++ b/tests/tas-elasticsearch/src/test/java/org/alfresco/elasticsearch/reindexing/NodesSecondaryPathIndexingTests.java @@ -0,0 +1,363 @@ +package org.alfresco.elasticsearch.reindexing; + +import static org.alfresco.elasticsearch.SearchQueryService.req; +import static org.alfresco.utility.report.log.Step.STEP; + +import org.alfresco.rest.search.SearchRequest; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.TestGroup; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Tests verifying live indexing of secondary children and PATH index in Elasticsearch. + */ +@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"}) // these are TAS E2E tests and use searchQueryService.expectResultsFromQuery for assertion +public class NodesSecondaryPathIndexingTests extends NodesSecondaryChildrenRelatedTests +{ + + private FileModel fileInP; + + /** + * Creates a user and a private site containing below hierarchy of folders. + *
+     * Site
+     * DL (Document Library)
+     *  += fA += fB += fC (folderC)
+     *         /     / |
+     *        +     +  +
+     *  += fK += fL += fM
+     *     |     +
+     *     +     |
+     *  += fX += fY += fZ
+     *  += fP += file -+ fA
+     *  += fQ
+     *  += fR
+     *  += fS
+     * 
+ * Parent += Child - primary parent-child relationship + * Parent +- Child - secondary parent-child relationship + */ + @BeforeClass(alwaysRun = true) + @Override + public void dataPreparation() + { + super.dataPreparation(); + + // given + STEP("Create few sets of nested folders in site's Document Library."); + folders().createNestedFolders(A, B, C); + folders().createNestedFolders(K, L, M); + folders().createNestedFolders(X, Y, Z); + folders().createFolder(P); + folders().createFolder(Q); + folders().createFolder(R); + folders().createFolder(S); + fileInP = folders(P).createRandomDocument(); + + STEP("Create few secondary parent-child relationships."); + folders(K).addSecondaryChild(folders(B)); + folders(X).addSecondaryChild(folders(K)); + folders(L).addSecondaryChildren(folders(C), folders(Y)); + folders(M).addSecondaryChild(folders(C)); + folders(A).addSecondaryChild(fileInP); + + STEP("Add to folderQ a secondary child folderR."); + folders(Q).addSecondaryChild(folders(R)); + + // when + STEP("Delete the secondary parent relationship between folderQ and FolderR."); + folders(Q).removeSecondaryChild(folders(R)); + } + + @Test(groups = TestGroup.SEARCH) + public void testSecondaryPathWithNodeHavingOneSecondaryChild() + { + // then + STEP("Verify that folderC can be found by secondary PATH using secondary parent folderM."); + SearchRequest query = req("PATH:\"//cm:" + folders(M).getName() + "//*\""); + searchQueryService.expectResultsFromQuery(query, testUser, + // secondary path + folders(C).getName()); + } + + @Test(groups = TestGroup.SEARCH) + public void testSecondaryPathWithNodeHavingOnePrimaryAndTwoSecondaryChildren() + { + // then + STEP("Verify that primary and secondary children of folderL can be found using PATH index."); + SearchRequest query = req("PATH:\"//cm:" + folders(L).getName() + "//*\""); + searchQueryService.expectResultsFromQuery(query, testUser, + // primary path + folders(M).getName(), + // secondary path + folders(C).getName(), + folders(Y).getName(), + folders(Z).getName() + ); + } + + @Test(groups = TestGroup.SEARCH) + public void testSecondaryPathWithNodeHavingDocumentAsSecondaryChild() + { + // then + STEP("Verify that a file being a secondary child of folderA can be found using PATH index."); + SearchRequest query = req("PATH:\"//cm:" + folders(A).getName() + "//*\""); + searchQueryService.expectResultsFromQuery(query, testUser, + // primary path + folders(B).getName(), + folders(C).getName(), + // secondary path + fileInP.getName()); + } + + @Test(groups = TestGroup.SEARCH) + public void testSecondaryPathWithNodeHavingComplexSecondaryRelationship() + { + // then + STEP("Verify that all secondary children of folderX can be found."); + SearchRequest query = req("PATH:\"//cm:" + folders(X).getName() + "//*\""); + searchQueryService.expectResultsFromQuery(query, testUser, + // primary path + folders(Y).getName(), + folders(Z).getName(), + // secondary path + folders(B).getName(), + folders(C).getName(), + folders(K).getName(), + folders(L).getName(), + folders(M).getName() + ); + } + + /** + * Verify that removing secondary parent-child relationship will result in updating ES index: PATH. + * Test changes below folders hierarchy: + *
+     * DL
+     *  += fQ
+     *     +
+     *     |
+     *  += fR
+     * 
+ * into: + *
+     * DL
+     *  += fQ
+     *  += fR
+     * 
+ */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryPathWithDeletedSecondaryRelationship() + { + // then + STEP("Verify that folderR cannot be found by PATH and folderQ anymore."); + SearchRequest query = req("PATH:\"//cm:" + folders(Q).getName() + "//*\""); + searchQueryService.expectNoResultsFromQuery(query, testUser); + } + + /** + * Verify that removing a node D (fD) having a secondary children relationship will remove the relationships and update PATH index in ES. + * Test changes below folders hierarchy from: + *
+     * DL
+     *  += fQ
+     *     +
+     *     |
+     *  += fE += fF
+     *     +
+     *     |
+     *  += fR
+     * 
+ * into: + *
+     * DL
+     *  += fQ
+     *  += fR
+     * 
+ */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryPathWithDeletedSecondaryParentNode() + { + // given + STEP("Create two nested folders (E and F) in Document Library."); + Folder folderE = folders().createFolder( "E"); + Folder folderF = folderE.createNestedFolder( "F"); + STEP("Make folderE a secondary children of folderQ and folderR a secondary children of folderE."); + folders(Q).addSecondaryChild(folderE); + folderE.addSecondaryChild(folders(R)); + + STEP("Verify that searching by PATH and folderQ will find nodes: folderE, folderF and folderR."); + SearchRequest queryPathQ = req("PATH:\"//cm:" + folders(Q).getName() + "//*\""); + searchQueryService.expectResultsFromQuery(queryPathQ, testUser, + // secondary path + folderE.getName(), + folderF.getName(), + folders(R).getName()); + STEP("Verify that searching by PATH and folderE will find its primary and secondary children: folderF and folderR."); + SearchRequest queryPathD = req("PATH:\"//cm:" + folderE.getName() + "//*\""); + searchQueryService.expectResultsFromQuery(queryPathD, testUser, + // primary path + folderF.getName(), + // secondary path + folders(R).getName()); + + // when + STEP("Delete folderE and verify that PATH was updated for nodes folderQ and folderR."); + folders().delete(folderE); + + // then + searchQueryService.expectNoResultsFromQuery(queryPathQ, testUser); + searchQueryService.expectNoResultsFromQuery(queryPathD, testUser); + } + + /** + * Verify that moving folder D (fD) containing secondary children from hierarchy: + *
+     * DL
+     *  += fQ += fD +- fP += file
+     *  += fR
+     * 
+ * to: + *
+     * DL
+     *  += fQ
+     *  += fR += fD +- fP += file
+     * 
+ * will update PATH index in ES. + */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryPathWithMovedSecondaryParentNode() + { + // given + STEP("Create folderD inside folderQ, and add folderP as a secondary child."); + Folder folderD = folders(Q).createNestedFolder( "D"); + folderD.addSecondaryChild(folders(P)); + folders(M).addSecondaryChild(folderD); + + STEP("Verify that searching by PATH and folderD will find nodes: folderP and file."); + SearchRequest queryPathD = req("PATH:\"//cm:" + folderD.getName() + "//*\""); + searchQueryService.expectResultsFromQuery(queryPathD, testUser, + // secondary path + folders(P).getName(), + fileInP.getName()); + STEP("Verify that searching by PATH and folderQ will find nodes: folderD, folderP and file."); + SearchRequest queryPathQ = req("PATH:\"//cm:" + folders(Q).getName() + "//*\""); + searchQueryService.expectResultsFromQuery(queryPathQ, testUser, + // primary path + folderD.getName(), + // secondary path + folders(P).getName(), + fileInP.getName()); + + // when + STEP("Move folderD from folderQ to folderR."); + folderD.moveTo(folders(R)); + + // then + STEP("Verify that search result for PATH and folderD didn't change."); + searchQueryService.expectResultsFromQuery(queryPathD, testUser, + // secondary path + folders(P).getName(), + fileInP.getName()); + STEP("Verify that searching by PATH and folderQ doesn't return any node anymore."); + searchQueryService.expectNoResultsFromQuery(queryPathQ, testUser); + STEP("Verify that searching by PATH and folderR will find nodes: folderD, folderP and file."); + SearchRequest queryPathR = req("PATH:\"//cm:" + folders(R).getName() + "//*\""); + searchQueryService.expectResultsFromQuery(queryPathR, testUser, + // primary path + folderD.getName(), + // secondary path + folders(P).getName(), + fileInP.getName()); + + STEP("Clean-up - delete folderD."); + folders().delete(folderD); + } + + /** + * Verify that copying folder will also result in copying folder's secondary children and update PATH index in ES. + * Test changes below folders hierarchy: + *
+     * DL
+     *  += fS += fG += fH
+     *         +
+     *        /
+     *  += fP += file
+     *  += fT
+     * 
+ * into: + *
+     * DL
+     *  += fS += fG += fH
+     *         +
+     *        /
+     *  += fP += file
+     *        \
+     *         +
+     *  += fT += fG-c += fH-c
+     *  
+ */ + @Test(groups = TestGroup.SEARCH) + public void testSecondaryParentWithCopiedSecondaryParentNode() + { + // given + STEP("Create nested folders (G and H) inside folderS and folderT in Document Library. Make folderP a secondary child of folderG."); + Folder folderG = folders(S).createNestedFolder( "G"); + Folder folderH = folderG.createNestedFolder("H"); + Folder folderT = folders().createFolder("T"); + folderG.addSecondaryChild(folders(P)); + + STEP("Verify that searching by PATH and folderG will find nodes: folderH, folderP and file."); + SearchRequest queryPathG = req("PATH:\"//cm:" + folderG.getName() + "//*\""); + searchQueryService.expectResultsFromQuery(queryPathG, testUser, + // primary path + folderH.getName(), + // secondary path + folders(P).getName(), + fileInP.getName()); + STEP("Verify that searching by PATH and folderS will find nodes: folderG, folderH, folderP and file."); + SearchRequest queryPathS = req("PATH:\"//cm:" + folders(S).getName() + "//*\""); + searchQueryService.expectResultsFromQuery(queryPathS, testUser, + // primary path + folderG.getName(), + folderH.getName(), + // secondary path + folders(P).getName(), + fileInP.getName()); + + // when + STEP("Copy folderG with its content to folderT."); + Folder folderGCopy = folderG.copyTo(folderT); + + // then + STEP("Verify that search result for PATH and folderS didn't change."); + searchQueryService.expectResultsFromQuery(queryPathS, testUser, + // primary path + folderH.getName(), + folderG.getName(), + // secondary path + folders(P).getName(), + fileInP.getName()); + STEP("Verify that searching by PATH and folderS/folderG will find nodes: folderH, folderP and file in P."); + SearchRequest queryPathSG = req("PATH:\"//cm:" + folders(S).getName() + "/cm:" + folderG.getName() + "//*\""); + searchQueryService.expectResultsFromQuery(queryPathSG, testUser, + // primary path + folderH.getName(), + // secondary path + folders(P).getName(), + fileInP.getName()); + STEP("Verify that folderG was copied with secondary parent-child relationship and PATH reflects that - search by folderT/folderGCopy should find nodes: folderH, folderP and file in P."); + SearchRequest queryPathTGCopy = req("PATH:\"//cm:" + folderT.getName() + "/cm:" + folderGCopy.getName() + "//*\""); + searchQueryService.expectResultsFromQuery(queryPathTGCopy, testUser, + // primary path + folderH.getName(), // the same name as folderH-copy + // secondary path + folders(P).getName(), + fileInP.getName()); + + STEP("Clean-up - delete folderG and folderT (with G's copy)."); + folders().delete(folderG); + folders().delete(folderT); + } +}