diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index b35c6ec586..e4ee5f2a5b 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Valid; @@ -48,16 +49,9 @@ /** * Management service for {@link Target}s. - * */ public interface TargetManagement { - enum OnNotFoundPolicy { - FAIL, // default - TAG_AND_SUCCESS, - TAG_AND_ERROR - } - /** * Counts number of targets with the given distribution set assigned. * @@ -674,12 +668,12 @@ Slice findByFilterOrderByLinkedDistributionSet(@NotNull Pageable pageabl * * @param controllerIds to assign for * @param targetTagId to assign - * @param onNotFoundPolicy what to do if there are targets that are not found + * @param notFoundHandler if not all targets found - if null - exception, otherwise tag what found and the handler is called with what's not found * @return list of assigned targets * @throws EntityNotFoundException if given targetTagId or at least one of the targets do not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) - List assignTag(@NotEmpty Collection controllerIds, long targetTagId, final OnNotFoundPolicy onNotFoundPolicy); + List assignTag(@NotEmpty Collection controllerIds, long targetTagId, final Consumer> notFoundHandler); /** * Assign a {@link TargetTag} assignment to given {@link Target}s. @@ -691,7 +685,7 @@ Slice findByFilterOrderByLinkedDistributionSet(@NotNull Pageable pageabl */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) default List assignTag(@NotEmpty Collection controllerIds, long targetTagId) { - return assignTag(controllerIds, targetTagId, OnNotFoundPolicy.FAIL); + return assignTag(controllerIds, targetTagId, null); } /** @@ -699,12 +693,12 @@ default List assignTag(@NotEmpty Collection controllerIds, long * * @param controllerIds to un-assign for * @param targetTagId to un-assign - * @param onNotFoundPolicy what to do if there are targets that are not found + * @param notFoundHandler if not all targets found - if null - exception, otherwise un-tag what found and the handler is called with what's not found * @return list of unassigned targets * @throws EntityNotFoundException if given targetTagId or at least one of the targets do not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) - List unassignTag(@NotEmpty Collection controllerIds, long targetTagId, final OnNotFoundPolicy onNotFoundPolicy); + List unassignTag(@NotEmpty Collection controllerIds, long targetTagId, final Consumer> notFoundHandler); /** * Un-assign a {@link TargetTag} assignment to given {@link Target}s. @@ -716,7 +710,7 @@ default List assignTag(@NotEmpty Collection controllerIds, long */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) default List unassignTag(@NotEmpty Collection controllerIds, long targetTagId) { - return unassignTag(controllerIds, targetTagId, OnNotFoundPolicy.FAIL); + return unassignTag(controllerIds, targetTagId, null); } /** @@ -1012,7 +1006,7 @@ Page findMetaDataByControllerIdAndRsql(@NotNull Pageable pageabl /** * Un-assign a {@link TargetTag} assignment to given {@link Target}. * - * @deprecated since 0.6.0 - use {@link #unassigŠ½nTag(List, long)} instead + * @deprecated since 0.6.0 - use {@link #unassignTag(Collection, long)} (List, long)} instead * @param controllerId to un-assign for * @param targetTagId to un-assign * @return the unassigned target or if no target is unassigned diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java index f0f6447b5c..25647f01f0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java @@ -20,6 +20,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -533,8 +534,8 @@ private List findTargetsByInSpecification(final Collection co @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public List assignTag(final Collection controllerIds, final long targetTagId, final OnNotFoundPolicy onNotFoundPolicy) { - return updateTag(controllerIds, targetTagId, onNotFoundPolicy, (tag, target) -> { + public List assignTag(final Collection controllerIds, final long targetTagId, final Consumer> notFoundHandler) { + return updateTag(controllerIds, targetTagId, notFoundHandler, (tag, target) -> { if (target.getTags().contains(tag)) { return target; } else { @@ -547,8 +548,8 @@ public List assignTag(final Collection controllerIds, final long @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public List unassignTag(final Collection controllerIds, final long targetTagId, final OnNotFoundPolicy onNotFoundPolicy) { - return updateTag(controllerIds, targetTagId, onNotFoundPolicy, (tag, target) -> { + public List unassignTag(final Collection controllerIds, final long targetTagId, final Consumer> notFoundHandler) { + return updateTag(controllerIds, targetTagId, notFoundHandler, (tag, target) -> { if (target.getTags().contains(tag)) { target.removeTag(tag); return targetRepository.save(target); @@ -558,7 +559,7 @@ public List unassignTag(final Collection controllerIds, final lo }); } private List updateTag( - final Collection controllerIds, final long targetTagId, final OnNotFoundPolicy notFoundPolicy, + final Collection controllerIds, final long targetTagId, final Consumer> notFoundHandler, final BiFunction updater) { final JpaTargetTag tag = targetTagRepository.findById(targetTagId) .orElseThrow(() -> new EntityNotFoundException(TargetTag.class, targetTagId)); @@ -568,25 +569,20 @@ private List updateTag( .orElseGet(Collections::emptyList) : targetRepository .findAll(TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds)); - final EntityNotFoundException notFoundException; - if (targets.size() < controllerIds.size() && notFoundPolicy != OnNotFoundPolicy.TAG_AND_SUCCESS) { - notFoundException = new EntityNotFoundException(Target.class, notFound(controllerIds, targets)); - if (notFoundPolicy == OnNotFoundPolicy.FAIL) { - throw notFoundException; + if (targets.size() < controllerIds.size()) { + final Collection notFound = notFound(controllerIds, targets); + if (notFoundHandler == null) { + throw new EntityNotFoundException(Target.class, notFound); + } else { + notFoundHandler.accept(notFound); } - } else { - notFoundException = null; } + targetRepository.getAccessController() .ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, targets)); try { - final List result = targets.stream().map(target -> updater.apply(tag, target)).toList(); - if (notFoundException != null) { // if notFoundPolicy is NotFoundPolicy.TAG_AND_FAIL - throw notFoundException; - } else { - return result; // if all found or notFoundPolicy is NotFoundPolicy.TAG_AND_SUCCESS - } + return targets.stream().map(target -> updater.apply(tag, target)).toList(); } finally { // No reason to save the tag entityManager.detach(tag); diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetTagRestApi.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetTagRestApi.java index 85fbc31607..76d40e36c4 100644 --- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetTagRestApi.java +++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetTagRestApi.java @@ -42,6 +42,12 @@ @Tag(name = "Target Tags", description = "REST API for Target Tag CRUD operations.") public interface MgmtTargetTagRestApi { + enum OnNotFoundPolicy { + FAIL, // default + ON_WHAT_FOUND_AND_FAIL, + ON_WHAT_FOUND_AND_SUCCESS + } + /** * Handles the GET request of retrieving all target tags. * @@ -351,6 +357,7 @@ ResponseEntity assignTarget( consumes = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE } ) ResponseEntity assignTargets( @PathVariable("targetTagId") Long targetTagId, + @RequestParam(value = "onNotFoundPolicy", required = false, defaultValue = "FAIL") OnNotFoundPolicy onNotFoundPolicy, @Schema(description = "List of controller ids to be assigned", example = "[\"controllerId1\", \"controllerId2\"]") @RequestBody List controllerIds); @@ -410,6 +417,7 @@ ResponseEntity unassignTarget( consumes = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) ResponseEntity unassignTargets( @PathVariable("targetTagId") Long targetTagId, + @RequestParam(value = "onNotFoundPolicy", required = false, defaultValue = "FAIL") OnNotFoundPolicy onNotFoundPolicy, @Schema(description = "List of controller ids to be unassigned", example = "[\"controllerId1\", \"controllerId2\"]") @RequestBody List controllerId); diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java index 7d1575395a..2e51ae81aa 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java @@ -9,10 +9,11 @@ */ package org.eclipse.hawkbit.mgmt.rest.resource; +import java.util.Collection; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtAssignedTargetRequestBody; @@ -186,14 +187,25 @@ public ResponseEntity assignTarget(final Long targetTagId, final String co } @Override - public ResponseEntity assignTargets(final Long targetTagId, final List controllerIds) { + public ResponseEntity assignTargets(final Long targetTagId, final OnNotFoundPolicy onNotFoundPolicy, final List controllerIds) { log.debug("Assign {} targets for target tag {}", controllerIds.size(), targetTagId); - this.targetManagement.assignTag(controllerIds, targetTagId); + if (onNotFoundPolicy == OnNotFoundPolicy.FAIL) { + this.targetManagement.assignTag(controllerIds, targetTagId); + } else { + final AtomicReference> notFound = new AtomicReference<>(); + this.targetManagement.assignTag(controllerIds, targetTagId, notFound::set); + if (notFound.get() != null) { + // has not found + if (onNotFoundPolicy == OnNotFoundPolicy.ON_WHAT_FOUND_AND_FAIL) { + throw new EntityNotFoundException(Target.class, notFound.get()); + } // else - success + } + } return ResponseEntity.ok().build(); } @Override - public ResponseEntity unassignTarget(@PathVariable("targetTagId") final Long targetTagId, + public ResponseEntity unassignTarget(final Long targetTagId, @PathVariable("controllerId") final String controllerId) { log.debug("Unassign target {} for target tag {}", controllerId, targetTagId); this.targetManagement.unassignTag(controllerId, targetTagId); @@ -201,9 +213,20 @@ public ResponseEntity unassignTarget(@PathVariable("targetTagId") final Lo } @Override - public ResponseEntity unassignTargets(final Long targetTagId, final List controllerIds) { + public ResponseEntity unassignTargets(final Long targetTagId, final OnNotFoundPolicy onNotFoundPolicy, final List controllerIds) { log.debug("Unassign {} targets for target tag {}", controllerIds.size(), targetTagId); - this.targetManagement.unassignTag(controllerIds, targetTagId); + if (onNotFoundPolicy == OnNotFoundPolicy.FAIL) { + this.targetManagement.unassignTag(controllerIds, targetTagId); + } else { + final AtomicReference> notFound = new AtomicReference<>(); + this.targetManagement.unassignTag(controllerIds, targetTagId, notFound::set); + if (notFound.get() != null) { + // has not found + if (onNotFoundPolicy == OnNotFoundPolicy.ON_WHAT_FOUND_AND_FAIL) { + throw new EntityNotFoundException(Target.class, notFound.get()); + } // else - success + } + } return ResponseEntity.ok().build(); } diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResourceTest.java index ed871f4abb..88f4780489 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResourceTest.java @@ -30,13 +30,13 @@ import java.util.stream.Collectors; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; +import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetTagRestApi; import org.eclipse.hawkbit.repository.event.remote.TargetTagDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetTagCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetTagUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; -import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetTag; @@ -344,6 +344,82 @@ public void assignTargetsNotFound() throws Exception { Collections.sort(notFound); assertThat(notFound).isEqualTo(missing); }); + assertThat(targetManagement.findByTag(PAGE, tag.getId()).getContent()).isEmpty(); + } + + @Test + @Description("Verifies that tag assignments (multi targets) done through tag API command are correctly stored in the repository.") + @ExpectEvents({ + @Expect(type = TargetTagCreatedEvent.class, count = 1), + @Expect(type = TargetCreatedEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 2)}) + public void assignTargetsNotFoundTagAndFail() throws Exception { + final TargetTag tag = testdataFactory.createTargetTags(1, "").get(0); + final List targets = testdataFactory.createTargets(2).stream().map(Target::getControllerId).toList(); + final List missing = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + while (true) { + final String id = String.valueOf(Math.abs(RND.nextLong())); + if (!targets.contains(id)) { + missing.add(id); + break; + } + } + } + Collections.sort(missing); + final List withMissing = new ArrayList<>(targets); + withMissing.addAll(missing); + + mvc.perform(put(MgmtRestConstants.TARGET_TAG_V1_REQUEST_MAPPING + "/" + tag.getId() + "/assigned") + .param("onNotFoundPolicy", MgmtTargetTagRestApi.OnNotFoundPolicy.ON_WHAT_FOUND_AND_FAIL.name()) + .content(JsonBuilder.toArray(withMissing)) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()) + .andExpect(status().isNotFound()) + .andExpect(handler -> { + final ExceptionInfo exceptionInfo = ResourceUtility.convertException(handler.getResponse().getContentAsString()); + final Map info = exceptionInfo.getInfo(); + assertThat(info).isNotNull(); + assertThat(info.get(EntityNotFoundException.TYPE)).isEqualTo(Target.class.getSimpleName()); + final List notFound = (List) info.get(EntityNotFoundException.ENTITY_ID); + Collections.sort(notFound); + assertThat(notFound).isEqualTo(missing); + }); + assertThat(targetManagement.findByTag(PAGE, tag.getId()).getContent().stream().map(Target::getControllerId).sorted().toList()) + .isEqualTo(targets.stream().sorted().toList()); + } + + @Test + @Description("Verifies that tag assignments (multi targets) done through tag API command are correctly stored in the repository.") + @ExpectEvents({ + @Expect(type = TargetTagCreatedEvent.class, count = 1), + @Expect(type = TargetCreatedEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 2)}) + public void assignTargetsNotFoundTagAndSuccess() throws Exception { + final TargetTag tag = testdataFactory.createTargetTags(1, "").get(0); + final List targets = testdataFactory.createTargets(2).stream().map(Target::getControllerId).toList(); + final List missing = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + while (true) { + final String id = String.valueOf(Math.abs(RND.nextLong())); + if (!targets.contains(id)) { + missing.add(id); + break; + } + } + } + Collections.sort(missing); + final List withMissing = new ArrayList<>(targets); + withMissing.addAll(missing); + + mvc.perform(put(MgmtRestConstants.TARGET_TAG_V1_REQUEST_MAPPING + "/" + tag.getId() + "/assigned") + .param("onNotFoundPolicy", MgmtTargetTagRestApi.OnNotFoundPolicy.ON_WHAT_FOUND_AND_SUCCESS.name()) + .content(JsonBuilder.toArray(withMissing)) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + assertThat(targetManagement.findByTag(PAGE, tag.getId()).getContent().stream().map(Target::getControllerId).sorted().toList()) + .isEqualTo(targets.stream().sorted().toList()); } @Test @@ -394,6 +470,126 @@ public void unassignTargets() throws Exception { .containsOnly(assigned.getControllerId()); } + @Test + @Description("Verifies that tag assignments (multi targets) done through tag API command are correctly stored in the repository.") + @ExpectEvents({ + @Expect(type = TargetTagCreatedEvent.class, count = 1), + @Expect(type = TargetCreatedEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 2)}) + public void unassignTargetsNotFound() throws Exception { + final TargetTag tag = testdataFactory.createTargetTags(1, "").get(0); + final List targets = testdataFactory.createTargets(2).stream().map(Target::getControllerId).toList(); + final List missing = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + while (true) { + final String id = String.valueOf(Math.abs(RND.nextLong())); + if (!targets.contains(id)) { + missing.add(id); + break; + } + } + } + Collections.sort(missing); + final List withMissing = new ArrayList<>(targets); + withMissing.addAll(missing); + + targetManagement.assignTag(targets, tag.getId()); + + mvc.perform(delete(MgmtRestConstants.TARGET_TAG_V1_REQUEST_MAPPING + "/" + tag.getId() + "/assigned") + .content(JsonBuilder.toArray(withMissing)) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()) + .andExpect(status().isNotFound()) + .andExpect(handler -> { + final ExceptionInfo exceptionInfo = ResourceUtility.convertException(handler.getResponse().getContentAsString()); + final Map info = exceptionInfo.getInfo(); + assertThat(info).isNotNull(); + assertThat(info.get(EntityNotFoundException.TYPE)).isEqualTo(Target.class.getSimpleName()); + final List notFound = (List) info.get(EntityNotFoundException.ENTITY_ID); + Collections.sort(notFound); + assertThat(notFound).isEqualTo(missing); + }); + assertThat(targetManagement.findByTag(PAGE, tag.getId()).getContent().stream().map(Target::getControllerId).sorted().toList()) + .isEqualTo(targets.stream().sorted().toList()); + } + + @Test + @Description("Verifies that tag assignments (multi targets) done through tag API command are correctly stored in the repository.") + @ExpectEvents({ + @Expect(type = TargetTagCreatedEvent.class, count = 1), + @Expect(type = TargetCreatedEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 4)}) + public void unassignTargetsNotFoundUntagAndFail() throws Exception { + final TargetTag tag = testdataFactory.createTargetTags(1, "").get(0); + final List targets = testdataFactory.createTargets(2).stream().map(Target::getControllerId).toList(); + final List missing = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + while (true) { + final String id = String.valueOf(Math.abs(RND.nextLong())); + if (!targets.contains(id)) { + missing.add(id); + break; + } + } + } + Collections.sort(missing); + final List withMissing = new ArrayList<>(targets); + withMissing.addAll(missing); + + targetManagement.assignTag(targets, tag.getId()); + + mvc.perform(delete(MgmtRestConstants.TARGET_TAG_V1_REQUEST_MAPPING + "/" + tag.getId() + "/assigned") + .param("onNotFoundPolicy", MgmtTargetTagRestApi.OnNotFoundPolicy.ON_WHAT_FOUND_AND_FAIL.name()) + .content(JsonBuilder.toArray(withMissing)) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()) + .andExpect(status().isNotFound()) + .andExpect(handler -> { + final ExceptionInfo exceptionInfo = ResourceUtility.convertException(handler.getResponse().getContentAsString()); + final Map info = exceptionInfo.getInfo(); + assertThat(info).isNotNull(); + assertThat(info.get(EntityNotFoundException.TYPE)).isEqualTo(Target.class.getSimpleName()); + final List notFound = (List) info.get(EntityNotFoundException.ENTITY_ID); + Collections.sort(notFound); + assertThat(notFound).isEqualTo(missing); + }); + assertThat(targetManagement.findByTag(PAGE, tag.getId()).getContent()).isEmpty(); + } + + @Test + @Description("Verifies that tag assignments (multi targets) done through tag API command are correctly stored in the repository.") + @ExpectEvents({ + @Expect(type = TargetTagCreatedEvent.class, count = 1), + @Expect(type = TargetCreatedEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 4)}) + public void unassignTargetsNotFoundUntagAndSuccess() throws Exception { + final TargetTag tag = testdataFactory.createTargetTags(1, "").get(0); + final List targets = testdataFactory.createTargets(2).stream().map(Target::getControllerId).toList(); + final List missing = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + while (true) { + final String id = String.valueOf(Math.abs(RND.nextLong())); + if (!targets.contains(id)) { + missing.add(id); + break; + } + } + } + Collections.sort(missing); + final List withMissing = new ArrayList<>(targets); + withMissing.addAll(missing); + + targetManagement.assignTag(targets, tag.getId()); + + mvc.perform(delete(MgmtRestConstants.TARGET_TAG_V1_REQUEST_MAPPING + "/" + tag.getId() + "/assigned") + .param("onNotFoundPolicy", MgmtTargetTagRestApi.OnNotFoundPolicy.ON_WHAT_FOUND_AND_SUCCESS.name()) + .content(JsonBuilder.toArray(withMissing)) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + assertThat(targetManagement.findByTag(PAGE, tag.getId()).getContent()).isEmpty(); + } + // DEPRECATED scenarios @Test @Description("Verifes that tag assignments done through toggle API command are correctly assigned or unassigned.")