ACS-4034: Assign a piece of content to a category (#1655)

* ACS-4034: Assign a piece of content to a category - POST /nodes/{nodeId}/category-links
- added also one TAS test for update category covering repeated name case
This commit is contained in:
krdabrowski
2023-01-09 16:01:57 +01:00
committed by GitHub
parent 6a240b722b
commit e4e4ed214a
10 changed files with 847 additions and 21 deletions

View File

@@ -25,16 +25,16 @@ public class RestCategoryLinkBodyModel extends TestModel implements IRestModel<R
*/
@JsonProperty(required = true)
private String id;
private String categoryId;
public String getId()
public String getCategoryId()
{
return this.id;
return categoryId;
}
public void setId(String id)
public void setCategoryId(String categoryId)
{
this.id = id;
}
this.categoryId = categoryId;
}
}

View File

@@ -2,7 +2,7 @@
* #%L
* alfresco-tas-restapi
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* 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
@@ -26,6 +26,7 @@
package org.alfresco.rest.requests;
import static org.alfresco.rest.core.JsonBodyGenerator.arrayToJson;
import static org.alfresco.rest.requests.RuleSettings.IS_INHERITANCE_ENABLED;
import static org.springframework.http.HttpMethod.PUT;
@@ -33,6 +34,7 @@ import javax.json.JsonArrayBuilder;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import io.restassured.http.ContentType;
import org.alfresco.rest.core.JsonBodyGenerator;
@@ -41,6 +43,9 @@ import org.alfresco.rest.core.RestResponse;
import org.alfresco.rest.core.RestWrapper;
import org.alfresco.rest.exception.JsonToModelConversionException;
import org.alfresco.rest.model.RestActionDefinitionModelsCollection;
import org.alfresco.rest.model.RestCategoryLinkBodyModel;
import org.alfresco.rest.model.RestCategoryModel;
import org.alfresco.rest.model.RestCategoryModelsCollection;
import org.alfresco.rest.model.RestCommentModel;
import org.alfresco.rest.model.RestCommentModelsCollection;
import org.alfresco.rest.model.RestNodeAssocTargetModel;
@@ -1106,4 +1111,28 @@ public class Node extends ModelRequest<Node>
RestRequest request = RestRequest.requestWithBody(HttpMethod.POST, body.toJson(), "nodes/{nodeId}/rule-executions", repoModel.getNodeRef());
return restWrapper.processModel(RestRuleExecutionModel.class, request);
}
/**
* Link content to category performing POST call on "/nodes/{nodeId}/category-links"
*
* @param categoryLink - contains category ID
* @return linked to category
*/
public RestCategoryModel linkToCategory(RestCategoryLinkBodyModel categoryLink)
{
RestRequest request = RestRequest.requestWithBody(HttpMethod.POST, categoryLink.toJson(), "nodes/{nodeId}/category-links", repoModel.getNodeRef());
return restWrapper.processModel(RestCategoryModel.class, request);
}
/**
* Link content to many categories performing POST call on "/nodes/{nodeId}/category-links"
*
* @param categoryLinks - contains categories IDs
* @return linked to categories
*/
public RestCategoryModelsCollection linkToCategories(List<RestCategoryLinkBodyModel> categoryLinks)
{
RestRequest request = RestRequest.requestWithBody(HttpMethod.POST, arrayToJson(categoryLinks), "nodes/{nodeId}/category-links", repoModel.getNodeRef());
return restWrapper.processModels(RestCategoryModelsCollection.class, request);
}
}

View File

