diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/model/RestCategoryModel.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/model/RestCategoryModel.java index e25c56e22a..337c11f2da 100644 --- a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/model/RestCategoryModel.java +++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/model/RestCategoryModel.java @@ -52,6 +52,11 @@ This must be unique within the parent category. */ private long count; + /** + The path to this category. + */ + private String path; + public String getId() { return this.id; @@ -102,6 +107,14 @@ This must be unique within the parent category. this.count = count; } + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + @Override public boolean equals(Object o) { @@ -126,6 +139,7 @@ This must be unique within the parent category. ", parentId='" + parentId + '\'' + ", hasChildren=" + hasChildren + ", count=" + count + + ", path=" + path + '}'; } diff --git a/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/categories/CategoriesPathTests.java b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/categories/CategoriesPathTests.java new file mode 100644 index 0000000000..55d986276f --- /dev/null +++ b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/categories/CategoriesPathTests.java @@ -0,0 +1,223 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2023 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ + +package org.alfresco.rest.categories; + +import static org.alfresco.utility.data.RandomData.getRandomName; +import static org.alfresco.utility.report.log.Step.STEP; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.OK; +import static org.testng.Assert.assertTrue; + +import org.alfresco.dataprep.CMISUtil; +import org.alfresco.rest.model.RestCategoryLinkBodyModel; +import org.alfresco.rest.model.RestCategoryModel; +import org.alfresco.rest.model.RestCategoryModelsCollection; +import org.alfresco.utility.Utility; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FolderModel; +import org.alfresco.utility.model.SiteModel; +import org.alfresco.utility.model.TestGroup; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class CategoriesPathTests extends CategoriesRestTest +{ + private FileModel file; + private RestCategoryModel category; + + @BeforeClass(alwaysRun = true) + @Override + public void dataPreparation() throws Exception + { + STEP("Create user and site"); + user = dataUser.createRandomTestUser(); + SiteModel site = dataSite.usingUser(user).createPublicRandomSite(); + + STEP("Create a folder, file in it and a category"); + FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder(); + file = dataContent.usingUser(user).usingResource(folder).createContent(CMISUtil.DocumentType.TEXT_PLAIN); + category = prepareCategoryUnderRoot(); + + STEP("Wait for indexing to complete"); + Utility.sleep(1000, 60000, () -> restClient.authenticateUser(user) + .withCoreAPI() + .usingCategory(category) + .include(INCLUDE_PATH_PARAM) + .getCategory() + .assertThat() + .field(FIELD_PATH) + .isNotNull()); + } + + /** + * Verify path for a category got by ID. + */ + @Test(groups = { TestGroup.REST_API }) + public void testGetCategoryById_includePath() + { + STEP("Get category and verify if path is a general path for categories"); + final RestCategoryModel actualCategory = restClient.authenticateUser(user) + .withCoreAPI() + .usingCategory(category) + .include(INCLUDE_PATH_PARAM) + .getCategory(); + + restClient.assertStatusCodeIs(OK); + actualCategory.assertThat().field(FIELD_ID).is(category.getId()); + actualCategory.assertThat().field(FIELD_PATH).is("/categories/General"); + } + + /** + * Verify path for category. + */ + @Test(groups = { TestGroup.REST_API }) + public void testGetCategories_includePath() + { + STEP("Get few categories and verify its paths"); + final RestCategoryModel parentCategory = createCategoryModelWithId(ROOT_CATEGORY_ID); + final RestCategoryModelsCollection actualCategories = restClient.authenticateUser(user) + .withCoreAPI() + .usingCategory(parentCategory) + .include(INCLUDE_PATH_PARAM) + .getCategoryChildren(); + + restClient.assertStatusCodeIs(OK); + assertTrue(actualCategories.getEntries().stream() + .map(RestCategoryModel::onModel) + .allMatch(cat -> cat.getPath().equals("/categories/General"))); + } + + /** + * Verify path for child category. + */ + @Test(groups = { TestGroup.REST_API }) + public void testGetChildCategory_includePath() + { + STEP("Create parent and child categories"); + final RestCategoryModel parentCategory = prepareCategoryUnderRoot(); + final RestCategoryModel childCategory = prepareCategoryUnder(parentCategory); + + STEP("Verify path for created child categories"); + final RestCategoryModelsCollection actualCategories = restClient.authenticateUser(user) + .withCoreAPI() + .usingCategory(parentCategory) + .include(INCLUDE_PATH_PARAM) + .getCategoryChildren(); + + restClient.assertStatusCodeIs(OK); + actualCategories.getEntries().stream() + .map(RestCategoryModel::onModel) + .forEach(cat -> cat.assertThat().field(FIELD_PATH).is("/categories/General/" + parentCategory.getName())); + } + + /** + * Create category and verify that it has a path. + */ + @Test(groups = { TestGroup.REST_API }) + public void testCreateCategory_includingPath() + { + STEP("Create a category under root and verify if path is a general path for categories"); + final String categoryName = getRandomName("Category"); + final RestCategoryModel rootCategory = createCategoryModelWithId(ROOT_CATEGORY_ID); + final RestCategoryModel aCategory = createCategoryModelWithName(categoryName); + final RestCategoryModel createdCategory = restClient.authenticateUser(dataUser.getAdminUser()) + .withCoreAPI() + .include(INCLUDE_PATH_PARAM) + .usingCategory(rootCategory) + .createSingleCategory(aCategory); + + restClient.assertStatusCodeIs(CREATED); + createdCategory.assertThat().field(FIELD_NAME).is(categoryName); + createdCategory.assertThat().field(FIELD_PATH).is("/categories/General"); + } + + /** + * Update category and verify that it has a path. + */ + @Test(groups = { TestGroup.REST_API }) + public void testUpdateCategory_includePath() + { + STEP("Update linked category and verify if path is a general path for categories"); + final String categoryNewName = getRandomName("NewCategoryName"); + final RestCategoryModel fixedCategoryModel = createCategoryModelWithName(categoryNewName); + final RestCategoryModel updatedCategory = restClient.authenticateUser(dataUser.getAdminUser()) + .withCoreAPI() + .usingCategory(category) + .include(INCLUDE_PATH_PARAM) + .updateCategory(fixedCategoryModel); + + restClient.assertStatusCodeIs(OK); + updatedCategory.assertThat().field(FIELD_ID).is(category.getId()); + updatedCategory.assertThat().field(FIELD_PATH).is("/categories/General"); + } + + /** + * Link node to categories and verify that they have path. + */ + @Test(groups = { TestGroup.REST_API }) + public void testLinkNodeToCategories_includePath() + { + STEP("Link node to categories and verify if path is a general path"); + final RestCategoryLinkBodyModel categoryLinkModel = createCategoryLinkModelWithId(category.getId()); + final RestCategoryModel linkedCategory = restClient.authenticateUser(dataUser.getAdminUser()) + .withCoreAPI() + .usingNode(file) + .include(INCLUDE_PATH_PARAM) + .linkToCategory(categoryLinkModel); + + restClient.assertStatusCodeIs(CREATED); + linkedCategory.assertThat().field(FIELD_ID).is(category.getId()); + linkedCategory.assertThat().field(FIELD_PATH).is("/categories/General"); + } + + /** + * List categories for given node and verify that they have a path. + */ + @Test(groups = { TestGroup.REST_API }) + public void testListCategoriesForNode_includePath() + { + STEP("Link file to category"); + final RestCategoryLinkBodyModel categoryLink = createCategoryLinkModelWithId(category.getId()); + final RestCategoryModel linkedCategory = restClient.authenticateUser(dataUser.getAdminUser()) + .withCoreAPI() + .usingNode(file) + .include(INCLUDE_PATH_PARAM) + .linkToCategory(categoryLink); + + STEP("Get linked category and verify if path is a general path"); + final RestCategoryModelsCollection linkedCategories = restClient.authenticateUser(dataUser.getAdminUser()) + .withCoreAPI() + .usingNode(file) + .include(INCLUDE_PATH_PARAM) + .getLinkedCategories(); + + restClient.assertStatusCodeIs(OK); + linkedCategories.assertThat().entriesListCountIs(1); + linkedCategories.getEntries().get(0).onModel().assertThat().field(FIELD_ID).is(category.getId()); + linkedCategories.getEntries().get(0).onModel().assertThat().field(FIELD_PATH).is("/categories/General"); + } +} diff --git a/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/categories/CategoriesRestTest.java b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/categories/CategoriesRestTest.java index c98c9802f7..792cec4b3e 100644 --- a/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/categories/CategoriesRestTest.java +++ b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/categories/CategoriesRestTest.java @@ -46,6 +46,7 @@ import org.testng.annotations.BeforeClass; abstract class CategoriesRestTest extends RestTest { protected static final String INCLUDE_COUNT_PARAM = "count"; + protected static final String INCLUDE_PATH_PARAM = "path"; protected static final String ROOT_CATEGORY_ID = "-root-"; protected static final String CATEGORY_NAME_PREFIX = "CategoryName"; protected static final String FIELD_NAME = "name"; @@ -53,6 +54,7 @@ abstract class CategoriesRestTest extends RestTest protected static final String FIELD_PARENT_ID = "parentId"; protected static final String FIELD_HAS_CHILDREN = "hasChildren"; protected static final String FIELD_COUNT = "count"; + protected static final String FIELD_PATH = "path"; protected UserModel user; diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/CategoriesImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/CategoriesImpl.java index 53f9eddab8..c733d71ab1 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/CategoriesImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/CategoriesImpl.java @@ -111,6 +111,11 @@ public class CategoriesImpl implements Categories category.setCount(categoriesCount.getOrDefault(category.getId(), 0)); } + if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH)) + { + category.setPath(getCategoryPath(category)); + } + return category; } @@ -128,6 +133,10 @@ public class CategoriesImpl implements Categories { category.setCount(0); } + if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH)) + { + category.setPath(getCategoryPath(category)); + } }) .collect(Collectors.toList()); } @@ -136,11 +145,18 @@ public class CategoriesImpl implements Categories public List getCategoryChildren(final StoreRef storeRef, final String parentCategoryId, final Parameters parameters) { final NodeRef parentNodeRef = getCategoryNodeRef(storeRef, parentCategoryId); - final List categories = nodeService.getChildAssocs(parentNodeRef).stream() - .filter(ca -> ContentModel.ASSOC_SUBCATEGORIES.equals(ca.getTypeQName())) - .map(ChildAssociationRef::getChildRef) - .map(this::mapToCategory) - .collect(Collectors.toList()); + final List categories = nodeService.getChildAssocs(parentNodeRef) + .stream() + .filter(ca -> ContentModel.ASSOC_SUBCATEGORIES.equals(ca.getTypeQName())) + .map(ChildAssociationRef::getChildRef) + .map(this::mapToCategory) + .peek(category -> { + if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH)) + { + category.setPath(getCategoryPath(category)); + } + }) + .collect(Collectors.toList()); if (parameters.getInclude().contains(INCLUDE_COUNT_PARAM)) { @@ -170,6 +186,11 @@ public class CategoriesImpl implements Categories category.setCount(categoriesCount.getOrDefault(category.getId(), 0)); } + if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH)) + { + category.setPath(getCategoryPath(category)); + } + return category; } @@ -200,7 +221,16 @@ public class CategoriesImpl implements Categories } final Collection actualCategories = DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, currentCategories); - return actualCategories.stream().map(this::mapToCategory).collect(Collectors.toList()); + return actualCategories + .stream() + .map(this::mapToCategory) + .peek(category -> { + if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH)) + { + category.setPath(getCategoryPath(category)); + } + }) + .collect(Collectors.toList()); } @Override @@ -230,7 +260,16 @@ public class CategoriesImpl implements Categories linkNodeToCategories(contentNodeRef, categoryNodeRefs); - return categoryNodeRefs.stream().map(this::mapToCategory).collect(Collectors.toList()); + return categoryNodeRefs + .stream() + .map(this::mapToCategory) + .peek(category -> { + if (parameters.getInclude().contains(Nodes.PARAM_INCLUDE_PATH)) + { + category.setPath(getCategoryPath(category)); + } + }) + .collect(Collectors.toList()); } @Override @@ -475,4 +514,16 @@ public class CategoriesImpl implements Categories .stream() .collect(Collectors.toMap(pair -> pair.getFirst().toString().replace(idPrefix, StringUtils.EMPTY), Pair::getSecond)); } + + /** + * Get path for a given category in human-readable form. + * + * @param category Category to provide path for. + * @return Path for a category in human-readable form. + */ + private String getCategoryPath(final Category category) + { + final NodeRef categoryNodeRef = nodes.getNode(category.getId()).getNodeRef(); + return nodeService.getPath(categoryNodeRef).toDisplayPath(nodeService, permissionService); + } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/Category.java b/remote-api/src/main/java/org/alfresco/rest/api/model/Category.java index 812d362a96..d65b22b8a8 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/model/Category.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/Category.java @@ -35,6 +35,7 @@ public class Category private String parentId; private boolean hasChildren; private Integer count; + private String path; public String getId() { @@ -91,6 +92,14 @@ public class Category this.count = count; } + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + @Override public boolean equals(Object o) { @@ -100,19 +109,20 @@ public class Category return false; Category category = (Category) o; return hasChildren == category.hasChildren && Objects.equals(id, category.id) && Objects.equals(name, category.name) && Objects.equals(parentId, category.parentId) - && Objects.equals(count, category.count); + && Objects.equals(count, category.count) && Objects.equals(path, category.path); } @Override public int hashCode() { - return Objects.hash(id, name, parentId, hasChildren, count); + return Objects.hash(id, name, parentId, hasChildren, count, path); } @Override public String toString() { - return "Category{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", parentId='" + parentId + '\'' + ", hasChildren=" + hasChildren + ", count=" + count + '}'; + return "Category{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", parentId='" + parentId + '\'' + ", hasChildren=" + hasChildren + + ", count=" + count + ", path=" + path + '}'; } public static Builder builder() @@ -127,6 +137,7 @@ public class Category private String parentId; private boolean hasChildren; private Integer count; + private String path; public Builder id(String id) { @@ -158,6 +169,12 @@ public class Category return this; } + public Builder path(String path) + { + this.path = path; + return this; + } + public Category create() { final Category category = new Category(); @@ -166,6 +183,7 @@ public class Category category.setParentId(parentId); category.setHasChildren(hasChildren); category.setCount(count); + category.setPath(path); return category; } } diff --git a/remote-api/src/test/java/org/alfresco/rest/api/impl/CategoriesImplTest.java b/remote-api/src/test/java/org/alfresco/rest/api/impl/CategoriesImplTest.java index 6d9dc98410..1ce88415ce 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/impl/CategoriesImplTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/impl/CategoriesImplTest.java @@ -60,6 +60,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; import org.alfresco.model.ContentModel; +import org.alfresco.repo.transfer.PathHelper; import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.model.Category; import org.alfresco.rest.api.model.Node; @@ -71,6 +72,7 @@ import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.CategoryService; import org.alfresco.service.cmr.security.AccessStatus; @@ -100,6 +102,9 @@ public class CategoriesImplTest private static final Category CATEGORY = createDefaultCategory(); private static final String CONTENT_NODE_ID = "content-node-id"; private static final NodeRef CONTENT_NODE_REF = createNodeRefWithId(CONTENT_NODE_ID); + private static final String MOCK_ROOT_LEVEL = "/{mockRootLevel}"; + private static final String MOCK_CHILD_LEVEL = "/{mockChild}"; + private static final String MOCK_CATEGORY_PATH = "//" + MOCK_ROOT_LEVEL + "//" + MOCK_CHILD_LEVEL; @Mock private Nodes nodesMock; @@ -252,6 +257,27 @@ public class CategoriesImplTest .isEqualTo(1); } + @Test + public void testGetCategoryById_includePath() + { + final QName categoryQName = createCmQNameOf(CATEGORY_NAME); + final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID); + final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName); + given(nodesMock.getNode(any())).willReturn(createNode()); + given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation); + given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH)); + given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath()); + + // when + final Category actualCategory = objectUnderTest.getCategoryById(CATEGORY_ID, parametersMock); + + assertThat(actualCategory) + .isNotNull() + .extracting(Category::getPath) + .isNotNull() + .isEqualTo(MOCK_CATEGORY_PATH); + } + @Test public void testGetCategoryById_notACategory() { @@ -479,6 +505,36 @@ public class CategoriesImplTest .isEqualTo(0); } + @Test + public void testCreateCategory_includePath() + { + final QName categoryQName = createCmQNameOf(CATEGORY_NAME); + final NodeRef categoryNodeRef = createNodeRefWithId(CATEGORY_ID); + final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID); + final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName); + given(nodesMock.validateNode(PARENT_ID)).willReturn(parentCategoryNodeRef); + given(categoryServiceMock.createCategory(parentCategoryNodeRef, CATEGORY_NAME)).willReturn(categoryNodeRef); + given(nodesMock.getNode(any())).willReturn(createNode()); + given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation); + given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH)); + given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath()); + final List categoryModels = new ArrayList<>(prepareCategories()); + + // when + final List actualCreatedCategories = objectUnderTest.createSubcategories(PARENT_ID, categoryModels, parametersMock); + + then(categoryServiceMock).should().createCategory(any(), any()); + then(categoryServiceMock).shouldHaveNoMoreInteractions(); + + assertThat(actualCreatedCategories) + .isNotNull() + .hasSize(1) + .element(0) + .extracting(Category::getPath) + .isNotNull() + .isEqualTo(MOCK_CATEGORY_PATH); + } + @Test public void testCreateCategories_noPermissions() { @@ -628,6 +684,30 @@ public class CategoriesImplTest .isEqualTo(List.of(0, 2, 0)); } + @Test + public void testGetCategoryChildren_includePath() + { + final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID); + given(nodesMock.validateNode(PARENT_ID)).willReturn(parentCategoryNodeRef); + given(nodesMock.isSubClass(parentCategoryNodeRef, ContentModel.TYPE_CATEGORY, false)).willReturn(true); + final int childrenCount = 3; + final List childAssociationRefMocks = prepareChildAssocMocks(childrenCount, parentCategoryNodeRef); + given(nodeServiceMock.getChildAssocs(parentCategoryNodeRef)).willReturn(childAssociationRefMocks); + childAssociationRefMocks.forEach(this::prepareCategoryNodeMocks); + given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH)); + given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath()); + + // when + final List actualCategoryChildren = objectUnderTest.getCategoryChildren(PARENT_ID, parametersMock); + + assertThat(actualCategoryChildren) + .isNotNull() + .hasSize(3) + .extracting(Category::getPath) + .isNotNull() + .isEqualTo(List.of(MOCK_CATEGORY_PATH, MOCK_CATEGORY_PATH, MOCK_CATEGORY_PATH)); + } + @Test public void testGetCategoryChildren_noChildren() { @@ -751,6 +831,32 @@ public class CategoriesImplTest .isEqualTo(1); } + @Test + public void testUpdateCategoryById_includePath() + { + final String categoryNewName = "categoryNewName"; + final Category fixedCategory = createCategoryOnlyWithName(categoryNewName); + // simulate path provided by client to check if it will be ignored + fixedCategory.setPath("/test/TestCat"); + final QName categoryQName = createCmQNameOf(CATEGORY_NAME); + final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID); + final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName); + given(nodesMock.getNode(any())).willReturn(createNode(categoryNewName)); + given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation); + given(nodeServiceMock.moveNode(any(), any(), any(), any())).willReturn(createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, createCmQNameOf(categoryNewName))); + given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH)); + given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath()); + + // when + final Category actualCategory = objectUnderTest.updateCategoryById(CATEGORY_ID, fixedCategory, parametersMock); + + assertThat(actualCategory) + .isNotNull() + .extracting(Category::getPath) + .isNotNull() + .isEqualTo(MOCK_CATEGORY_PATH); + } + @Test public void testUpdateCategoryById_noPermission() { @@ -918,6 +1024,7 @@ public class CategoriesImplTest then(nodeServiceMock).should().getParentAssocs(categoryParentNodeRef); then(nodeServiceMock).shouldHaveNoMoreInteractions(); final List expectedLinkedCategories = List.of(CATEGORY); + expectedLinkedCategories.get(0).setPath(null); assertThat(actualLinkedCategories) .isNotNull().usingRecursiveComparison() .isEqualTo(expectedLinkedCategories); @@ -984,6 +1091,36 @@ public class CategoriesImplTest .isEqualTo(expectedLinkedCategories); } + @Test + public void testLinkNodeToCategories_includePath() + { + final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID); + final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF); + given(nodesMock.getNode(any())).willReturn(createNode()); + given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation); + given(nodeServiceMock.hasAspect(any(), any())).willReturn(true); + given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH)); + given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath()); + + // when + final List actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY), parametersMock); + + then(nodesMock).should(times(2)).getNode(CATEGORY_ID); + then(nodeServiceMock).should().getChildAssocs(CATEGORY_NODE_REF, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false); + then(nodeServiceMock).should().getPrimaryParent(CATEGORY_NODE_REF); + then(nodeServiceMock).should().getParentAssocs(CATEGORY_NODE_REF); + then(nodeServiceMock).should().hasAspect(CONTENT_NODE_REF, ContentModel.ASPECT_GEN_CLASSIFIABLE); + then(nodeServiceMock).should().getProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES); + final Serializable expectedCategories = (Serializable) List.of(CATEGORY_NODE_REF); + then(nodeServiceMock).should().setProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES, expectedCategories); + then(nodeServiceMock).should().getParentAssocs(categoryParentNodeRef); + final List expectedLinkedCategories = List.of(CATEGORY); + expectedLinkedCategories.get(0).setPath(MOCK_CATEGORY_PATH); + assertThat(actualLinkedCategories) + .isNotNull().usingRecursiveComparison() + .isEqualTo(expectedLinkedCategories); + } + @Test public void testLinkNodeToCategories_withPreviouslyLinkedCategories() { @@ -1168,11 +1305,46 @@ public class CategoriesImplTest then(nodeServiceMock).should().getParentAssocs(categoryParentNodeRef); then(nodeServiceMock).shouldHaveNoMoreInteractions(); final List expectedCategories = List.of(CATEGORY); + expectedCategories.get(0).setPath(null); assertThat(actualCategories) .isNotNull().usingRecursiveComparison() .isEqualTo(expectedCategories); } + @Test + public void testListCategoriesForNode_includePath() + { + final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID); + final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF); + given(nodeServiceMock.getProperty(any(), eq(ContentModel.PROP_CATEGORIES))).willReturn((Serializable) List.of(CATEGORY_NODE_REF)); + given(nodesMock.getNode(any())).willReturn(createNode()); + given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation); + given(parametersMock.getInclude()).willReturn(List.of(Nodes.PARAM_INCLUDE_PATH)); + given(nodeServiceMock.getPath(any())).willReturn(mockCategoryPath()); + + // when + final List actualCategories = objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID, parametersMock); + + then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID); + then(permissionServiceMock).should().hasReadPermission(CONTENT_NODE_REF); + then(permissionServiceMock).shouldHaveNoMoreInteractions(); + then(typeConstraint).should().matches(CONTENT_NODE_REF); + then(typeConstraint).shouldHaveNoMoreInteractions(); + then(nodesMock).should(times(2)).getNode(CATEGORY_ID); + then(nodesMock).shouldHaveNoMoreInteractions(); + then(nodeServiceMock).should().getProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES); + then(nodeServiceMock).should().getChildAssocs(CATEGORY_NODE_REF, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false); + then(nodeServiceMock).should().getPrimaryParent(CATEGORY_NODE_REF); + then(nodeServiceMock).should().getParentAssocs(categoryParentNodeRef); + then(nodeServiceMock).should().getPath(any()); + then(nodeServiceMock).shouldHaveNoMoreInteractions(); + final List expectedCategories = List.of(CATEGORY); + expectedCategories.get(0).setPath(MOCK_CATEGORY_PATH); + assertThat(actualCategories) + .isNotNull().usingRecursiveComparison() + .isEqualTo(expectedCategories); + } + @Test public void testListCategoriesForNode_withInvalidNodeId() { @@ -1329,4 +1501,13 @@ public class CategoriesImplTest { return new ChildAssociationRef(ContentModel.ASSOC_SUBCATEGORIES, parentNode, childNodeName, childNode); } + + private Path mockCategoryPath() + { + final Path mockPath = new Path(); + mockPath.append(PathHelper.stringToPath(MOCK_ROOT_LEVEL)); + mockPath.append(PathHelper.stringToPath(MOCK_CHILD_LEVEL)); + mockPath.append(PathHelper.stringToPath("/")); + return mockPath; + } }