diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/model/RestTagModel.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/model/RestTagModel.java index f214b57fee..db3909df2e 100644 --- a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/model/RestTagModel.java +++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/model/RestTagModel.java @@ -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 @@ -27,6 +27,8 @@ package org.alfresco.rest.model; import static org.alfresco.utility.report.log.Step.STEP; +import java.util.Objects; + import com.fasterxml.jackson.annotation.JsonProperty; import org.alfresco.rest.core.IRestModel; @@ -73,4 +75,65 @@ public class RestTagModel extends TagModel implements IRestModel this.count = count; } + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + RestTagModel tagModel = (RestTagModel) o; + return Objects.equals(id, tagModel.id) && Objects.equals(tag, tagModel.tag) && Objects.equals(count, tagModel.count); + } + + @Override + public int hashCode() + { + return Objects.hash(id, tag, count); + } + + @Override + public String toString() + { + return "RestTagModel{" + "id='" + id + ", tag='" + tag + '\'' + ", count=" + count + '\'' + '}'; + } + + public static Builder builder() + { + return new Builder(); + } + + public static class Builder + { + private String id; + private String tag; + private Integer count; + + public Builder id(String id) + { + this.id = id; + return this; + } + + public Builder tag(String tag) + { + this.tag = tag; + return this; + } + + public Builder count(Integer count) + { + this.count = count; + return this; + } + + public RestTagModel create() + { + final RestTagModel tag = new RestTagModel(); + tag.setId(id); + tag.setTag(this.tag); + tag.setCount(count); + return tag; + } + } } diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/requests/coreAPI/RestCoreAPI.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/requests/coreAPI/RestCoreAPI.java index 8dec5f6da0..4e7942abdf 100644 --- a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/requests/coreAPI/RestCoreAPI.java +++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/requests/coreAPI/RestCoreAPI.java @@ -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 @@ -25,6 +25,12 @@ */ package org.alfresco.rest.requests.coreAPI; +import static org.alfresco.rest.core.JsonBodyGenerator.arrayToJson; + +import java.util.List; + +import io.restassured.RestAssured; +import org.alfresco.rest.core.RestRequest; import org.alfresco.rest.core.RestWrapper; import org.alfresco.rest.model.RestCategoryModel; import org.alfresco.rest.model.RestDownloadsModel; @@ -49,8 +55,7 @@ import org.alfresco.rest.requests.Trashcan; import org.alfresco.utility.model.RepoTestModel; import org.alfresco.utility.model.SiteModel; import org.alfresco.utility.model.UserModel; - -import io.restassured.RestAssured; +import org.springframework.http.HttpMethod; /** * Defines the entire Rest Core API @@ -172,6 +177,30 @@ public class RestCoreAPI extends ModelRequest return new Networks(restWrapper); } + /** + * Create a single orphan tag. + * + * @param tag Tag model to create. + * @return Created tag. + */ + public RestTagModel createSingleTag(RestTagModel tag) + { + RestRequest request = RestRequest.requestWithBody(HttpMethod.POST, tag.toJson(), "tags/"); + return restWrapper.processModel(RestTagModel.class, request); + } + + /** + * Create several orphan tags in one request. + * + * @param tags Tags models to create. + * @return Created tags. + */ + public RestTagModelsCollection createTags(List tags) + { + RestRequest request = RestRequest.requestWithBody(HttpMethod.POST, arrayToJson(tags), "tags/"); + return restWrapper.processModels(RestTagModelsCollection.class, request); + } + public Tags usingTag(RestTagModel tag) { return new Tags(tag, restWrapper); diff --git a/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/tags/CreateTagsTests.java b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/tags/CreateTagsTests.java new file mode 100644 index 0000000000..cfe028d10f --- /dev/null +++ b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/tags/CreateTagsTests.java @@ -0,0 +1,218 @@ +/* + * #%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.tags; + +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.CREATED; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.springframework.http.HttpStatus.OK; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.alfresco.rest.RestTest; +import org.alfresco.rest.model.RestTagModel; +import org.alfresco.rest.model.RestTagModelsCollection; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.model.UserModel; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class CreateTagsTests extends RestTest +{ + private static final String FIELD_ID = "id"; + private static final String FIELD_TAG = "tag"; + private static final String FIELD_COUNT = "count"; + private static final String TAG_NAME_PREFIX = "tag-name"; + + private UserModel admin; + private UserModel user; + + @BeforeClass + public void init() + { + admin = dataUser.getAdminUser(); + user = dataUser.createRandomTestUser(); + } + + /** + * Verify if tag does not exist in the system, create one as admin and check if now it's there. + */ + @Test(groups = { TestGroup.REST_API, TestGroup.TAGS }) + public void testCreateSingleTag() + { + STEP("Create single tag as admin"); + final RestTagModel tagModel = createTagModelWithName(getRandomName("99gat").toLowerCase()); + final RestTagModel createdTag = restClient.authenticateUser(admin).withCoreAPI().createSingleTag(tagModel); + + restClient.assertStatusCodeIs(CREATED); + createdTag.assertThat().field(FIELD_TAG).is(tagModel.getTag()) + .assertThat().field(FIELD_ID).isNotEmpty(); + + STEP("Verify that tag does exist in the system"); + RestTagModel tag = restClient.authenticateUser(admin).withCoreAPI().getTag(createdTag); + restClient.assertStatusCodeIs(OK); + tag.assertThat().isEqualTo(createdTag); + } + + /** + * Create multiple orphan tags. + */ + @Test(groups = { TestGroup.REST_API, TestGroup.TAGS }) + public void testCreateMultipleTags() + { + STEP("Create several tags as admin"); + final List tagModels = IntStream.range(0, 3) + .mapToObj(i -> createTagModelWithName(getRandomName(TAG_NAME_PREFIX + "-" + i).toLowerCase())) + .collect(Collectors.toList()); + final RestTagModelsCollection createdTags = restClient.authenticateUser(admin).withCoreAPI().createTags(tagModels); + + restClient.assertStatusCodeIs(CREATED); + IntStream.range(0, tagModels.size()) + .forEach(i -> createdTags.getEntries().get(i).onModel() + .assertThat().field(FIELD_TAG).is(tagModels.get(i).getTag()) + .assertThat().field(FIELD_ID).isNotEmpty() + ); + } + + /** + * Verify that tag name's case will be lowered. + */ + @Test(groups = { TestGroup.REST_API, TestGroup.TAGS }) + public void testCreateSingleTag_usingUppercaseName() + { + STEP("Create single tag as admin using uppercase name"); + final RestTagModel tagModel = createTagModelWithName(getRandomName(TAG_NAME_PREFIX).toUpperCase()); + final RestTagModel createdTag = restClient.authenticateUser(admin).withCoreAPI().createSingleTag(tagModel); + + restClient.assertStatusCodeIs(CREATED); + createdTag.assertThat().field(FIELD_TAG).is(tagModel.getTag().toLowerCase()) + .assertThat().field(FIELD_ID).isNotEmpty(); + } + + /** + * Try to create few tags including repeating ones. Repeated tags should be omitted. + */ + @Test(groups = { TestGroup.REST_API, TestGroup.TAGS }) + public void testCreateMultipleTags_withRepeatedName() + { + STEP("Create models of tags"); + final String repeatedTagName = getRandomName(TAG_NAME_PREFIX).toLowerCase(); + final List tagModels = List.of( + createTagModelWithName(repeatedTagName), + createTagModelWithRandomName(), + createTagModelWithName(repeatedTagName) + ); + + STEP("Create several tags skipping repeating names"); + final RestTagModelsCollection createdTags = restClient.authenticateUser(admin).withCoreAPI().createTags(tagModels); + + restClient.assertStatusCodeIs(CREATED); + createdTags.assertThat().entriesListCountIs(2); + createdTags.assertThat().entriesListContains(FIELD_TAG, tagModels.get(0).getTag()) + .and().entriesListContains(FIELD_TAG, tagModels.get(1).getTag()); + } + + /** + * Try to create a tag as a common user and expect 403 (Forbidden) + */ + @Test(groups = { TestGroup.REST_API, TestGroup.TAGS }) + public void testCreateTag_asUser() + { + STEP("Try to create single tag as a common user and expect 403"); + final RestTagModel tagModel = createTagModelWithRandomName(); + restClient.authenticateUser(user).withCoreAPI().createSingleTag(tagModel); + + restClient.assertStatusCodeIs(FORBIDDEN); + } + + /** + * Try to call create tag API passing empty list and expect 400 (Bad Request) + */ + @Test(groups = { TestGroup.REST_API, TestGroup.TAGS }) + public void testCreateTags_passingEmptyList() + { + STEP("Pass empty list while creating tags and expect 400"); + restClient.authenticateUser(admin).withCoreAPI().createTags(Collections.emptyList()); + + restClient.assertStatusCodeIs(BAD_REQUEST); + } + + /** + * Try to create a tag, which already exists in the system and expect 409 (Conflict) + */ + @Test(groups = { TestGroup.REST_API, TestGroup.TAGS }) + public void testCreateTag_usingAlreadyExistingTagName() + { + STEP("Create some tag in the system"); + final RestTagModel alreadyExistingTag = prepareOrphanTag(); + + STEP("Try to use already existing tag to create duplicate and expect 409"); + restClient.authenticateUser(admin).withCoreAPI().createSingleTag(alreadyExistingTag); + + restClient.assertStatusCodeIs(CONFLICT); + restClient.assertLastError().containsSummary("Duplicate child name not allowed: " + alreadyExistingTag.getTag()); + } + + /** + * Verify if count field is 0 for newly created tags. + */ + @Test(groups = { TestGroup.REST_API, TestGroup.TAGS }) + public void testCreateTag_includingCount() + { + STEP("Create single tag as admin including count and verify if it is 0"); + final RestTagModel tagModel = createTagModelWithRandomName(); + final RestTagModel createdTag = restClient.authenticateUser(admin).withCoreAPI().include(FIELD_COUNT).createSingleTag(tagModel); + + restClient.assertStatusCodeIs(CREATED); + createdTag.assertThat().field(FIELD_TAG).is(tagModel.getTag()) + .assertThat().field(FIELD_ID).isNotEmpty() + .assertThat().field(FIELD_COUNT).is(0); + } + + private RestTagModel prepareOrphanTag() + { + final RestTagModel tagModel = createTagModelWithRandomName(); + final RestTagModel tag = restClient.authenticateUser(admin).withCoreAPI().createSingleTag(tagModel); + restClient.assertStatusCodeIs(CREATED); + return tag; + } + + private static RestTagModel createTagModelWithRandomName() + { + return createTagModelWithName(getRandomName(TAG_NAME_PREFIX).toLowerCase()); + } + + private static RestTagModel createTagModelWithName(final String tagName) + { + return RestTagModel.builder().tag(tagName).create(); + } +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/Tags.java b/remote-api/src/main/java/org/alfresco/rest/api/Tags.java index e3fc52c7b7..ec64b5b8de 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/Tags.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/Tags.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 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 @@ -25,22 +25,33 @@ */ package org.alfresco.rest.api; +import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE; + import java.util.List; import org.alfresco.rest.api.model.Tag; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; -import org.alfresco.rest.framework.resource.parameters.Paging; import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.service.Experimental; import org.alfresco.service.cmr.repository.StoreRef; public interface Tags { - public List addTags(String nodeId, List tags); - public Tag getTag(StoreRef storeRef, String tagId); - public void deleteTag(String nodeId, String tagId); - public CollectionWithPagingInfo getTags(StoreRef storeRef, Parameters params); - public Tag changeTag(StoreRef storeRef, String tagId, Tag tag); - public CollectionWithPagingInfo getTags(String nodeId, Parameters params); + List addTags(String nodeId, List tags); + Tag getTag(StoreRef storeRef, String tagId); + void deleteTag(String nodeId, String tagId); + CollectionWithPagingInfo getTags(StoreRef storeRef, Parameters params); + Tag changeTag(StoreRef storeRef, String tagId, Tag tag); + CollectionWithPagingInfo getTags(String nodeId, Parameters params); + + @Experimental + List createTags(StoreRef storeRef, List tags, Parameters parameters); + + @Experimental + default List createTags(List tags, Parameters parameters) + { + return createTags(STORE_REF_WORKSPACE_SPACESSTORE, tags, parameters); + } void deleteTagById(StoreRef storeRef, String tagId); } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/TagsImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/TagsImpl.java index b7ef74f162..266993d93e 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/TagsImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/TagsImpl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 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,9 +27,13 @@ package org.alfresco.rest.api.impl; import java.util.AbstractList; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; import org.alfresco.query.PagingResults; import org.alfresco.repo.tagging.NonExistentTagException; @@ -47,12 +51,14 @@ import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationE import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Paging; import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.service.Experimental; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.tagging.TaggingService; import org.alfresco.util.Pair; import org.alfresco.util.TypeConstraint; +import org.apache.commons.collections.CollectionUtils; /** * Centralises access to tag services and maps between representations. @@ -63,13 +69,14 @@ import org.alfresco.util.TypeConstraint; public class TagsImpl implements Tags { private static final Object PARAM_INCLUDE_COUNT = "count"; - private Nodes nodes; + static final String NOT_A_VALID_TAG = "An invalid parameter has been supplied"; + static final String NO_PERMISSION_TO_MANAGE_A_TAG = "Current user does not have permission to manage a tag"; + + private Nodes nodes; private TaggingService taggingService; private TypeConstraint typeConstraint; private AuthorityService authorityService; - static final String NO_PERMISSION_TO_MANAGE_A_TAG = "Current user does not have permission to manage a tag"; - public void setTypeConstraint(TypeConstraint typeConstraint) { this.typeConstraint = typeConstraint; @@ -140,10 +147,7 @@ public class TagsImpl implements Tags @Override public void deleteTagById(StoreRef storeRef, String tagId) { - if (!authorityService.hasAdminAuthority()) - { - throw new PermissionDeniedException(NO_PERMISSION_TO_MANAGE_A_TAG); - } + verifyAdminAuthority(); NodeRef tagNodeRef = validateTag(storeRef, tagId); String tagValue = taggingService.getTagName(tagNodeRef); @@ -253,4 +257,38 @@ public class TagsImpl implements Tags return CollectionWithPagingInfo.asPaged(params.getPaging(), tags, results.hasMoreItems(), (totalItems == null ? null : totalItems.intValue())); } + + @Experimental + @Override + public List createTags(final StoreRef storeRef, final List tags, final Parameters parameters) + { + verifyAdminAuthority(); + final List tagNames = Optional.ofNullable(tags).orElse(Collections.emptyList()).stream() + .filter(Objects::nonNull) + .map(Tag::getTag) + .distinct() + .collect(Collectors.toList()); + + if (CollectionUtils.isEmpty(tagNames)) + { + throw new InvalidArgumentException(NOT_A_VALID_TAG); + } + + return taggingService.createTags(storeRef, tagNames).stream() + .map(pair -> Tag.builder().tag(pair.getFirst()).nodeRef(pair.getSecond()).create()) + .peek(tag -> { + if (parameters.getInclude().contains(PARAM_INCLUDE_COUNT)) + { + tag.setCount(0); + } + }).collect(Collectors.toList()); + } + + private void verifyAdminAuthority() + { + if (!authorityService.hasAdminAuthority()) + { + throw new PermissionDeniedException(NO_PERMISSION_TO_MANAGE_A_TAG); + } + } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/Tag.java b/remote-api/src/main/java/org/alfresco/rest/api/model/Tag.java index 1fb4c16088..bcbd4e6fd4 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/model/Tag.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/Tag.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 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 @@ -25,6 +25,8 @@ */ package org.alfresco.rest.api.model; +import java.util.Objects; + import com.fasterxml.jackson.annotation.JsonProperty; import org.alfresco.rest.framework.resource.UniqueId; import org.alfresco.service.cmr.repository.NodeRef; @@ -109,7 +111,7 @@ public class Tag implements Comparable /* * Tags are equal if they have the same NodeRef - * + * * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @@ -134,7 +136,45 @@ public class Tag implements Comparable @Override public String toString() { - return "Tag [nodeRef=" + nodeRef + ", tag=" + tag + "]"; + return "Tag{" + "nodeRef=" + nodeRef + ", tag='" + tag + '\'' + ", count=" + count + '}'; + } + + public static Builder builder() + { + return new Builder(); + } + + public static class Builder + { + private NodeRef nodeRef; + private String tag; + private Integer count; + + public Builder nodeRef(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + return this; + } + + public Builder tag(String tag) + { + this.tag = tag; + return this; + } + + public Builder count(Integer count) + { + this.count = count; + return this; + } + + public Tag create() + { + final Tag tag = new Tag(); + tag.setNodeRef(nodeRef); + tag.setTag(this.tag); + tag.setCount(count); + return tag; + } } - } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/tags/TagsEntityResource.java b/remote-api/src/main/java/org/alfresco/rest/api/tags/TagsEntityResource.java index 635acdf895..8b30d637c7 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/tags/TagsEntityResource.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/tags/TagsEntityResource.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 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 @@ -25,6 +25,9 @@ */ package org.alfresco.rest.api.tags; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + import org.alfresco.rest.api.Tags; import org.alfresco.rest.api.model.Tag; import org.alfresco.rest.framework.WebApiDescription; @@ -33,12 +36,14 @@ import org.alfresco.rest.framework.resource.EntityResource; import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.service.Experimental; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.util.ParameterCheck; import org.springframework.beans.factory.InitializingBean; @EntityResource(name="tags", title = "Tags") -public class TagsEntityResource implements EntityResourceAction.Read, EntityResourceAction.ReadById, EntityResourceAction.Update, EntityResourceAction.Delete, InitializingBean +public class TagsEntityResource implements EntityResourceAction.Read, + EntityResourceAction.ReadById, EntityResourceAction.Update, EntityResourceAction.Create, EntityResourceAction.Delete, InitializingBean { private Tags tags; @@ -78,6 +83,21 @@ public class TagsEntityResource implements EntityResourceAction.Read, Entit return tags.getTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id); } + /** + * POST /tags + */ + @Experimental + @WebApiDescription( + title = "Create an orphan tag", + description = "Creates a tag, which is not associated with any node", + successStatus = HttpServletResponse.SC_CREATED + ) + @Override + public List create(List tags, Parameters parameters) + { + return this.tags.createTags(tags, parameters); + } + @Override public void delete(String id, Parameters parameters) { diff --git a/remote-api/src/test/java/org/alfresco/AppContextExtraTestSuite.java b/remote-api/src/test/java/org/alfresco/AppContextExtraTestSuite.java index 1963f197c6..82368bfd0c 100644 --- a/remote-api/src/test/java/org/alfresco/AppContextExtraTestSuite.java +++ b/remote-api/src/test/java/org/alfresco/AppContextExtraTestSuite.java @@ -50,6 +50,7 @@ import org.junit.runners.Suite; org.alfresco.repo.webdav.WebDAVLockServiceImplTest.class, org.alfresco.rest.api.RulesUnitTests.class, org.alfresco.rest.api.CategoriesUnitTests.class, + org.alfresco.rest.api.TagsUnitTests.class, org.alfresco.rest.api.impl.ContentStorageInformationImplTest.class, org.alfresco.rest.api.nodes.NodeStorageInfoRelationTest.class, org.alfresco.rest.api.search.ResultMapperTests.class, diff --git a/remote-api/src/test/java/org/alfresco/rest/api/TagsUnitTests.java b/remote-api/src/test/java/org/alfresco/rest/api/TagsUnitTests.java new file mode 100644 index 0000000000..2cc0e5d7b5 --- /dev/null +++ b/remote-api/src/test/java/org/alfresco/rest/api/TagsUnitTests.java @@ -0,0 +1,42 @@ +/* + * #%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.api; + +import org.alfresco.rest.api.impl.TagsImplTest; +import org.alfresco.rest.api.tags.TagsEntityResourceTest; +import org.alfresco.service.Experimental; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@Experimental +@RunWith(Suite.class) +@Suite.SuiteClasses({ + TagsImplTest.class, + TagsEntityResourceTest.class +}) +public class TagsUnitTests +{ +} 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 9852d7da91..575f0cc0dc 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 @@ -133,7 +133,6 @@ public class CategoriesImplTest given(typeConstraint.matches(any())).willReturn(true); given(permissionServiceMock.hasReadPermission(any())).willReturn(AccessStatus.ALLOWED); given(permissionServiceMock.hasPermission(any(), any())).willReturn(AccessStatus.ALLOWED); - //given(parametersMock.getInclude()).willReturn(Co); } @Test diff --git a/remote-api/src/test/java/org/alfresco/rest/api/impl/TagsImplTest.java b/remote-api/src/test/java/org/alfresco/rest/api/impl/TagsImplTest.java index f1ee8cee95..6593d0366c 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/impl/TagsImplTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/impl/TagsImplTest.java @@ -23,16 +23,33 @@ * along with Alfresco. If not, see . * #L% */ - package org.alfresco.rest.api.impl; +import static org.alfresco.rest.api.impl.TagsImpl.NOT_A_VALID_TAG; +import static org.alfresco.rest.api.impl.TagsImpl.NO_PERMISSION_TO_MANAGE_A_TAG; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + import org.alfresco.rest.api.Nodes; +import org.alfresco.rest.api.model.Tag; 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.resource.parameters.Parameters; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.tagging.TaggingService; +import org.alfresco.util.Pair; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,16 +57,12 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import static org.junit.Assert.assertThrows; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.then; - @RunWith(MockitoJUnitRunner.class) public class TagsImplTest { private static final String TAG_ID = "tag-node-id"; private static final String TAG_NAME = "tag-dummy-name"; - private static final NodeRef TAG_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE,TAG_ID); + private static final NodeRef TAG_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID); @Mock private Nodes nodesMock; @@ -57,6 +70,8 @@ public class TagsImplTest private AuthorityService authorityServiceMock; @Mock private TaggingService taggingServiceMock; + @Mock + private Parameters parametersMock; @InjectMocks private TagsImpl objectUnderTest; @@ -116,4 +131,162 @@ public class TagsImplTest then(taggingServiceMock).shouldHaveNoInteractions(); } + + @Test + public void testCreateTags() + { + final List tagNames = List.of("tag1", "99gat"); + final List tagsToCreate = createTags(tagNames); + given(taggingServiceMock.createTags(any(), any())).willAnswer(invocation -> createTagAndNodeRefPairs(invocation.getArgument(1))); + + //when + final List actualCreatedTags = objectUnderTest.createTags(tagsToCreate, parametersMock); + + then(authorityServiceMock).should().hasAdminAuthority(); + then(authorityServiceMock).shouldHaveNoMoreInteractions(); + then(taggingServiceMock).should().createTags(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, tagNames); + then(taggingServiceMock).shouldHaveNoMoreInteractions(); + final List expectedTags = createTagsWithNodeRefs(tagNames); + assertThat(actualCreatedTags) + .isNotNull() + .isEqualTo(expectedTags); + } + + @Test + public void testCreateTags_withoutPermission() + { + given(authorityServiceMock.hasAdminAuthority()).willReturn(false); + + //when + final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(List.of(createTag(TAG_NAME)), parametersMock)); + + then(authorityServiceMock).should().hasAdminAuthority(); + then(authorityServiceMock).shouldHaveNoMoreInteractions(); + then(taggingServiceMock).shouldHaveNoInteractions(); + assertThat(actualException) + .isInstanceOf(PermissionDeniedException.class) + .hasMessageContaining(NO_PERMISSION_TO_MANAGE_A_TAG); + } + + @Test + public void testCreateTags_passingNullInsteadList() + { + //when + final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(null, parametersMock)); + + then(taggingServiceMock).shouldHaveNoInteractions(); + assertThat(actualException) + .isInstanceOf(InvalidArgumentException.class) + .hasMessageContaining(NOT_A_VALID_TAG); + } + + @Test + public void testCreateTags_passingEmptyList() + { + //when + final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(Collections.emptyList(), parametersMock)); + + then(taggingServiceMock).shouldHaveNoInteractions(); + assertThat(actualException) + .isInstanceOf(InvalidArgumentException.class) + .hasMessageContaining(NOT_A_VALID_TAG); + } + + @Test + public void testCreateTags_passingListOfNulls() + { + //when + final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(Collections.singletonList(null), parametersMock)); + + then(taggingServiceMock).shouldHaveNoInteractions(); + assertThat(actualException) + .isInstanceOf(InvalidArgumentException.class) + .hasMessageContaining(NOT_A_VALID_TAG); + } + + @Test + public void testCreateTags_whileTagAlreadyExists() + { + given(taggingServiceMock.createTags(any(), any())).willThrow(new DuplicateChildNodeNameException(null, null, TAG_NAME, null)); + + //when + final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(List.of(createTag(TAG_NAME)), parametersMock)); + + then(taggingServiceMock).should().createTags(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME)); + then(taggingServiceMock).shouldHaveNoMoreInteractions(); + assertThat(actualException).isInstanceOf(DuplicateChildNodeNameException.class); + } + + @Test + public void testCreateTags_withRepeatedTagName() + { + final List tagNames = List.of(TAG_NAME, TAG_NAME); + final List tagsToCreate = createTags(tagNames); + given(taggingServiceMock.createTags(any(), any())).willAnswer(invocation -> createTagAndNodeRefPairs(invocation.getArgument(1))); + + //when + final List actualCreatedTags = objectUnderTest.createTags(tagsToCreate, parametersMock); + + then(taggingServiceMock).should().createTags(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME)); + final List expectedTags = List.of(createTagWithNodeRef(TAG_NAME)); + assertThat(actualCreatedTags) + .isNotNull() + .isEqualTo(expectedTags); + } + + @Test + public void testCreateTags_includingCount() + { + final List tagNames = List.of("tag1", "99gat"); + final List tagsToCreate = createTags(tagNames); + given(taggingServiceMock.createTags(any(), any())).willAnswer(invocation -> createTagAndNodeRefPairs(invocation.getArgument(1))); + given(parametersMock.getInclude()).willReturn(List.of("count")); + + //when + final List actualCreatedTags = objectUnderTest.createTags(tagsToCreate, parametersMock); + + final List expectedTags = createTagsWithNodeRefs(tagNames).stream() + .peek(tag -> tag.setCount(0)) + .collect(Collectors.toList()); + assertThat(actualCreatedTags) + .isNotNull() + .isEqualTo(expectedTags); + } + + private static List> createTagAndNodeRefPairs(final List tagNames) + { + return tagNames.stream() + .map(tagName -> createPair(tagName, new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID.concat("-").concat(tagName)))) + .collect(Collectors.toList()); + } + + private static Pair createPair(final String tagName, final NodeRef nodeRef) + { + return new Pair<>(tagName, nodeRef); + } + + private static List createTags(final List tagNames) + { + return tagNames.stream().map(TagsImplTest::createTag).collect(Collectors.toList()); + } + + private static List createTagsWithNodeRefs(final List tagNames) + { + return tagNames.stream().map(TagsImplTest::createTagWithNodeRef).collect(Collectors.toList()); + } + + private static Tag createTag(final String tagName) + { + return Tag.builder() + .tag(tagName) + .create(); + } + + private static Tag createTagWithNodeRef(final String tagName) + { + return Tag.builder() + .nodeRef(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID.concat("-").concat(tagName))) + .tag(tagName) + .create(); + } } diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tags/TagsEntityResourceTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tags/TagsEntityResourceTest.java new file mode 100644 index 0000000000..572d23f740 --- /dev/null +++ b/remote-api/src/test/java/org/alfresco/rest/api/tags/TagsEntityResourceTest.java @@ -0,0 +1,78 @@ +/* + * #%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.api.tags; + +import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; + +import java.util.List; + +import org.alfresco.rest.api.Tags; +import org.alfresco.rest.api.model.Tag; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.service.cmr.repository.NodeRef; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class TagsEntityResourceTest +{ + private static final String TAG_ID = "tag-dummy-id"; + private static final String TAG_NAME = "tag-dummy-name"; + private static final NodeRef TAG_NODE_REF = new NodeRef(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID); + + @Mock + private Parameters parametersMock; + @Mock + private Tags tagsMock; + + @InjectMocks + private TagsEntityResource tagsEntityResource; + + @Test + public void testCreate() + { + final Tag tag = Tag.builder().tag(TAG_NAME).create(); + final List tags = List.of(tag); + given(tagsMock.createTags(any(), any())).willCallRealMethod(); + given(tagsMock.createTags(any(), any(), any())).willReturn(List.of(Tag.builder().nodeRef(TAG_NODE_REF).tag(TAG_NAME).create())); + + //when + final List actualCreatedTags = tagsEntityResource.create(tags, parametersMock); + + then(tagsMock).should().createTags(STORE_REF_WORKSPACE_SPACESSTORE, tags, parametersMock); + final List expectedTags = List.of(Tag.builder().nodeRef(TAG_NODE_REF).tag(TAG_NAME).create()); + assertThat(actualCreatedTags) + .isNotEmpty() + .isEqualTo(expectedTags); + } +} \ No newline at end of file diff --git a/repository/src/main/java/org/alfresco/repo/tagging/TaggingServiceImpl.java b/repository/src/main/java/org/alfresco/repo/tagging/TaggingServiceImpl.java index e9f2fc17ae..f05eeb4fa9 100644 --- a/repository/src/main/java/org/alfresco/repo/tagging/TaggingServiceImpl.java +++ b/repository/src/main/java/org/alfresco/repo/tagging/TaggingServiceImpl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2020 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,7 +26,6 @@ package org.alfresco.repo.tagging; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Serializable; @@ -41,9 +40,10 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; -import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.query.EmptyPagingResults; import org.alfresco.query.PagingRequest; @@ -64,11 +64,13 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListener; +import org.alfresco.service.Experimental; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; @@ -1575,4 +1577,27 @@ public class TaggingServiceImpl implements TaggingService, } } + @Experimental + @Override + public List> createTags(final StoreRef storeRef, final List tagNames) + { + updateTagBehaviour.disable(); + createTagBehaviour.disable(); + try + { + return tagNames.stream() + .peek(tagName -> categoryService.getRootCategories(storeRef, ContentModel.ASPECT_TAGGABLE, tagName, false).stream() + .filter(association -> Objects.nonNull(association.getChildRef())) + .findAny() + .ifPresent(association -> { throw new DuplicateChildNodeNameException(association.getParentRef(), association.getTypeQName(), tagName, null); })) + .map(String::toLowerCase) + .map(tagName -> new Pair<>(tagName, getTagNodeRef(storeRef, tagName, true))) + .collect(Collectors.toList()); + } + finally + { + updateTagBehaviour.enable(); + createTagBehaviour.enable(); + } + } } diff --git a/repository/src/main/java/org/alfresco/service/cmr/tagging/TaggingService.java b/repository/src/main/java/org/alfresco/service/cmr/tagging/TaggingService.java index 6b588966ed..6035ed710a 100644 --- a/repository/src/main/java/org/alfresco/service/cmr/tagging/TaggingService.java +++ b/repository/src/main/java/org/alfresco/service/cmr/tagging/TaggingService.java @@ -1,36 +1,38 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 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% - */ +/* + * #%L + * Alfresco Repository + * %% + * 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.service.cmr.tagging; +import java.util.Collections; import java.util.List; import org.alfresco.api.AlfrescoPublicApi; import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; import org.alfresco.service.Auditable; +import org.alfresco.service.Experimental; import org.alfresco.service.NotAuditable; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; @@ -306,17 +308,31 @@ public interface TaggingService */ @NotAuditable Pair, Integer> getPagedTags(StoreRef storeRef, String filter, int fromTag, int pageSize); - - - /** - * Get tagged nodes and count of nodes group by tag name - * - * @param storeRef - * @return - */ - @NotAuditable - List> findTaggedNodesAndCountByTagName(StoreRef storeRef); - + + + /** + * Get tagged nodes and count of nodes group by tag name + * + * @param storeRef + * @return + */ + @NotAuditable + List> findTaggedNodesAndCountByTagName(StoreRef storeRef); + + /** + * Creates orphan tags. Tag names case will be lowered. + * + * @param storeRef Reference to node store. + * @param tagNames List of tag names. + * @return {@link List} of {@link Pair}s of tag names and node references. + * @throws org.alfresco.service.cmr.repository.DuplicateChildNodeNameException if tag already exists. + */ + @Experimental + @Auditable(parameters = {"tagNames"}) + default List> createTags(StoreRef storeRef, List tagNames) + { + return Collections.emptyList(); + } } diff --git a/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java b/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java index e0642cee21..8ca0d38ab0 100644 --- a/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java +++ b/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java @@ -244,7 +244,8 @@ import org.junit.runners.Suite; org.alfresco.repo.event2.RepoEvent2UnitSuite.class, - org.alfresco.util.schemacomp.SchemaDifferenceHelperUnitTest.class + org.alfresco.util.schemacomp.SchemaDifferenceHelperUnitTest.class, + org.alfresco.repo.tagging.TaggingServiceImplUnitTest.class }) public class AllUnitTestsSuite { diff --git a/repository/src/test/java/org/alfresco/repo/tagging/TaggingServiceImplUnitTest.java b/repository/src/test/java/org/alfresco/repo/tagging/TaggingServiceImplUnitTest.java new file mode 100644 index 0000000000..d0afd37558 --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/tagging/TaggingServiceImplUnitTest.java @@ -0,0 +1,106 @@ +/* + * #%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.repo.tagging; + +import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.CategoryService; +import org.alfresco.util.Pair; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class TaggingServiceImplUnitTest +{ + private static final String TAG_ID = "tag-node-id"; + private static final String TAG_NAME = "tag-dummy-name"; + private static final NodeRef TAG_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID); + + @Mock + private CategoryService categoryServiceMock; + @Mock + private PolicyComponent policyComponentMock; + + @InjectMocks + private TaggingServiceImpl taggingService; + + @Before + public void setUp() throws Exception + { + taggingService.init(); + } + + @Test + public void testCreateTags() + { + final ChildAssociationRef tagAssociationMock = mock(ChildAssociationRef.class); + given(categoryServiceMock.getRootCategories(any(), any(), any(String.class), eq(true))).willReturn(List.of(tagAssociationMock)); + given(tagAssociationMock.getChildRef()).willReturn(TAG_NODE_REF); + + //when + final List> actualTagPairs = taggingService.createTags(STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME)); + + then(categoryServiceMock).should().getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ContentModel.ASPECT_TAGGABLE, TAG_NAME, false); + then(categoryServiceMock).should().getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ContentModel.ASPECT_TAGGABLE, TAG_NAME, true); + then(categoryServiceMock).shouldHaveNoMoreInteractions(); + List> expectedTagPairs = List.of(new Pair<>(TAG_NAME, TAG_NODE_REF)); + assertThat(actualTagPairs) + .isNotNull() + .isEqualTo(expectedTagPairs); + } + + @Test + public void testCreateTags_whileTagAlreadyExists() + { + given(categoryServiceMock.getRootCategories(any(), any(), any(String.class), eq(false))).willThrow(new DuplicateChildNodeNameException(null, null, null, null)); + + //when + final Throwable actualException = catchThrowable(() -> taggingService.createTags(STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME))); + + then(categoryServiceMock).should().getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ContentModel.ASPECT_TAGGABLE, TAG_NAME, false); + then(categoryServiceMock).shouldHaveNoMoreInteractions(); + assertThat(actualException).isInstanceOf(DuplicateChildNodeNameException.class); + } +} \ No newline at end of file