@@ -0,0 +1,350 @@
/*
* #%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 <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.categories;
import static org.alfresco.utility.constants.UserRole.SiteManager;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.METHOD_NOT_ALLOWED;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import javax.json.Json;
import java.util.Collections;
import java.util.List;
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.rest.model.RestNodeModel;
import org.alfresco.rest.model.RestTagModel;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.RepoTestModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.apache.commons.lang3.StringUtils;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class LinkToCategoriesTests extends CategoriesRestTest
{
private static final String ASPECTS_FIELD = "aspectNames";
private static final String PROPERTIES_FIELD = "properties";
private UserModel user;
private SiteModel site;
private FolderModel folder;
private FileModel file;
private RestCategoryModel category;
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
STEP("Create user and a site");
user = dataUser.createRandomTestUser();
site = dataSite.usingUser(user).createPublicRandomSite();
}
@BeforeMethod(alwaysRun = true)
public void setUp()
{
STEP("Create a folder, file in it and a category under root");
folder = dataContent.usingUser(user).usingSite(site).createFolder();
file = dataContent.usingUser(user).usingResource(folder).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
category = prepareCategoryUnderRoot();
}
/**
* Link content to category and verify if this category is present in node's properties
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToCategory()
{
STEP("Check if file is not linked to any category");
RestNodeModel fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(file).getNode();
fileNode.assertThat().field(ASPECTS_FIELD).notContains("cm:generalclassifiable");
fileNode.assertThat().field(PROPERTIES_FIELD).notContains("cm:categories");
STEP("Link content to created category and expect 201");
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkWithId(category.getId());
final RestCategoryModel linkedCategory = restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategory(categoryLink);
restClient.assertStatusCodeIs(CREATED);
linkedCategory.assertThat().isEqualTo(category);
STEP("Verify if category is present in file metadata");
fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(file).getNode();
fileNode.assertThat().field(ASPECTS_FIELD).contains("cm:generalclassifiable");
fileNode.assertThat().field(PROPERTIES_FIELD).contains("cm:categories");
fileNode.assertThat().field(PROPERTIES_FIELD).contains(category.getId());
}
/**
* Link content to two categories and verify if both are present in node's properties
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToMultipleCategories()
{
STEP("Check if file is not linked to any category");
RestNodeModel fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(file).getNode();
fileNode.assertThat().field(ASPECTS_FIELD).notContains("cm:generalclassifiable");
fileNode.assertThat().field(PROPERTIES_FIELD).notContains("cm:categories");
STEP("Create second category under root");
final RestCategoryModel secondCategory = prepareCategoryUnderRoot();
STEP("Link content to created categories and expect 201");
final List<RestCategoryLinkBodyModel> categoryLinks = List.of(
createCategoryLinkWithId(category.getId()),
createCategoryLinkWithId(secondCategory.getId())
);
final RestCategoryModelsCollection linkedCategories = restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategories(categoryLinks);
restClient.assertStatusCodeIs(CREATED);
linkedCategories.getEntries().get(0).onModel().assertThat().isEqualTo(category);
linkedCategories.getEntries().get(1).onModel().assertThat().isEqualTo(secondCategory);
STEP("Verify if both categories are present in file metadata");
fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(file).getNode();
fileNode.assertThat().field(ASPECTS_FIELD).contains("cm:generalclassifiable");
fileNode.assertThat().field(PROPERTIES_FIELD).contains("cm:categories");
fileNode.assertThat().field(PROPERTIES_FIELD).contains(category.getId());
fileNode.assertThat().field(PROPERTIES_FIELD).contains(secondCategory.getId());
}
/**
* Link content, which already has some linked category to new ones and verify if all categories are present in node's properties
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToCategory_usingContentWithAlreadyLinkedCategories()
{
STEP("Link content to created category");
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkWithId(category.getId());
restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategory(categoryLink);
restClient.assertStatusCodeIs(CREATED);
STEP("Create second and third category under root, link content to them and expect 201");
final RestCategoryModel secondCategory = prepareCategoryUnderRoot();
final RestCategoryModel thirdCategory = prepareCategoryUnderRoot();
final List<RestCategoryLinkBodyModel> categoryLinks = List.of(
createCategoryLinkWithId(secondCategory.getId()),
createCategoryLinkWithId(thirdCategory.getId())
);
final RestCategoryModelsCollection linkedCategories = restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategories(categoryLinks);
restClient.assertStatusCodeIs(CREATED);
linkedCategories.assertThat().entriesListCountIs(2);
linkedCategories.getEntries().get(0).onModel().assertThat().isEqualTo(secondCategory);
linkedCategories.getEntries().get(1).onModel().assertThat().isEqualTo(thirdCategory);
STEP("Verify if all three categories are present in file metadata");
final RestNodeModel fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(file).getNode();
fileNode.assertThat().field(PROPERTIES_FIELD).contains("cm:categories");
fileNode.assertThat().field(PROPERTIES_FIELD).contains(category.getId());
fileNode.assertThat().field(PROPERTIES_FIELD).contains(secondCategory.getId());
fileNode.assertThat().field(PROPERTIES_FIELD).contains(thirdCategory.getId());
}
/**
* Try to link content to category as a user without read permission and expect 403 (Forbidden)
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToCategory_asUserWithoutReadPermissionAndExpect403()
{
STEP("Try to link content to a category using user without read permission and expect 403");
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkWithId(category.getId());
final UserModel userWithoutRights = dataUser.createRandomTestUser();
restClient.authenticateUser(userWithoutRights).withCoreAPI().usingNode(file).linkToCategory(categoryLink);
restClient.assertStatusCodeIs(FORBIDDEN);
}
/**
* Try to link content to category as a user without change and expect 403 (Forbidden)
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToCategory_asUserWithoutChangePermissionAndExpect403()
{
STEP("Create another user as a consumer for file");
final UserModel consumer = dataUser.createRandomTestUser();
addPermissionsForUser(consumer.getUsername(), "Consumer", file);
STEP("Try to link content to a category using user without change permission and expect 403");
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkWithId(category.getId());
restClient.authenticateUser(consumer).withCoreAPI().usingNode(file).linkToCategory(categoryLink);
restClient.assertStatusCodeIs(FORBIDDEN);
}
/**
* Try to link content to category as owner and expect 201
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToCategory_asOwner()
{
STEP("Use admin to create a private site");
final SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
STEP("Add the user to the site, let him create a folder and then evict him from the site again");
dataUser.addUserToSite(user, privateSite, SiteManager);
final FolderModel privateFolder = dataContent.usingUser(user).usingSite(privateSite).createFolder();
final FileModel privateFile = dataContent.usingUser(user).usingResource(privateFolder).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
dataUser.removeUserFromSite(user, privateSite);
STEP("Try to link content to a category as owner and expect 201");
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkWithId(category.getId());
restClient.authenticateUser(user).withCoreAPI().usingNode(privateFile).linkToCategory(categoryLink);
restClient.assertStatusCodeIs(CREATED);
}
/**
* Try to link content to category using non-existing category and expect 404 (Not Found)
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToCategory_usingNonExistingCategoryAndExpect404()
{
STEP("Try to link content to non-existing category and expect 404");
final String nonExistingCategoryId = "non-existing-dummy-id";
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkWithId(nonExistingCategoryId);
restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategory(categoryLink);
restClient.assertStatusCodeIs(NOT_FOUND);
}
/**
* Try to link content to category passing empty list as input and expect 400 (Bad Request)
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToCategory_passingEmptyListAndExpect400()
{
STEP("Try to call link content API with empty list and expect 400");
restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategories(Collections.emptyList());
restClient.assertStatusCodeIs(BAD_REQUEST);
}
/**
* Try to link content to category passing invalid ID in input list and expect 400 (Bad Request)
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToCategory_passingEmptyIdAndExpect400()
{
STEP("Try to call link content API with empty category ID and expect 400");
final String nonExistingCategoryId = StringUtils.EMPTY;
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkWithId(nonExistingCategoryId);
restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategory(categoryLink);
restClient.assertStatusCodeIs(BAD_REQUEST);
}
/**
* Link folder node to category and expect 201 (Created)
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkFolderToCategory()
{
STEP("Link folder node to category");
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkWithId(category.getId());
restClient.authenticateUser(user).withCoreAPI().usingNode(folder).linkToCategory(categoryLink);
restClient.assertStatusCodeIs(CREATED);
}
/**
* Try to link non-content node to category and expect 405 (Method Not Allowed)
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToCategory_usingTagInsteadOfContentAndExpect405()
{
STEP("Try to link a tag to category and expect 405");
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkWithId(category.getId());
final RestTagModel tag = restClient.authenticateUser(user).withCoreAPI().usingNode(file).addTag("someTag");
final RepoTestModel tagNode = new RepoTestModel() {};
tagNode.setNodeRef(tag.getId());
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingNode(tagNode).linkToCategory(categoryLink);
restClient.assertStatusCodeIs(METHOD_NOT_ALLOWED);
}
/**
* Try to link content to non-category node and expect 400 (Bad Request)
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToCategory_usingFolderInsteadOfCategoryAndExpect400()
{
STEP("Try to link content to non-category and expect 400");
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkWithId(folder.getNodeRef());
restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategory(categoryLink);
restClient.assertStatusCodeIs(BAD_REQUEST);
}
/**
* Try to link content to root category and expect 400 (Bad Request)
*/
@Test(groups = { TestGroup.REST_API})
public void testLinkContentToCategory_usingRootCategoryAndExpect400()
{
STEP("Try to link content to root category and expect 400");
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkWithId(ROOT_CATEGORY_ID);
restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategory(categoryLink);
restClient.assertStatusCodeIs(BAD_REQUEST);
}
private RestCategoryLinkBodyModel createCategoryLinkWithId(final String id)
{
final RestCategoryLinkBodyModel categoryLink = new RestCategoryLinkBodyModel();
categoryLink.setCategoryId(id);
return categoryLink;
}
private void addPermissionsForUser(final String username, final String role, final FileModel file)
{
final String putPermissionsBody = Json.createObjectBuilder().add("permissions",
Json.createObjectBuilder()
.add("isInheritanceEnabled", true)
.add("locallySet", Json.createObjectBuilder()
.add("authorityId", username)
.add("name", role).add("accessStatus", "ALLOWED")))
.build()
.toString();
restClient.authenticateUser(user).withCoreAPI().usingNode(file).updateNode(putPermissionsBody);
}
}

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* 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
@@ -29,6 +29,7 @@ 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.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CONFLICT;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.OK;
@@ -93,6 +94,27 @@ public class UpdateCategoriesTests extends CategoriesRestTest
updatedCategory.assertThat().field(FIELD_NAME).is(categoryNewName);
}
/**
* Try to update a category with a name, which is already present within the parent category
*/
@Test(groups = { TestGroup.REST_API})
public void testUpdateCategory_usingRecurringName()
{
STEP("Prepare as admin two categories under root category");
final RestCategoryModel createdCategory = prepareCategoryUnderRoot();
final RestCategoryModel secondCreatedCategory = prepareCategoryUnderRoot();
STEP("Try to update as admin newly created category using name of already present, different category");
final String categoryNewName = secondCreatedCategory.getName();
final RestCategoryModel fixedCategoryModel = createCategoryModelWithName(categoryNewName);
restClient.authenticateUser(dataUser.getAdminUser())
.withCoreAPI()
.usingCategory(createdCategory)
.updateCategory(fixedCategoryModel);
restClient.assertStatusCodeIs(CONFLICT);
}
/**
* Try to update a category as a user and expect 403 (Forbidden) in response
*/

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* 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
@@ -53,4 +53,12 @@ public interface Categories
void deleteCategoryById(String id, Parameters parameters);
/**
* Link node to categories. Node types allowed for categorization are specified within {@link org.alfresco.util.TypeConstraint}.
*
* @param nodeId Node ID.
* @param categoryLinks Category IDs to which content should be linked to.
* @return Linked to categories.
*/
List<Category> linkNodeToCategories(String nodeId, List<Category> categoryLinks);
}

View File

@@ -0,0 +1,64 @@
/*
* #%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 <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.categories;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import org.alfresco.rest.api.Categories;
import org.alfresco.rest.api.model.Category;
import org.alfresco.rest.api.nodes.NodesEntityResource;
import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
import org.alfresco.rest.framework.resource.parameters.Parameters;
@RelationshipResource(name = "category-links", entityResource = NodesEntityResource.class, title = "Category links")
public class NodesCategoryLinksRelation implements RelationshipResourceAction.Create<Category>
{
private final Categories categories;
public NodesCategoryLinksRelation(Categories categories)
{
this.categories = categories;
}
/**
* POST /nodes/{nodeId}/category-links
*/
@WebApiDescription(
title = "Link content node to categories",
description = "Creates a link between a content node and categories",
successStatus = HttpServletResponse.SC_CREATED
)
@Override
public List<Category> create(String nodeId, List<Category> categoryLinks, Parameters parameters)
{
return categories.linkNodeToCategories(nodeId, categoryLinks);
}
}

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* 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
@@ -27,8 +27,15 @@
package org.alfresco.rest.api.impl;
import static org.alfresco.rest.api.Nodes.PATH_ROOT;
import static org.alfresco.service.cmr.security.AccessStatus.ALLOWED;
import static org.alfresco.service.cmr.security.PermissionService.CHANGE_PERMISSIONS;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.alfresco.model.ContentModel;
@@ -39,6 +46,7 @@ import org.alfresco.rest.api.model.Node;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.ListPage;
import org.alfresco.rest.framework.resource.parameters.Parameters;
@@ -47,10 +55,13 @@ 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.StoreRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.search.CategoryService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.TypeConstraint;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@@ -59,20 +70,26 @@ public class CategoriesImpl implements Categories
{
static final String NOT_A_VALID_CATEGORY = "Node id does not refer to a valid category";
static final String NO_PERMISSION_TO_MANAGE_A_CATEGORY = "Current user does not have permission to manage a category";
static final String NO_PERMISSION_TO_READ_CONTENT = "Current user does not have read permission to content";
static final String NOT_NULL_OR_EMPTY = "Category name must not be null or empty";
static final String FIELD_NOT_MATCH = "Category field: %s does not match the original one";
static final String INVALID_NODE_TYPE = "Cannot categorize this node type";
private final AuthorityService authorityService;
private final CategoryService categoryService;
private final Nodes nodes;
private final NodeService nodeService;
private final PermissionService permissionService;
private final TypeConstraint typeConstraint;
public CategoriesImpl(AuthorityService authorityService, CategoryService categoryService, Nodes nodes, NodeService nodeService)
public CategoriesImpl(AuthorityService authorityService, CategoryService categoryService, Nodes nodes, NodeService nodeService, PermissionService permissionService,
TypeConstraint typeConstraint)
{
this.authorityService = authorityService;
this.categoryService = categoryService;
this.nodes = nodes;
this.nodeService = nodeService;
this.permissionService = permissionService;
this.typeConstraint = typeConstraint;
}
@Override
@@ -123,7 +140,7 @@ public class CategoriesImpl implements Categories
throw new InvalidArgumentException(NOT_A_VALID_CATEGORY, new String[]{id});
}
verifyCategoryFields(fixedCategoryModel);
validateCategoryFields(fixedCategoryModel);
return mapToCategory(changeCategoryName(categoryNodeRef, fixedCategoryModel.getName()));
}
@@ -141,6 +158,35 @@ public class CategoriesImpl implements Categories
nodeService.deleteNode(nodeRef);
}
@Override
public List<Category> linkNodeToCategories(final String nodeId, final List<Category> categoryLinks)
{
if (CollectionUtils.isEmpty(categoryLinks))
{
throw new InvalidArgumentException(NOT_A_VALID_CATEGORY);
}
final NodeRef contentNodeRef = nodes.validateNode(nodeId);
verifyChangePermission(contentNodeRef);
verifyNodeType(contentNodeRef);
final Collection<NodeRef> categoryNodeRefs = categoryLinks.stream()
.filter(Objects::nonNull)
.map(Category::getId)
.filter(StringUtils::isNotEmpty)
.map(this::getCategoryNodeRef)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(categoryNodeRefs) || isRootCategoryPresent(categoryNodeRefs))
{
throw new InvalidArgumentException(NOT_A_VALID_CATEGORY);
}
linkNodeToCategories(contentNodeRef, categoryNodeRefs);
return categoryNodeRefs.stream().map(this::mapToCategory).collect(Collectors.toList());
}
private void verifyAdminAuthority()
{
if (!authorityService.hasAdminAuthority())
@@ -149,6 +195,22 @@ public class CategoriesImpl implements Categories
}
}
private void verifyChangePermission(final NodeRef nodeRef)
{
if (permissionService.hasPermission(nodeRef, CHANGE_PERMISSIONS) != ALLOWED)
{
throw new PermissionDeniedException(NO_PERMISSION_TO_READ_CONTENT);
}
}
private void verifyNodeType(final NodeRef nodeRef)
{
if (!typeConstraint.matches(nodeRef))
{
throw new UnsupportedResourceOperationException(INVALID_NODE_TYPE);
}
}
/**
* This method gets category NodeRef for a given category id.
* If '-root-' is passed as category id, then it's retrieved as a call to {@link org.alfresco.service.cmr.search.CategoryService#getRootCategoryNodeRef}
@@ -181,7 +243,7 @@ public class CategoriesImpl implements Categories
private NodeRef createCategoryNodeRef(NodeRef parentNodeRef, Category c)
{
verifyCategoryFields(c);
validateCategoryFields(c);
return categoryService.createCategory(parentNodeRef, c.getName());
}
@@ -236,15 +298,67 @@ public class CategoriesImpl implements Categories
}
/**
* Verify if fixed category name is not empty.
* Validate if fixed category name is not empty.
*
* @param fixedCategoryModel Fixed category model.
*/
private void verifyCategoryFields(final Category fixedCategoryModel)
private void validateCategoryFields(final Category fixedCategoryModel)
{
if (StringUtils.isEmpty(fixedCategoryModel.getName()))
{
throw new InvalidArgumentException(NOT_NULL_OR_EMPTY);
}
}
private boolean isRootCategoryPresent(final Collection<NodeRef> categoryNodeRefs)
{
return categoryNodeRefs.stream().anyMatch(this::isRootCategory);
}
private boolean isCategoryAspectMissing(final NodeRef nodeRef)
{
return !nodeService.hasAspect(nodeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE);
}
/**
* Merge already present and new categories ignoring repeating ones.
*
* @param currentCategories Already present categories.
* @param newCategories Categories which should be added.
* @return Merged categories.
*/
private Collection<NodeRef> mergeCategories(final Serializable currentCategories, final Collection<NodeRef> newCategories)
{
if (currentCategories == null)
{
return newCategories;
}
final Collection<NodeRef> actualCategories = DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, currentCategories);
final Collection<NodeRef> allCategories = new HashSet<>(actualCategories);
allCategories.addAll(newCategories);
return allCategories;
}
/**
* Add to or update node's property cm:categories containing linked category references.
*
* @param nodeRef Node reference.
* @param categoryNodeRefs Category node references.
*/
private void linkNodeToCategories(final NodeRef nodeRef, final Collection<NodeRef> categoryNodeRefs)
{
if (isCategoryAspectMissing(nodeRef))
{
final Map<QName, Serializable> properties = Map.of(ContentModel.PROP_CATEGORIES, (Serializable) categoryNodeRefs);
nodeService.addAspect(nodeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE, properties);
}
else
{
final Serializable currentCategories = nodeService.getProperty(nodeRef, ContentModel.PROP_CATEGORIES);
final Collection<NodeRef> allCategories = mergeCategories(currentCategories, categoryNodeRefs);
nodeService.setProperty(nodeRef, ContentModel.PROP_CATEGORIES, (Serializable) allCategories);
}
}
}

View File

@@ -45,6 +45,11 @@ public class Category
this.id = id;
}
public void setCategoryId(String categoryId)
{
this.id = categoryId;
}
public String getName()
{
return name;

View File

@@ -834,6 +834,8 @@
<constructor-arg name="categoryService" ref="CategoryService"/>
<constructor-arg name="nodes" ref="nodes"/>
<constructor-arg name="nodeService" ref="NodeService"/>
<constructor-arg name="permissionService" ref="PermissionService"/>
<constructor-arg name="typeConstraint" ref="nodeTypeConstraint"/>
</bean>
<bean id="Categories" class="org.springframework.aop.framework.ProxyFactoryBean">
@@ -1105,6 +1107,10 @@
<constructor-arg name="categories" ref="Categories"/>
</bean>
<bean class="org.alfresco.rest.api.categories.NodesCategoryLinksRelation">
<constructor-arg name="categories" ref="Categories"/>
</bean>
<bean class="org.alfresco.rest.api.tags.TagsEntityResource">
<property name="tags" ref="Tags" />
</bean>

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* 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
@@ -27,24 +27,31 @@
package org.alfresco.rest.api.impl;
import static org.alfresco.rest.api.Nodes.PATH_ROOT;
import static org.alfresco.rest.api.impl.CategoriesImpl.INVALID_NODE_TYPE;
import static org.alfresco.rest.api.impl.CategoriesImpl.NOT_A_VALID_CATEGORY;
import static org.alfresco.rest.api.impl.CategoriesImpl.NOT_NULL_OR_EMPTY;
import static org.alfresco.rest.api.impl.CategoriesImpl.NO_PERMISSION_TO_READ_CONTENT;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -55,6 +62,7 @@ import org.alfresco.rest.api.model.Node;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
@@ -62,9 +70,14 @@ import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.CategoryService;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.TypeConstraint;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -81,6 +94,8 @@ public class CategoriesImplTest
private static final String CAT_ROOT_NODE_ID = "cat-root-node-id";
private static final NodeRef CATEGORY_NODE_REF = createNodeRefWithId(CATEGORY_ID);
private static final Category CATEGORY = createDefaultCategoryWithName(CATEGORY_NAME);
private static final String CONTENT_NODE_ID = "content-node-id";
private static final NodeRef CONTENT_NODE_REF = createNodeRefWithId(CONTENT_NODE_ID);
@Mock
private Nodes nodesMock;
@@ -96,6 +111,10 @@ public class CategoriesImplTest
private ChildAssociationRef dummyChildAssociationRefMock;
@Mock
private ChildAssociationRef categoryChildAssociationRefMock;
@Mock
private PermissionService permissionServiceMock;
@Mock
private TypeConstraint typeConstraint;
@InjectMocks
private CategoriesImpl objectUnderTest;
@@ -104,8 +123,11 @@ public class CategoriesImplTest
public void setUp() throws Exception
{
given(authorityServiceMock.hasAdminAuthority()).willReturn(true);
given(nodesMock.validateNode(eq(CATEGORY_ID))).willReturn(CATEGORY_NODE_REF);
given(nodesMock.validateNode(CATEGORY_ID)).willReturn(CATEGORY_NODE_REF);
given(nodesMock.validateNode(CONTENT_NODE_ID)).willReturn(CONTENT_NODE_REF);
given(nodesMock.isSubClass(any(), any(), anyBoolean())).willReturn(true);
given(typeConstraint.matches(any())).willReturn(true);
given(permissionServiceMock.hasPermission(any(), any())).willReturn(AccessStatus.ALLOWED);
}
@Test
@@ -774,6 +796,208 @@ public class CategoriesImplTest
.isEqualTo(expectedCategory);
}
@Test
public void testLinkNodeToCategories_withoutCategoryAspect()
{
final List<Category> categoryLinks = List.of(CATEGORY);
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode());
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
// when
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks);
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
then(permissionServiceMock).should().hasPermission(CONTENT_NODE_REF, PermissionService.CHANGE_PERMISSIONS);
then(permissionServiceMock).shouldHaveNoMoreInteractions();
then(typeConstraint).should().matches(CONTENT_NODE_REF);
then(typeConstraint).shouldHaveNoMoreInteractions();
then(nodesMock).should().validateNode(CATEGORY_ID);
then(nodesMock).should().getNode(CATEGORY_ID);
then(nodesMock).should().isSubClass(CATEGORY_NODE_REF, ContentModel.TYPE_CATEGORY, false);
then(nodesMock).shouldHaveNoMoreInteractions();
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);
final Map<QName, Serializable> expectedProperties = Map.of(ContentModel.PROP_CATEGORIES, (Serializable) List.of(CATEGORY_NODE_REF));
then(nodeServiceMock).should().addAspect(CONTENT_NODE_REF, ContentModel.ASPECT_GEN_CLASSIFIABLE, expectedProperties);
then(nodeServiceMock).should().getParentAssocs(categoryParentNodeRef);
then(nodeServiceMock).shouldHaveNoMoreInteractions();
final List<Category> expectedLinkedCategories = List.of(CATEGORY);
assertThat(actualLinkedCategories)
.isNotNull().usingRecursiveComparison()
.isEqualTo(expectedLinkedCategories);
}
@Test
public void testLinkNodeToCategories_withPresentCategoryAspect()
{
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode());
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
given(nodeServiceMock.hasAspect(any(), any())).willReturn(true);
// when
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY));
then(nodesMock).should().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);
then(nodeServiceMock).shouldHaveNoMoreInteractions();
final List<Category> expectedLinkedCategories = List.of(CATEGORY);
assertThat(actualLinkedCategories)
.isNotNull().usingRecursiveComparison()
.isEqualTo(expectedLinkedCategories);
}
@Test
public void testLinkNodeToCategories_withMultipleCategoryIds()
{
final String secondCategoryId = "second-category-id";
final String secondCategoryName = "secondCategoryName";
final NodeRef secondCategoryNodeRef = createNodeRefWithId(secondCategoryId);
final Category secondCategoryLink = Category.builder().id(secondCategoryId).create();
final List<Category> categoryLinks = List.of(CATEGORY, secondCategoryLink);
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
final ChildAssociationRef categoryParentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
final ChildAssociationRef secondCategoryParentAssociation = createAssociationOf(categoryParentNodeRef, secondCategoryNodeRef);
given(nodesMock.validateNode(secondCategoryId)).willReturn(secondCategoryNodeRef);
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode(), prepareCategoryNode(secondCategoryName));
given(nodeServiceMock.getPrimaryParent(any())).willReturn(categoryParentAssociation, secondCategoryParentAssociation);
// when
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks);
then(nodesMock).should().validateNode(CATEGORY_ID);
then(nodesMock).should().validateNode(secondCategoryId);
then(nodesMock).should().isSubClass(CATEGORY_NODE_REF, ContentModel.TYPE_CATEGORY, false);
then(nodesMock).should().isSubClass(secondCategoryNodeRef, ContentModel.TYPE_CATEGORY, false);
final Map<QName, Serializable> expectedProperties = Map.of(ContentModel.PROP_CATEGORIES, (Serializable) List.of(CATEGORY_NODE_REF, secondCategoryNodeRef));
then(nodeServiceMock).should().addAspect(CONTENT_NODE_REF, ContentModel.ASPECT_GEN_CLASSIFIABLE, expectedProperties);
final List<Category> expectedLinkedCategories = List.of(
CATEGORY,
Category.builder().id(secondCategoryId).name(secondCategoryName).parentId(PARENT_ID).create()
);
assertThat(actualLinkedCategories)
.isNotNull().usingRecursiveComparison()
.isEqualTo(expectedLinkedCategories);
}
@Test
public void testLinkNodeToCategories_withPreviouslyLinkedCategories()
{
final String otherCategoryId = "other-category-id";
final NodeRef otherCategoryNodeRef = createNodeRefWithId(otherCategoryId);
final Serializable previousCategories = (Serializable) List.of(otherCategoryNodeRef);
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode());
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
given(nodeServiceMock.hasAspect(any(), any())).willReturn(true);
given(nodeServiceMock.getProperty(any(), eq(ContentModel.PROP_CATEGORIES))).willReturn(previousCategories);
// when
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY));
final Serializable expectedCategories = (Serializable) Set.of(otherCategoryNodeRef, CATEGORY_NODE_REF);
then(nodeServiceMock).should().setProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES, expectedCategories);
final List<Category> expectedLinkedCategories = List.of(CATEGORY);
assertThat(actualLinkedCategories)
.isNotNull().usingRecursiveComparison()
.isEqualTo(expectedLinkedCategories);
}
@Test
public void testLinkNodeToCategories_withInvalidNodeId()
{
given(nodesMock.validateNode(CONTENT_NODE_ID)).willThrow(EntityNotFoundException.class);
// when
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY)));
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
then(permissionServiceMock).shouldHaveNoInteractions();
then(nodeServiceMock).shouldHaveNoInteractions();
assertThat(actualException)
.isInstanceOf(EntityNotFoundException.class);
}
@Test
public void testLinkNodeToCategories_withoutPermission()
{
given(permissionServiceMock.hasPermission(any(), any())).willReturn(AccessStatus.DENIED);
// when
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY)));
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
then(permissionServiceMock).should().hasPermission(CONTENT_NODE_REF, PermissionService.CHANGE_PERMISSIONS);
then(nodeServiceMock).shouldHaveNoInteractions();
assertThat(actualException)
.isInstanceOf(PermissionDeniedException.class)
.hasMessageContaining(NO_PERMISSION_TO_READ_CONTENT);
}
@Test
public void testLinkContentNodeToCategories_withInvalidNodeType()
{
given(typeConstraint.matches(any())).willReturn(false);
// when
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY)));
then(typeConstraint).should().matches(CONTENT_NODE_REF);
then(nodeServiceMock).shouldHaveNoInteractions();
assertThat(actualException)
.isInstanceOf(UnsupportedResourceOperationException.class)
.hasMessageContaining(INVALID_NODE_TYPE);
}
@Test
public void testLinkNodeToCategories_withEmptyLinks()
{
final List<Category> categoryLinks = Collections.emptyList();
// when
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks));
then(nodesMock).shouldHaveNoInteractions();
then(permissionServiceMock).shouldHaveNoInteractions();
then(nodeServiceMock).shouldHaveNoInteractions();
assertThat(actualException)
.isInstanceOf(InvalidArgumentException.class)
.hasMessageContaining(NOT_A_VALID_CATEGORY);
}
@Test
public void testLinkNodeToCategories_withInvalidCategoryIds()
{
final Category categoryLinkWithNullId = Category.builder().id(null).create();
final Category categoryLinkWithEmptyId = Category.builder().id(StringUtils.EMPTY).create();
final List<Category> categoryLinks = new ArrayList<>();
categoryLinks.add(categoryLinkWithNullId);
categoryLinks.add(null);
categoryLinks.add(categoryLinkWithEmptyId);
// when
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks));
then(nodeServiceMock).shouldHaveNoInteractions();
assertThat(actualException)
.isInstanceOf(InvalidArgumentException.class)
.hasMessageContaining(NOT_A_VALID_CATEGORY);
}
private Node prepareCategoryNode(final String name, final String id, final NodeRef parentNodeRef)
{
final Node categoryNode = new Node();
@@ -785,8 +1009,7 @@ public class CategoriesImplTest
private Node prepareCategoryNode(final String name)
{
final NodeRef parentNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PARENT_ID);
return prepareCategoryNode(name, CATEGORY_ID, parentNodeRef);
return prepareCategoryNode(name, CATEGORY_ID, createNodeRefWithId(PARENT_ID));
}
private Node prepareCategoryNode()
@@ -858,7 +1081,12 @@ public class CategoriesImplTest
private static QName createCmQNameOf(final String name)
{
return QName.createQName(ContentModel.TYPE_CATEGORY.getNamespaceURI(), QName.createValidLocalName(name));
return QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name));
}
private static ChildAssociationRef createAssociationOf(final NodeRef parentNode, final NodeRef childNode)
{
return createAssociationOf(parentNode, childNode, null);
}
private static ChildAssociationRef createAssociationOf(final NodeRef parentNode, final NodeRef childNode, final QName childNodeName)