From 4f0a8893c7cdd1542835046886e5eb50b7b44f9a Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Thu, 28 Aug 2025 12:56:16 +0300 Subject: [PATCH] Abstract RepositoryManagement test (#2631) Signed-off-by: Avgustin Marinov --- .../repository/ControllerManagement.java | 10 +- .../repository/DeploymentManagement.java | 10 - ...DistributionSetInvalidationManagement.java | 1 - .../DistributionSetTagManagement.java | 11 - .../repository/MaintenanceScheduleHelper.java | 8 +- .../repository/OffsetBasedPageRequest.java | 8 +- .../hawkbit/repository/QuotaManagement.java | 21 +- .../repository/RegexCharacterCollection.java | 68 -- .../repository/RepositoryConstants.java | 11 +- .../hawkbit/repository/RolloutExecutor.java | 42 +- .../repository/RolloutGroupManagement.java | 9 - .../hawkbit/repository/RolloutHandler.java | 29 +- .../hawkbit/repository/RolloutManagement.java | 90 +-- .../repository/SoftwareModuleManagement.java | 8 +- .../hawkbit/repository/SystemManagement.java | 3 - .../TargetFilterQueryManagement.java | 3 +- .../hawkbit/repository/TargetManagement.java | 6 - .../TenantConfigurationManagement.java | 44 +- .../repository/TenantStatsManagement.java | 3 +- .../hawkbit/repository/UpdateMode.java | 10 +- .../repository/ValidStringValidator.java | 2 +- .../model/DistributionSetFilter.java | 31 - .../hawkbit/repository/model/PollStatus.java | 14 +- .../hawkbit/repository/RegexCharTest.java | 111 --- .../jpa/JpaRepositoryConfiguration.java | 4 +- .../management/JpaControllerManagement.java | 16 +- .../management/JpaDeploymentManagement.java | 11 +- .../management/JpaRolloutGroupManagement.java | 7 - .../jpa/management/JpaSystemManagement.java | 5 - .../JpaTargetFilterQueryManagement.java | 6 +- .../jpa/management/JpaTargetManagement.java | 2 +- .../jpa/model/JpaDistributionSet.java | 3 +- .../jpa/model/JpaSoftwareModule.java | 15 +- .../jpa/AbstractJpaIntegrationTest.java | 56 +- .../AbstractRepositoryManagementTest.java | 662 ++++++++++++++++++ ...tRepositoryManagementWithMetadataTest.java | 163 +++++ .../DistributionSetManagementTest.java | 558 ++------------- .../DistributionSetTagManagementTest.java | 306 ++------ .../DistributionSetTypeManagementTest.java | 356 +++++----- .../management/ManagementSecurityTest.java | 3 - .../RolloutGroupManagementTest.java | 1 - .../SoftwareModuleManagementTest.java | 425 ++--------- .../SoftwareModuleTypeManagementTest.java | 172 +---- .../TargetFilterQueryManagementTest.java | 421 +++++------ .../jpa/management/TargetManagementTest.java | 585 ++++------------ .../management/TargetTagManagementTest.java | 205 ++---- .../management/TargetTypeManagementTest.java | 185 ++--- .../src/test/resources/jpa-test.properties | 26 +- .../test/matcher/EventVerifier.java | 176 +++-- .../test/util/AbstractIntegrationTest.java | 11 + .../test/util/TestLoggerExtension.java | 2 +- .../repository/test/util/TestdataFactory.java | 8 +- 52 files changed, 1901 insertions(+), 3042 deletions(-) delete mode 100644 hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RegexCharacterCollection.java delete mode 100644 hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetFilter.java delete mode 100644 hawkbit-repository/hawkbit-repository-api/src/test/java/org/eclipse/hawkbit/repository/RegexCharTest.java create mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/AbstractRepositoryManagementTest.java create mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/AbstractRepositoryManagementWithMetadataTest.java diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java index 6cd574306..160d1b575 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java @@ -239,11 +239,10 @@ public interface ControllerManagement { * * @param actionId to the handle status for * @param message for the status - * @return the update action in case the status has been changed to {@link Status#RETRIEVED} * @throws EntityNotFoundException if action with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) - Action registerRetrieved(long actionId, String message); + void registerRetrieved(long actionId, String message); /** * Updates attributes of the controller according to the given {@link UpdateMode}. @@ -260,8 +259,7 @@ public interface ControllerManagement { Target updateControllerAttributes(@NotEmpty String controllerId, @NotNull Map attributes, UpdateMode mode); /** - * Finds {@link Target} based on given controller ID returns found Target without details, i.e. - * NO {@link Target#getTags()} and {@link Target#getActions()} possible. + * Finds {@link Target} based on given controller ID returns found Target without details * * @param controllerId to look for. * @return {@link Target} or {@code null} if it does not exist @@ -271,9 +269,7 @@ public interface ControllerManagement { Optional findByControllerId(@NotEmpty String controllerId); /** - * Finds {@link Target} based on given ID returns found Target without details, i.e. - * NO {@link Target#getTags()} and {@link Target#getActions()} - * possible. + * Finds {@link Target} based on given ID returns found Target without details * * @param targetId to look for. * @return {@link Target} or {@code null} if it does not exist diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index 2185065dd..7a04c776e 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -242,16 +242,6 @@ public interface DeploymentManagement extends PermissionSupport { @PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) Page findActionStatusByAction(long actionId, @NotNull Pageable pageable); - /** - * Counts all the {@link ActionStatus} entries of the given {@link Action}. - * - * @param actionId to be filtered on - * @return count of {@link ActionStatus} entries - * @throws EntityNotFoundException if action with given ID does not exist - */ - @PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) - long countActionStatusByAction(long actionId); - /** * Retrieves all messages for an {@link ActionStatus}.

* No entity based access control applied. diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetInvalidationManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetInvalidationManagement.java index 115cf50b3..ab08a1b9d 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetInvalidationManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetInvalidationManagement.java @@ -13,7 +13,6 @@ import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation; -import org.eclipse.hawkbit.repository.model.DistributionSetInvalidationCount; import org.springframework.security.access.prepost.PreAuthorize; /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagManagement.java index f4c9fafd1..b97954da8 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagManagement.java @@ -9,9 +9,6 @@ */ package org.eclipse.hawkbit.repository; -import java.util.Optional; - -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @@ -20,17 +17,9 @@ import lombok.Getter; import lombok.ToString; import lombok.experimental.SuperBuilder; import org.eclipse.hawkbit.im.authentication.SpPermission; -import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions; -import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; -import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.Tag; -import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetTag; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.security.access.prepost.PreAuthorize; /** * Management service for {@link DistributionSetTag}s. diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/MaintenanceScheduleHelper.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/MaintenanceScheduleHelper.java index 145103f67..738601f43 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/MaintenanceScheduleHelper.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/MaintenanceScheduleHelper.java @@ -88,11 +88,9 @@ public final class MaintenanceScheduleHelper { if (allNotEmpty(cronSchedule, duration, timezone)) { validateCronSchedule(cronSchedule); validateDuration(duration); - // check if there is a window currently active or available in - // future. - if (!getNextMaintenanceWindow(cronSchedule, duration, timezone).isPresent()) { - throw new InvalidMaintenanceScheduleException( - "No valid maintenance window available after current time"); + // check if there is a window currently active or available in the future. + if (getNextMaintenanceWindow(cronSchedule, duration, timezone).isEmpty()) { + throw new InvalidMaintenanceScheduleException("No valid maintenance window available after current time"); } } else if (atLeastOneNotEmpty(cronSchedule, duration, timezone)) { throw new InvalidMaintenanceScheduleException("All of schedule, duration and timezone should either be null or non empty."); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/OffsetBasedPageRequest.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/OffsetBasedPageRequest.java index cd78c6b53..6d599281f 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/OffsetBasedPageRequest.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/OffsetBasedPageRequest.java @@ -18,11 +18,9 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; /** - * An implementation of the {@link PageRequest} which is offset based by means - * the offset is given and not the page number as in the original - * {@link PageRequest} implementation where the offset is generated. Due that - * the REST-API is working with {@code offset} and {@code limit} parameter we - * need an offset based page request. + * An implementation of the {@link PageRequest} which is offset based by means the offset is given and not the page number as in the original + * {@link PageRequest} implementation where the offset is generated. Due to the REST-API is working with {@code offset} and {@code limit} + * parameter we need an offset based page request. */ @Getter @EqualsAndHashCode(callSuper = true) diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/QuotaManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/QuotaManagement.java index 2afce90c9..9c1db1bd2 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/QuotaManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/QuotaManagement.java @@ -20,8 +20,7 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup; public interface QuotaManagement { /** - * @return maximum number of {@link ActionStatus} entries that the - * controller can report for an {@link Action}. + * @return maximum number of {@link ActionStatus} entries that the controller can report for an {@link Action}. */ int getMaxStatusEntriesPerAction(); @@ -31,15 +30,12 @@ public interface QuotaManagement { int getMaxAttributeEntriesPerTarget(); /** - * @return maximum number of allowed {@link RolloutGroup}s per - * {@link Rollout}. + * @return maximum number of allowed {@link RolloutGroup}s per {@link Rollout}. */ int getMaxRolloutGroupsPerRollout(); /** - * @return maximum number of - * {@link ControllerManagement#getActionHistoryMessages(Long, int)} - * for an individual {@link ActionStatus}. + * @return maximum number of action (history) messages per actions status. */ int getMaxMessagesPerActionStatus(); @@ -49,7 +45,7 @@ public interface QuotaManagement { int getMaxMetaDataEntriesPerSoftwareModule(); /** - * @return maximum number of meta data entries per distribution set + * @return maximum number of metadata entries per distribution set */ int getMaxMetaDataEntriesPerDistributionSet(); @@ -80,14 +76,12 @@ public interface QuotaManagement { int getMaxTargetsPerRolloutGroup(); /** - * @return the maximum number of target distribution set assignments - * resulting from a manual assignment + * @return the maximum number of target distribution set assignments resulting from a manual assignment */ int getMaxTargetDistributionSetAssignmentsPerManualAssignment(); /** - * @return the maximum number of targets for an automatic distribution set - * assignment + * @return the maximum number of targets for an automatic distribution set assignment */ int getMaxTargetsPerAutoAssignment(); @@ -110,5 +104,4 @@ public interface QuotaManagement { * @return the maximum number of distribution set types per target type */ int getMaxDistributionSetTypesPerTargetType(); - -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RegexCharacterCollection.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RegexCharacterCollection.java deleted file mode 100644 index 50671c957..000000000 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RegexCharacterCollection.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository; - -import java.util.Arrays; -import java.util.EnumSet; -import java.util.Optional; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * Collection of regular expression characters to check strings - */ -public class RegexCharacterCollection { - - private final EnumSet characters; - private final Pattern findAnyCharacter; - - public RegexCharacterCollection(final RegexChar... characters) { - this.characters = EnumSet.copyOf(Arrays.asList(characters)); - this.findAnyCharacter = getPatternFindAnyCharacter(); - } - - public static boolean stringContainsCharacter(final String stringToCheck, - final RegexCharacterCollection regexCharacterCollection) { - return regexCharacterCollection.findAnyCharacter.matcher(stringToCheck).matches(); - } - - private Pattern getPatternFindAnyCharacter() { - final String regexCharacters = characters.stream().map(RegexChar::getRegExp) - .collect(Collectors.joining()); - final String regularExpression = String.format(".*[%s]+.*", regexCharacters); - return Pattern.compile(regularExpression); - } - - public enum RegexChar { - WHITESPACE("\\s", "character.whitespace"), DIGITS("0-9", "character.digits"), QUOTATION_MARKS("'\"", - "character.quotationMarks"), SLASHES("\\/\\\\", "character.slashes"), GREATER_THAN( - ">"), LESS_THAN("<"), EQUALS_SYMBOL("="), EXCLAMATION_MARK("!"), QUESTION_MARK("?"), COLON(":"); - - private final String regExp; - private final String l18nReferenceDescription; - - RegexChar(final String character) { - this(character, null); - } - - RegexChar(final String regExp, final String l18nReferenceDescription) { - this.regExp = regExp; - this.l18nReferenceDescription = l18nReferenceDescription; - } - - public String getRegExp() { - return regExp; - } - - public Optional getL18nReferenceDescription() { - return Optional.ofNullable(l18nReferenceDescription); - } - } -} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryConstants.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryConstants.java index 31101d07f..4f0114d1d 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryConstants.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RepositoryConstants.java @@ -12,7 +12,6 @@ package org.eclipse.hawkbit.repository; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.model.Action; -import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModule; @@ -23,15 +22,12 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule; public final class RepositoryConstants { /** - * Prefix that the server puts in front of - * {@link ActionStatus#getMessages()} if the message is generated by the - * server. + * Prefix that the server puts in front of action status messages if the message is generated by the server. */ public static final String SERVER_MESSAGE_PREFIX = "Update Server: "; /** - * Number of {@link DistributionSetType}s that are generated as part of - * default tenant setup. + * Number of {@link DistributionSetType}s that are generated as part of default tenant setup. */ public static final int DEFAULT_DS_TYPES_IN_TENANT = 3; @@ -41,8 +37,7 @@ public final class RepositoryConstants { public static final int MAX_ACTION_COUNT = 100; /** - * Maximum number of messages that can be retrieved by a controller for an - * {@link Action}. + * Maximum number of messages that can be retrieved by a controller for an {@link Action}. */ public static final int MAX_ACTION_HISTORY_MSG_COUNT = 100; diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutExecutor.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutExecutor.java index eaf33d877..f5b1b8dd1 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutExecutor.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutExecutor.java @@ -10,8 +10,9 @@ package org.eclipse.hawkbit.repository; import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions; - import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Target; import org.springframework.security.access.prepost.PreAuthorize; @@ -22,32 +23,25 @@ import org.springframework.security.access.prepost.PreAuthorize; public interface RolloutExecutor { /** - * This execution should only be triggered by the system as a background job and - * not available via API. - * + * This execution should only be triggered by the system as a background job and not available via API. + *

* Process rollout based on its current {@link Rollout#getStatus()}. - * - * For {@link RolloutStatus#CREATING} that means creating the - * {@link RolloutGroup}s with {@link Target}s and when finished switch to - * {@link RolloutStatus#READY}. - * - * For {@link RolloutStatus#READY} that means switching to - * {@link RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is set and - * time of calling this method is beyond this point in time. This auto start - * mechanism is optional. Call {@link #start(Long)} otherwise. - * - * For {@link RolloutStatus#STARTING} that means starting the first - * {@link RolloutGroup}s in line and when finished switch to + *

+ * For {@link RolloutStatus#CREATING} that means creating the {@link org.eclipse.hawkbit.repository.model.RolloutGroup}s + * with {@link Target}s and when finished switch to {@link RolloutStatus#READY}. + *

+ * For {@link RolloutStatus#READY} that means switching to {@link RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is set and + * time of calling this method is beyond this point in time. This auto start mechanism is optional. + * Call {@link RolloutManagement#start(long)} otherwise. + *

+ * For {@link RolloutStatus#STARTING} that means starting the first {@link RolloutGroup}s in line and when finished switch to * {@link RolloutStatus#RUNNING}. - * - * For {@link RolloutStatus#RUNNING} that means checking to activate further - * groups based on the defined thresholds. Switched to + *

+ * For {@link RolloutStatus#RUNNING} that means checking to activate further groups based on the defined thresholds. Switched to * {@link RolloutStatus#FINISHED} is all groups are finished. - * - * For {@link RolloutStatus#DELETING} that means either soft delete in case - * rollout was already {@link RolloutStatus#RUNNING} which results in status - * change {@link RolloutStatus#DELETED} or hard delete from the persistence - * otherwise. + *

+ * For {@link RolloutStatus#DELETING} that means either soft delete in case rollout was already {@link RolloutStatus#RUNNING} + * which results in status change {@link RolloutStatus#DELETED} or hard delete from the persistence otherwise. */ @PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE) void execute(Rollout rollout); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java index 4073a250c..ea28601cc 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java @@ -95,15 +95,6 @@ public interface RolloutGroupManagement extends PermissionSupport { @PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) Page findByRollout(long rolloutId, @NotNull Pageable pageable); - /** - * Retrieves a page of {@link RolloutGroup}s filtered by a given {@link Rollout}. - * - * @param rolloutId the ID of the rollout to filter the {@link RolloutGroup}s - * @return a page of found {@link RolloutGroup}s - */ - @PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) - long countByRollout(long rolloutId); - /** * Get targets of specified rollout group. * diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutHandler.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutHandler.java index d8d3d3e2b..10b4a8453 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutHandler.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutHandler.java @@ -24,27 +24,22 @@ public interface RolloutHandler { /** * Process rollout based on its current {@link Rollout#getStatus()}. - * - * For {@link Rollout.RolloutStatus#CREATING} that means creating the - * {@link RolloutGroup}s with {@link Target}s and when finished switch to + *

+ * For {@link Rollout.RolloutStatus#CREATING} that means creating the {@link RolloutGroup}s with {@link Target}s and when finished switch to * {@link Rollout.RolloutStatus#READY}. - * - * For {@link Rollout.RolloutStatus#READY} that means switching to - * {@link Rollout.RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is - * set and time of calling this method is beyond this point in time. This auto + *

+ * For {@link Rollout.RolloutStatus#READY} that means switching to {@link Rollout.RolloutStatus#STARTING} if the + * {@link Rollout#getStartAt()} is set and time of calling this method is beyond this point in time. This auto * start mechanism is optional. Call {@link RolloutManagement#start(long)} otherwise. - * - * For {@link Rollout.RolloutStatus#STARTING} that means starting the first - * {@link RolloutGroup}s in line and when finished switch to + *

+ * For {@link Rollout.RolloutStatus#STARTING} that means starting the first {@link RolloutGroup}s in line and when finished switch to * {@link Rollout.RolloutStatus#RUNNING}. - * - * For {@link Rollout.RolloutStatus#RUNNING} that means checking to activate - * further groups based on the defined thresholds. Switched to + *

+ * For {@link Rollout.RolloutStatus#RUNNING} that means checking to activate further groups based on the defined thresholds. Switched to * {@link Rollout.RolloutStatus#FINISHED} is all groups are finished. - * - * For {@link Rollout.RolloutStatus#DELETING} that means either soft delete in - * case rollout was already {@link Rollout.RolloutStatus#RUNNING} which results - * in status change {@link Rollout.RolloutStatus#DELETED} or hard delete from + *

+ * For {@link Rollout.RolloutStatus#DELETING} that means either soft delete in case rollout was already + * {@link Rollout.RolloutStatus#RUNNING} which results in status change {@link Rollout.RolloutStatus#DELETED} or hard delete from * the persistence otherwise. */ @PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE) diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java index 739932af3..9acac1e55 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java @@ -86,25 +86,19 @@ public interface RolloutManagement extends PermissionSupport { long countByDistributionSetIdAndRolloutIsStoppable(long setId); /** - * Persists a new rollout entity. The filter within the - * {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets which - * are effected by this rollout to create. The amount of groups will be defined - * as equally sized. - * - * The rollout is not started. Only the preparation of the rollout is done, - * creating and persisting all the necessary groups. The Rollout and the groups - * are persisted in {@link RolloutStatus#CREATING} and - * {@link RolloutGroupStatus#CREATING}. - * - * The RolloutScheduler will start to assign targets to the groups. Once all - * targets have been assigned to the groups, the rollout status is changed to - * {@link RolloutStatus#READY} so it can be started with . + * Persists a new rollout entity. The filter within the {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets which + * are effected by this rollout to create. The amount of groups will be defined as equally sized. + *

+ * The rollout is not started. Only the preparation of the rollout is done, creating and persisting all the necessary groups. The Rollout + * and the groups are persisted in {@link RolloutStatus#CREATING} and {@link RolloutGroupStatus#CREATING}. + *

+ * The RolloutScheduler will start to assign targets to the groups. Once all targets have been assigned to the groups, the rollout status + * is changed to {@link RolloutStatus#READY} so it can be started with . * * @param create the rollout entity to create * @param amountGroup the amount of groups to split the rollout into * @param confirmationRequired if a confirmation is required by the device group(s) of the rollout - * @param conditions the rolloutgroup conditions and actions which should be applied - * for each {@link RolloutGroup} + * @param conditions the rolloutgroup conditions and actions which should be applied for each {@link RolloutGroup} * @param dynamicRolloutGroupTemplate the template for dynamic rollout groups * @return the persisted rollout. * @throws EntityNotFoundException if given {@link DistributionSet} does not exist @@ -118,61 +112,45 @@ public interface RolloutManagement extends PermissionSupport { @NotNull RolloutGroupConditions conditions, DynamicRolloutGroupTemplate dynamicRolloutGroupTemplate); /** - * Persists a new rollout entity. The filter within the - * {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets which - * are effected by this rollout to create. The amount of groups will be defined - * as equally sized. - * - * The rollout is not started. Only the preparation of the rollout is done, - * creating and persisting all the necessary groups. The Rollout and the groups - * are persisted in {@link RolloutStatus#CREATING} and - * {@link RolloutGroupStatus#CREATING}. - * - * The RolloutScheduler will start to assign targets to the groups. Once all - * targets have been assigned to the groups, the rollout status is changed to - * {@link RolloutStatus#READY} so it can be started with . + * Persists a new rollout entity. The filter within the {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets which + * are effected by this rollout to create. The amount of groups will be defined as equally sized. + *

+ * The rollout is not started. Only the preparation of the rollout is done, creating and persisting all the necessary groups. The Rollout + * and the groups are persisted in {@link RolloutStatus#CREATING} and {@link RolloutGroupStatus#CREATING}. + *

+ * The RolloutScheduler will start to assign targets to the groups. Once all targets have been assigned to the groups, the rollout status is + * changed to {@link RolloutStatus#READY} so it can be started with . * * @param create the rollout entity to create * @param amountGroup the amount of groups to split the rollout into * @param confirmationRequired if a confirmation is required by the device group(s) of the rollout - * @param conditions the rolloutgroup conditions and actions which should be applied - * for each {@link RolloutGroup} + * @param conditions the rolloutgroup conditions and actions which should be applied for each {@link RolloutGroup} * @return the persisted rollout. * @throws EntityNotFoundException if given {@link DistributionSet} does not exist * @throws ConstraintViolationException if rollout or group parameters are invalid. - * @throws AssignmentQuotaExceededException if the maximum number of allowed targets per rollout group is - * exceeded. + * @throws AssignmentQuotaExceededException if the maximum number of allowed targets per rollout group is exceeded. */ @PreAuthorize(SpringEvalExpressions.HAS_CREATE_REPOSITORY) - Rollout create(@NotNull @Valid Create create, int amountGroup, boolean confirmationRequired, - @NotNull RolloutGroupConditions conditions); + Rollout create(@NotNull @Valid Create create, int amountGroup, boolean confirmationRequired, @NotNull RolloutGroupConditions conditions); /** - * Persists a new rollout entity. The filter within the - * {@link Rollout#getTargetFilterQuery()} is used to filter the targets which - * are affected by this rollout. The given groups will be used to create the - * groups. - * - * The rollout is not started. Only the preparation of the rollout is done, - * creating and persisting all the necessary groups. The Rollout and the groups - * are persisted in {@link RolloutStatus#CREATING} and - * {@link RolloutGroupStatus#CREATING}. - * - * The RolloutScheduler will start to assign targets to the groups. Once all - * targets have been assigned to the groups, the rollout status is changed to - * {@link RolloutStatus#READY} so it can be started with - * {@link #start(long)}. + * Persists a new rollout entity. The filter within the {@link Rollout#getTargetFilterQuery()} is used to filter the targets which + * are affected by this rollout. The given groups will be used to create the groups. + *

+ * The rollout is not started. Only the preparation of the rollout is done, creating and persisting all the necessary groups. The Rollout + * and the groups are persisted in {@link RolloutStatus#CREATING} and {@link RolloutGroupStatus#CREATING}. + *

+ * The RolloutScheduler will start to assign targets to the groups. Once all targets have been assigned to the groups, the rollout status is + * changed to {@link RolloutStatus#READY} so it can be started with {@link #start(long)}. * * @param rollout the rollout entity to create * @param groups optional definition of groups - * @param conditions the rollout group conditions and actions which should be applied - * for each {@link RolloutGroup} if not defined by the RolloutGroup - * itself + * @param conditions the rollout group conditions and actions which should be applied for each {@link RolloutGroup} if not defined by the + * RolloutGroup itself * @return the persisted rollout. * @throws EntityNotFoundException if given {@link DistributionSet} does not exist * @throws ConstraintViolationException if rollout or group parameters are invalid - * @throws AssignmentQuotaExceededException if the maximum number of allowed targets per rollout group is - * exceeded. + * @throws AssignmentQuotaExceededException if the maximum number of allowed targets per rollout group is exceeded. */ @PreAuthorize(SpringEvalExpressions.HAS_CREATE_REPOSITORY) Rollout create(@Valid @NotNull Create rollout, @NotNull @Valid List groups, RolloutGroupConditions conditions); @@ -192,8 +170,7 @@ public interface RolloutManagement extends PermissionSupport { * * @param deleted flag if deleted rollouts should be included * @param pageable the page request to sort and limit the result - * @return a list of rollouts with details of targets count for different - * statuses + * @return a list of rollouts with details of targets count for different statuses */ @PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) Page findAllWithDetailedStatus(boolean deleted, @NotNull Pageable pageable); @@ -218,7 +195,7 @@ public interface RolloutManagement extends PermissionSupport { * @param rsql search text which matches name or description of rollout * @param deleted flag if deleted rollouts should be included * @param pageable the page request to sort and limit the result - * @return the founded rollout or {@code null} if rollout with given ID does not exists + * @return the founded rollout or {@code null} if rollout with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) Page findByRsqlWithDetailedStatus(@NotEmpty String rsql, boolean deleted, @NotNull Pageable pageable); @@ -356,6 +333,7 @@ public interface RolloutManagement extends PermissionSupport { /** * Stop a rollout + * * @param rolloutId of the rollout to be stopped * @return stopped rollout */ diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java index e6e64dd30..2b395405b 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java @@ -11,10 +11,7 @@ package org.eclipse.hawkbit.repository; import java.util.Collection; import java.util.Map; -import java.util.Optional; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @@ -25,8 +22,6 @@ import lombok.ToString; import lombok.experimental.SuperBuilder; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions; -import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; -import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.NamedEntity; @@ -36,7 +31,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValue; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; import org.springframework.security.access.prepost.PreAuthorize; /** @@ -90,6 +84,7 @@ public interface SoftwareModuleManagement @ToString(callSuper = true) final class Create extends UpdateCreate { + private SoftwareModuleType type; private boolean encrypted; } @@ -123,6 +118,5 @@ public interface SoftwareModuleManagement @ValidString @Size(max = SoftwareModule.VENDOR_MAX_SIZE) private String vendor; - private SoftwareModuleType type; } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java index 673382cb3..3d65dec2c 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java @@ -115,7 +115,4 @@ public interface SystemManagement { */ @PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TENANT_CONFIGURATION + "')") TenantMetaData updateTenantMetadata(long defaultDsType); - - @PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE) - TenantMetaData getTenantMetadata(long tenantId); } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java index 2db2f4417..7b92f028a 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java @@ -56,13 +56,12 @@ public interface TargetFilterQueryManagement * Verifies the provided filter syntax. * * @param query to verify - * @return true if syntax is valid * @throws RSQLParameterUnsupportedFieldException if a field in the RSQL string is used but not provided by the * given {@code fieldNameProvider} * @throws RSQLParameterSyntaxException if the RSQL syntax is wrong */ @PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY) - boolean verifyTargetFilterQuerySyntax(@NotNull String query); + void verifyTargetFilterQuerySyntax(@NotNull String query); /** * Counts all target filters that have a given auto assign distribution set 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 b1e073133..b34bb78fb 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 @@ -60,7 +60,6 @@ public interface TargetManagement String HAS_READ_TARGET_AND_READ_ROLLOUT = HAS_READ_REPOSITORY + " and hasAuthority('READ_" + SpPermission.ROLLOUT + "')"; String HAS_READ_TARGET_AND_READ_DISTRIBUTION_SET = HAS_READ_REPOSITORY + " and hasAuthority('READ_" + SpPermission.DISTRIBUTION_SET + "')"; - String DETAILS_BASE = "base"; String DETAILS_AUTO_CONFIRMATION_STATUS = "autoConfirmationStatus"; String DETAILS_TAGS = "tags"; @@ -111,11 +110,6 @@ public interface TargetManagement @PreAuthorize(HAS_READ_REPOSITORY) Target getWithDetails(@NotEmpty String controllerId, String detailsKey); - @PreAuthorize(HAS_READ_REPOSITORY) - default Target getWithDetails(@NotEmpty String controllerId) { - return getWithDetails(controllerId, DETAILS_BASE); - } - @PreAuthorize(HAS_READ_REPOSITORY) default Target getWithAutoConfigurationStatus(@NotEmpty String controllerId) { return getWithDetails(controllerId, DETAILS_AUTO_CONFIRMATION_STATUS); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java index b874f4cf2..f5790c739 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java @@ -59,8 +59,7 @@ public interface TenantConfigurationManagement extends PermissionSupport { Map> addOrUpdateConfiguration(Map configurations); /** - * Deletes a specific configuration for the current tenant. Does nothing in - * case there is no tenant specific configuration value. + * Deletes a specific configuration for the current tenant. Does nothing in case there is no tenant specific configuration value. * * @param configurationKey the configuration key to be deleted */ @@ -68,59 +67,46 @@ public interface TenantConfigurationManagement extends PermissionSupport { void deleteConfiguration(String configurationKey); /** - * Retrieves a configuration value from the e.g. tenant overwritten - * configuration values or in case the tenant does not a have a specific + * Retrieves a configuration value from the e.g. tenant overwritten configuration values or in case the tenant does not a have a specific * configuration the global default value hold in the {@link Environment}. * * @param configurationKeyName the key of the configuration - * @return the converted configuration value either from the tenant specific - * configuration stored or from the fall back default values or - * {@code null} in case key has not been configured and not default - * value exists + * @return the converted configuration value either from the tenant specific configuration stored or from the fallback default values or + * {@code null} in case key has not been configured and not default value exists * @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in general does not * match the expected type and format defined by the Key - * @throws ConversionFailedException if the property cannot be converted to the given - * {@code propertyType} + * @throws ConversionFailedException if the property cannot be converted to the given {@code propertyType} */ @PreAuthorize(value = SpringEvalExpressions.HAS_READ_REPOSITORY) TenantConfigurationValue getConfigurationValue(String configurationKeyName); /** - * Retrieves a configuration value from the e.g. tenant overwritten - * configuration values or in case the tenant does not a have a specific + * Retrieves a configuration value from the e.g. tenant overwritten configuration values or in case the tenant does not a have a specific * configuration the global default value hold in the {@link Environment}. * * @param the type of the configuration value * @param configurationKeyName the key of the configuration - * @param propertyType the type of the configuration value, e.g. {@code String.class} - * , {@code Integer.class}, etc - * @return the converted configuration value either from the tenant specific - * configuration stored or from the fallback default values or - * {@code null} in case key has not been configured and not default - * value exists + * @param propertyType the type of the configuration value, e.g. {@code String.class}, {@code Integer.class}, etc + * @return the converted configuration value either from the tenant specific configuration stored or from the fallback default values or + * {@code null} in case key has not been configured and not default value exists * @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in general does not * match the expected type and format defined by the Key - * @throws ConversionFailedException if the property cannot be converted to the given - * {@code propertyType} + * @throws ConversionFailedException if the property cannot be converted to the given {@code propertyType} */ @PreAuthorize(value = SpringEvalExpressions.HAS_READ_REPOSITORY) TenantConfigurationValue getConfigurationValue(String configurationKeyName, Class propertyType); /** - * returns the global configuration property either defined in the property - * file or an default value otherwise. + * returns the global configuration property either defined in the property file or a default value otherwise. * * @param the type of the configuration value * @param configurationKeyName the key of the configuration - * @param propertyType the type of the configuration value, e.g. {@code String.class} - * , {@code Integer.class}, etc + * @param propertyType the type of the configuration value, e.g. {@code String.class}, {@code Integer.class}, etc * @return the global configured value - * @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in the property - * file or the default value does not match the expected type - * and format defined by the Key - * @throws ConversionFailedException if the property cannot be converted to the given - * {@code propertyType} + * @throws TenantConfigurationValidatorException if the {@code propertyType} and the value in the property file or the default value + * does not match the expected type and format defined by the Key + * @throws ConversionFailedException if the property cannot be converted to the given {@code propertyType} */ @PreAuthorize(value = SpringEvalExpressions.HAS_READ_REPOSITORY) T getGlobalConfigurationValue(String configurationKeyName, Class propertyType); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantStatsManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantStatsManagement.java index af83e363c..2d3a7efd5 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantStatsManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantStatsManagement.java @@ -25,7 +25,8 @@ public interface TenantStatsManagement { * * @return collected statistics */ - @PreAuthorize("hasAuthority('" + SpRole.TENANT_ADMIN + "')" + " or " + + @PreAuthorize( + "hasAuthority('" + SpRole.TENANT_ADMIN + "')" + " or " + SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN + " or " + SpringEvalExpressions.IS_SYSTEM_CODE) TenantUsage getStatsOfTenant(); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/UpdateMode.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/UpdateMode.java index 854e08876..407cdb1ac 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/UpdateMode.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/UpdateMode.java @@ -13,8 +13,7 @@ import java.util.Arrays; import java.util.Optional; /** - * Enumerates the supported update modes. Each mode represents an attribute - * update strategy. + * Enumerates the supported update modes. Each mode represents an attribute update strategy. * * @see ControllerManagement */ @@ -34,9 +33,4 @@ public enum UpdateMode { * Removal update strategy */ REMOVE; - - public static Optional valueOfIgnoreCase(final String name) { - return Arrays.stream(values()).filter(mode -> mode.name().equalsIgnoreCase(name)).findAny(); - } - -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ValidStringValidator.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ValidStringValidator.java index 03bf5150f..e667a3e2e 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ValidStringValidator.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ValidStringValidator.java @@ -46,7 +46,7 @@ public class ValidStringValidator implements ConstraintValidator tagNames; -} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/PollStatus.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/PollStatus.java index e6219d66d..dee2e9760 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/PollStatus.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/PollStatus.java @@ -14,9 +14,8 @@ import java.time.LocalDateTime; import lombok.Data; /** - * The poll time object which holds all the necessary information around the - * target poll time, e.g. the last poll time, the next poll time and the overdue - * poll time. + * The poll time object which holds all the necessary information around the target poll time, e.g. the last poll time, the next poll time and + * the overdue poll time. */ @Data public class PollStatus { @@ -26,7 +25,8 @@ public class PollStatus { private final LocalDateTime overdueDate; private final LocalDateTime currentDate; - public PollStatus(final LocalDateTime lastPollDate, final LocalDateTime nextPollDate, + public PollStatus( + final LocalDateTime lastPollDate, final LocalDateTime nextPollDate, final LocalDateTime overdueDate, final LocalDateTime currentDate) { this.lastPollDate = lastPollDate; this.nextPollDate = nextPollDate; @@ -35,11 +35,9 @@ public class PollStatus { } /** - * calculates if the target poll time is overdue and the target has not been - * polled in the configured poll time interval. + * Calculates if the target poll time is overdue and the target has not been polled in the configured poll time interval. * - * @return {@code true} if the current time is after the poll time overdue - * date otherwise {@code false}. + * @return {@code true} if the current time is after the poll time overdue date otherwise {@code false}. */ public boolean isOverdue() { return currentDate.isAfter(overdueDate); diff --git a/hawkbit-repository/hawkbit-repository-api/src/test/java/org/eclipse/hawkbit/repository/RegexCharTest.java b/hawkbit-repository/hawkbit-repository-api/src/test/java/org/eclipse/hawkbit/repository/RegexCharTest.java deleted file mode 100644 index b508b58ec..000000000 --- a/hawkbit-repository/hawkbit-repository-api/src/test/java/org/eclipse/hawkbit/repository/RegexCharTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.eclipse.hawkbit.repository.RegexCharacterCollection.RegexChar; -import org.junit.jupiter.api.Test; - -/** - * Feature: Unit Tests - Repository
- * Story: Regular expression helper - */ -class RegexCharTest { - - private static final int INDEX_FIRST_PRINTABLE_ASCII_CHAR = 32; - private static final int INDEX_LAST_PRINTABLE_ASCII_CHAR = 127; - private static final String TEST_STRING = getPrintableAsciiCharacters(); - - /** - * Verifies every RegexChar can be used to exclusively find the desired characters in a String. - */ - @Test - void allRegexCharsOnlyFindExpectedChars() { - for (final RegexChar character : RegexChar.values()) { - switch (character) { - case DIGITS: - assertRegexCharExclusivelyFindsGivenCharacters(character, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); - break; - case WHITESPACE: - assertRegexCharExclusivelyFindsGivenCharacters(character, " ", "\t"); - break; - case SLASHES: - assertRegexCharExclusivelyFindsGivenCharacters(character, "/", "\\"); - break; - case QUOTATION_MARKS: - assertRegexCharExclusivelyFindsGivenCharacters(character, "\"", "'"); - break; - default: - assertRegexCharExclusivelyFindsGivenCharacters(character, character.getRegExp()); - break; - } - } - } - - /** - * Verifies that combinations of RegexChars can be used to find the desired characters in a String. - */ - @Test - void combinedRegexCharsFindExpectedChars() { - final RegexCharacterCollection greaterAndLessThan = new RegexCharacterCollection(RegexChar.GREATER_THAN, - RegexChar.LESS_THAN); - final RegexCharacterCollection equalsAndQuestionMark = new RegexCharacterCollection(RegexChar.EQUALS_SYMBOL, - RegexChar.QUESTION_MARK); - final RegexCharacterCollection colonAndWhitespace = new RegexCharacterCollection(RegexChar.COLON, - RegexChar.WHITESPACE); - - assertRegexCharsExclusivelyFindsGivenCharacters(greaterAndLessThan, "<", ">"); - assertRegexCharsExclusivelyFindsGivenCharacters(equalsAndQuestionMark, "=", "?"); - assertRegexCharsExclusivelyFindsGivenCharacters(colonAndWhitespace, ":", " ", "\t"); - } - - private static String getPrintableAsciiCharacters() { - final StringBuilder stringBuilder = new StringBuilder(); - for (int i = INDEX_FIRST_PRINTABLE_ASCII_CHAR; i < INDEX_LAST_PRINTABLE_ASCII_CHAR; i++) { - stringBuilder.append((char) i); - } - stringBuilder.append("\t"); - return stringBuilder.toString(); - } - - private static String insertStringIntoString(final String baseString, final String stringToInsert, - final int position) { - final StringBuilder stringBuilder = new StringBuilder(baseString); - return stringBuilder.insert(position, stringToInsert).toString(); - } - - private void assertRegexCharExclusivelyFindsGivenCharacters(final RegexChar characterToVerify, - final String... charactersExpectedToBeFoundByRegex) { - assertRegexCharsExclusivelyFindsGivenCharacters(new RegexCharacterCollection(characterToVerify), - charactersExpectedToBeFoundByRegex); - } - - private void assertRegexCharsExclusivelyFindsGivenCharacters(final RegexCharacterCollection regexToVerify, - final String... charactersExpectedToBeFoundByRegex) { - String notMatchingString = TEST_STRING; - for (final String character : charactersExpectedToBeFoundByRegex) { - notMatchingString = notMatchingString.replace(character, ""); - } - for (final String character : charactersExpectedToBeFoundByRegex) { - assertThat(RegexCharacterCollection.stringContainsCharacter("", regexToVerify)).isFalse(); - assertThat(RegexCharacterCollection.stringContainsCharacter(notMatchingString, regexToVerify)).isFalse(); - assertThat(RegexCharacterCollection.stringContainsCharacter(character, regexToVerify)).isTrue(); - assertThat(RegexCharacterCollection - .stringContainsCharacter(insertStringIntoString(notMatchingString, character, 0), regexToVerify)) - .isTrue(); - assertThat(RegexCharacterCollection.stringContainsCharacter( - insertStringIntoString(notMatchingString, character, notMatchingString.length()), regexToVerify)).isTrue(); - assertThat(RegexCharacterCollection.stringContainsCharacter( - insertStringIntoString(notMatchingString, character, notMatchingString.length() / 2), regexToVerify)).isTrue(); - - } - } -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRepositoryConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRepositoryConfiguration.java index 82c807036..d759796a4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRepositoryConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRepositoryConfiguration.java @@ -156,7 +156,7 @@ public class JpaRepositoryConfiguration { * @return the {@link MethodValidationPostProcessor} */ @Bean - public MethodValidationPostProcessor methodValidationPostProcessor() { + public static MethodValidationPostProcessor methodValidationPostProcessor() { final MethodValidationPostProcessor processor = new MethodValidationPostProcessor(); // ValidatorFactory shall NOT be closed because after closing the generated Validator // methods shall not be called - we need the validator in future @@ -170,7 +170,7 @@ public class JpaRepositoryConfiguration { } @Bean - public BeanPostProcessor entityManagerBeanPostProcessor( + public static BeanPostProcessor entityManagerBeanPostProcessor( @Autowired(required = false) final AccessController artifactAccessController, @Autowired(required = false) final AccessController softwareModuleTypeAccessController, @Autowired(required = false) final AccessController softwareModuleAccessController, diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java index 713044d97..b094b549f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java @@ -431,8 +431,8 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont @Transactional @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public Action registerRetrieved(final long actionId, final String message) { - return handleRegisterRetrieved(actionId, message); + public void registerRetrieved(final long actionId, final String message) { + handleRegisterRetrieved(actionId, message); } @Override @@ -895,10 +895,8 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont * * @param actionId to the handle status for * @param message for the status - * @return the updated action in case the status has been changed to - * {@link Status#RETRIEVED} */ - private Action handleRegisterRetrieved(final Long actionId, final String message) { + private void handleRegisterRetrieved(final Long actionId, final String message) { final JpaAction action = actionRepository.getById(actionId); // do a manual query with CriteriaBuilder to avoid unnecessary field queries and an extra // count query made by spring-data when using pageable requests, we don't need an extra count @@ -910,8 +908,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont .multiselect(actionStatusRoot.get(AbstractJpaBaseEntity_.id), actionStatusRoot.get(JpaActionStatus_.status)) .where(cb.equal(actionStatusRoot.get(JpaActionStatus_.action).get(AbstractJpaBaseEntity_.id), actionId)) .orderBy(cb.desc(actionStatusRoot.get(AbstractJpaBaseEntity_.id))); - final List resultList = entityManager.createQuery(query).setFirstResult(0).setMaxResults(1) - .getResultList(); + final List resultList = entityManager.createQuery(query).setFirstResult(0).setMaxResults(1).getResultList(); // if the latest status is not in retrieve state then we add a retrieved state again, we want // to document a deployment retrieved status and a cancel retrieved status, but multiple @@ -919,8 +916,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont // case controller retrieves a action multiple times. if (resultList.isEmpty() || (Status.RETRIEVED != resultList.get(0)[1])) { // document that the status has been retrieved - actionStatusRepository - .save(new JpaActionStatus(action, Status.RETRIEVED, System.currentTimeMillis(), message)); + actionStatusRepository.save(new JpaActionStatus(action, Status.RETRIEVED, System.currentTimeMillis(), message)); // don't change the action status itself in case the action is in // canceling state otherwise @@ -928,10 +924,8 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont // cancel job anymore. if (!action.isCancelingOrCanceled()) { action.setStatus(Status.RETRIEVED); - return actionRepository.save(action); } } - return action; } private void cancelAssignDistributionSetEvent(final Action action) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java index deb943197..3604afda4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java @@ -316,16 +316,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl return actionStatusRepository.findByActionId(pageable, actionId); } - @Override - public long countActionStatusByAction(final long actionId) { - assertActionExistsAndAccessible(actionId); - - return actionStatusRepository.countByActionId(actionId); - } - - // action is already got and there are checked read permissions - do not check - // permissions - // and UI which is to be removed + // action is already got and there are checked read permissions - do not check permissions and UI which is to be removed @Override public Page findMessagesByActionStatusId(final long actionStatusId, final Pageable pageable) { final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutGroupManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutGroupManagement.java index b0d945b9c..d6a5f4377 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutGroupManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutGroupManagement.java @@ -153,13 +153,6 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement { return JpaManagementHelper.convertPage(rolloutGroupRepository.findByRolloutId(rolloutId, pageable), pageable); } - @Override - public long countByRollout(final long rolloutId) { - throwEntityNotFoundExceptionIfRolloutDoesNotExist(rolloutId); - - return rolloutGroupRepository.countByRolloutId(rolloutId); - } - @Override public Page findTargetsOfRolloutGroup(final long rolloutGroupId, final Pageable page) { final JpaRolloutGroup rolloutGroup = rolloutGroupRepository.getById(rolloutGroupId); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java index 7ba07ac44..7c864bc71 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java @@ -271,11 +271,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst return tenantMetaDataRepository.save(data); } - @Override - public TenantMetaData getTenantMetadata(final long tenantId) { - return tenantMetaDataRepository.findById(tenantId).orElseThrow(() -> new EntityNotFoundException(TenantMetaData.class, tenantId)); - } - private static boolean isPostgreSql(final JpaProperties properties) { return Database.POSTGRESQL == properties.getDatabase(); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetFilterQueryManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetFilterQueryManagement.java index a8c9e8ef1..91d104a3b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetFilterQueryManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetFilterQueryManagement.java @@ -30,6 +30,7 @@ import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; +import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; @@ -114,13 +115,12 @@ class JpaTargetFilterQueryManagement } @Override - public boolean verifyTargetFilterQuerySyntax(final String query) { + public void verifyTargetFilterQuerySyntax(final String query) { try { RsqlUtility.getInstance().validateRsqlFor(query, TargetFields.class, JpaTarget.class); - return true; } catch (final RSQLParserException | RSQLParameterUnsupportedFieldException e) { log.debug("The RSQL query '{}}' is invalid.", query, e); - return false; + throw new RSQLParameterSyntaxException("Cannot create a Rollout with an empty target query filter!"); } } 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 5f323145b..4b9e77fd3 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 @@ -464,7 +464,7 @@ public class JpaTargetManagement // get the modifiable metadata map final Map metadata = target.getMetadata(); if (!metadata.containsKey(key)) { - throw new EntityNotFoundException("Target metadata", controllerId + ":" + key); + assertMetadataQuota(target.getId(), metadata.size() + 1); } metadata.put(key, value); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java index 2fe260b43..b42a61361 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.model; import java.io.Serial; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Optional; @@ -119,7 +120,7 @@ public class JpaDistributionSet foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_metadata_ds")) @MapKeyColumn(name = "meta_key", length = DistributionSet.METADATA_MAX_KEY_SIZE) @Column(name = "meta_value", length = DistributionSet.METADATA_MAX_VALUE_SIZE) - private Map metadata; + private Map metadata = new HashMap<>(); @Column(name = "complete") private boolean complete; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModule.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModule.java index 5f86ab1c9..33d409378 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModule.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModule.java @@ -119,22 +119,9 @@ public class JpaSoftwareModule @ManyToMany(mappedBy = "modules", targetEntity = JpaDistributionSet.class, fetch = FetchType.LAZY) private List assignedTo; - /** - * Parameterized constructor. - */ public JpaSoftwareModule(final SoftwareModuleType type, final String name, final String version) { - this(type, name, version, null, null, false); - } - - /** - * Parameterized constructor. - */ - public JpaSoftwareModule(final SoftwareModuleType type, final String name, final String version, - final String description, final String vendor, final boolean encrypted) { - super(name, version, description); - this.vendor = vendor; + super(name, version, null); this.type = (JpaSoftwareModuleType) type; - this.encrypted = encrypted; } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java index dd3cc503d..289072a96 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java @@ -20,7 +20,6 @@ import java.util.stream.StreamSupport; import lombok.extern.slf4j.Slf4j; import org.assertj.core.api.Assertions; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; -import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; @@ -48,7 +47,6 @@ import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecific import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionStatusCreate; import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSetFilter; import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; @@ -167,11 +165,18 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest targets.stream().map(Target::getControllerId).toList(), tag.getId()); } - protected List assignTag(final Collection sets, - final DistributionSetTag tag) { + protected List assignTag(final Collection sets, final DistributionSetTag tag) { return distributionSetManagement.assignTag(sets.stream().map(DistributionSet::getId).toList(), tag.getId()); } + protected List assignTags(final Collection sets, final DistributionSetTag... tags) { + List result = null; + for (DistributionSetTag tag : tags) { + result = assignTag(sets, tag); + } + return result; + } + protected List unassignTag( final Collection sets, final DistributionSetTag tag) { return distributionSetManagement.unassignTag(sets.stream().map(DistributionSet::getId).toList(), tag.getId()); @@ -181,8 +186,8 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest targets.stream().map(Target::getControllerId).forEach(id -> targetManagement.assignType(id, type.getId())); } - protected void assertRollout(final Rollout rollout, final boolean dynamic, final Rollout.RolloutStatus status, final int groupCreated, - final long totalTargets) { + protected void assertRollout( + final Rollout rollout, final boolean dynamic, final Rollout.RolloutStatus status, final int groupCreated, final long totalTargets) { final Rollout refreshed = refresh(rollout); assertThat(refreshed.isDynamic()).as("Is dynamic").isEqualTo(dynamic); assertThat(refreshed.getStatus()).as("Status").isEqualTo(status); @@ -229,43 +234,4 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest private JpaRollout refresh(final Rollout rollout) { return rolloutRepository.findById(rollout.getId()).get(); } - - protected Slice findDsByDistributionSetFilter(final DistributionSetFilter distributionSetFilter, final Pageable pageable) { - final List> specList = buildDistributionSetSpecifications(distributionSetFilter); - return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetRepository, specList, pageable); - } - - private static List> buildDistributionSetSpecifications( - final DistributionSetFilter distributionSetFilter) { - final List> specList = new ArrayList<>(10); - - if (distributionSetFilter.getIsComplete() != null) { - specList.add(DistributionSetSpecification.isCompleted(distributionSetFilter.getIsComplete())); - } - if (distributionSetFilter.getIsDeleted() != null) { - specList.add(DistributionSetSpecification.isDeleted(distributionSetFilter.getIsDeleted())); - } - if (distributionSetFilter.getIsValid() != null) { - specList.add(DistributionSetSpecification.isValid(distributionSetFilter.getIsValid())); - } - if (distributionSetFilter.getTypeId() != null) { - specList.add(DistributionSetSpecification.byType(distributionSetFilter.getTypeId())); - } - if (!ObjectUtils.isEmpty(distributionSetFilter.getSearchText())) { - final String[] dsFilterNameAndVersionEntries = JpaManagementHelper - .getFilterNameAndVersionEntries(distributionSetFilter.getSearchText().trim()); - specList.add(DistributionSetSpecification.likeNameAndVersion(dsFilterNameAndVersionEntries[0], dsFilterNameAndVersionEntries[1])); - } - if (hasTagsFilterActive(distributionSetFilter)) { - specList.add(DistributionSetSpecification.hasTags( - distributionSetFilter.getTagNames(), distributionSetFilter.getSelectDSWithNoTag())); - } - return specList; - } - - private static boolean hasTagsFilterActive(final DistributionSetFilter distributionSetFilter) { - final boolean isNoTagActive = Boolean.TRUE.equals(distributionSetFilter.getSelectDSWithNoTag()); - final boolean isAtLeastOneTagActive = !CollectionUtils.isEmpty(distributionSetFilter.getTagNames()); - return isNoTagActive || isAtLeastOneTagActive; - } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/AbstractRepositoryManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/AbstractRepositoryManagementTest.java new file mode 100644 index 000000000..42d543192 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/AbstractRepositoryManagementTest.java @@ -0,0 +1,662 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.management; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.repository.Identifiable; +import org.eclipse.hawkbit.repository.RepositoryManagement; +import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.model.BaseEntity; +import org.eclipse.hawkbit.repository.test.matcher.EventVerifier; +import org.eclipse.hawkbit.repository.test.matcher.Expect; +import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; +import org.eclipse.hawkbit.utils.ObjectCopyUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.core.ResolvableType; +import org.springframework.util.ObjectUtils; + +@Slf4j +public abstract class AbstractRepositoryManagementTest> + extends AbstractJpaIntegrationTest { + + protected RepositoryManagement repositoryManagement; + + @Getter(AccessLevel.PROTECTED) + private Class entityType; // T + private Class createType; // C + private Class updateType; // U + private Function, ?> entityFactory; + + @BeforeEach + void setup() { + synchronized (AbstractRepositoryManagementTest.class) { + if (repositoryManagement == null) { + final RepoMan repoMan = resolveRepositoryManagement(); + repositoryManagement = repoMan.repo; + entityType = repoMan.entityType(); + createType = repoMan.createType(); + updateType = repoMan.updateType(); + entityFactory = repoMan.entityFactory; + } + } + + expectedEvents.clear(); + EventVerifier.DYNAMIC_EXPECTATIONS.set(() -> expectedEvents.entrySet().stream() + .map(e -> new EventVerifier.DynamicExpect(e.getKey(), e.getValue().get())) + .toList() + .toArray(new Expect[0])); + } + + // also test get/find(Long) + @ExpectEvents + @Test + void create() { + final T instance = instance(); + assertEquals(repositoryManagement.get(instance.getId()), instance); + assertThat(repositoryManagement.find(instance.getId())) + .isPresent() + .hasValueSatisfying(get -> assertEquals(get, instance)); + } + + // also test get/find(Collection) + @ExpectEvents + @Test + void creates() { + final List instances = instances(); + final List instanceIds = instances.stream().map(Identifiable::getId).toList(); + assertEquals(repositoryManagement.get(instanceIds), instances); + assertThat(repositoryManagement.find(instanceIds)).hasSize(instances.size()); + } + + @Test + void findCountAll() { + // some entities as distribution set types has pre-initialized entities + final int alreadyExisting = repositoryManagement.findAll(UNPAGED).getContent().size(); + final int count = 5; + final List instances = instances(count); // create 'count' instances + assertThat(repositoryManagement.findAll(UNPAGED).getContent()).as("Wrong size of all entities").hasSize(count + alreadyExisting); + assertThat(repositoryManagement.count()).as("Wrong size of all entities").isEqualTo(count + alreadyExisting); + + repositoryManagement.delete(instances.get(0).getId()); + assertThat(repositoryManagement.findAll(UNPAGED).getContent()).as("Wrong size of all entities").hasSize(count + alreadyExisting - 1); + assertThat(repositoryManagement.count()).as("Wrong size of all entities").isEqualTo(count + alreadyExisting - 1); + } + + @ExpectEvents + @Test + void update() { + final T instance = instance(); + + final U update = forBuildableTypeRe(updateType, instance.getId()); + incrementEvents(entityType, EventType.UPDATED); + final T instanceUpdated = repositoryManagement.update(update); + assertNotNull(instanceUpdated); + assertEquals(instanceUpdated, update); + + final T get = repositoryManagement.get(instance.getId()); + assertEquals(get, update); + assertEquals(get, ObjectCopyUtil.copy(update, instance, false, UnaryOperator.identity())); + } + + /** + * Calling update without changing fields results in no recorded change in the repository including unchanged audit fields. + */ + @SneakyThrows + @Test + void updateNothingDontChangeRepository() { + final T instance = instance(); + + final U emptyUpdate = forBuildableType(updateType, instance.getId(), (name, type) -> { + try { + try { + final Method getter = entityType.getMethod("get" + Character.toUpperCase(name.charAt(0)) + name.substring(1)); + if (getter.getReturnType() == type) { + return getter.invoke(instance); + } + } catch (final NoSuchMethodException e) { + if (type == boolean.class || type == Boolean.class) { + final Method getter = entityType.getMethod("is" + Character.toUpperCase(name.charAt(0)) + name.substring(1)); + if (getter.getReturnType() == boolean.class || getter.getReturnType() == Boolean.class) { + return getter.invoke(instance); + } + } + } + } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e2) { + log.debug("Could not invoke getter for {} in {}", name, instance, e2); + } + return null; + }); + + final T updated = repositoryManagement.update(emptyUpdate); + assertThat(updated.getOptLockRevision()) + .as("Expected version number of updated entity to be equal to created version") + .isEqualTo(instance.getOptLockRevision()); + } + + @ExpectEvents + @Test + void delete() { + final Long instanceId = instance().getId(); + + incrementEvents(entityType, EventType.DELETED); + repositoryManagement.delete(instanceId); + assertThat(repositoryManagement.find(instanceId)).isEmpty(); + assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> repositoryManagement.get(instanceId)); + } + + @ExpectEvents + @Test + void deletes() { + final List instanceIds = instances().stream().map(Identifiable::getId).toList(); + final int count = instanceIds.size(); + assertThat(repositoryManagement.get(instanceIds)).hasSize(count); + assertThat(repositoryManagement.find(instanceIds)).hasSize(count); + + incrementEvents(entityType, EventType.DELETED, count); + repositoryManagement.delete(instanceIds); + for (final Long instanceId : instanceIds) { + assertThat(repositoryManagement.find(instanceId)).isEmpty(); + assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> repositoryManagement.get(instanceId)); + } + } + + @Test + void failToReadUpdateDeleteNotExisting() { + assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> repositoryManagement.get(NOT_EXIST_IDL)); + assertThat(repositoryManagement.find(-1L)).isEmpty(); + final U notExistUpdate = forBuildableTypeRe(updateType, NOT_EXIST_IDL); + assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> repositoryManagement.update(notExistUpdate)); + assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> repositoryManagement.delete(NOT_EXIST_IDL)); + } + + @Test + void failToDuplicate() { + final C create = forType(createType); + incrementEvents(entityType, EventType.CREATED); + repositoryManagement.create(create); + assertThatExceptionOfType(EntityAlreadyExistsException.class).isThrownBy(() -> repositoryManagement.create(create)); + } + + protected T instance() { + final C create = forType(createType); + incrementEvents(entityType, EventType.CREATED); + final T instance = repositoryManagement.create(create); + + assertNotNull(instance); + assertEquals(instance, create); + final Long instanceId = instance.getId(); + assertNotNull(repositoryManagement.get(instanceId)); + assertThat(repositoryManagement.find(instanceId)).isPresent(); + + return instance; + } + + protected List instances() { + return instances(1 + RND.nextInt(5)); + } + + protected List instances(final int count) { + final List creates = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + creates.add(forType(createType)); + } + incrementEvents(entityType, EventType.CREATED, count); + final List instances = repositoryManagement.create(creates); + + assertNotNull(instances); + assertThat(instances).hasSize(count); + assertEquals(instances, creates); + + return instances; + } + + // asserts that all expected's fields (getters) are equal to the actual's fields + // does a deep compare for fields which are not primitive or String + protected void assertEquals(final Object actual, final Object expected) { + final Deque stack = new ArrayDeque<>(); + try { + assertEquals(actual, expected, stack); + } catch (final AssertionError e) { + throw new AssertionError( + String.format( + "Comparison failed at path: %s%n\tActual: %s,%n\tExpected: %s", + String.join("->", stack), actual, expected), + e); + } + } + + private final Map, AtomicInteger> expectedEvents = new ConcurrentHashMap<>(); + + protected void incrementEvents(final Class entityType, final EventType eventType) { + incrementEvents(entityType, eventType, 1); + } + + protected void incrementEvents(final Class entityType, final EventType eventType, final int count) { + expectedEvents.computeIfAbsent(eventType.getEventType(entityType), k -> new AtomicInteger(0)).addAndGet(count); + } + + protected static Type[] genericTypes(final Class clazz, final Class targetSuperClass) { + return findGenericSuperType(ResolvableType.forType(clazz), targetSuperClass) + .map(ResolvableType::resolveGenerics) + .orElseThrow(() -> + new IllegalStateException("No generic type found for " + targetSuperClass.getName() + " in " + clazz.getName())); + } + + protected final AtomicLong counter = new AtomicLong(); + + @SuppressWarnings("unchecked") + protected O forType(final Class type) { + if (type == boolean.class || type == Boolean.class) { + return (O) (counter.incrementAndGet() % 2 == 0 ? Boolean.TRUE : Boolean.FALSE); + } else if (type == Integer.class || type == int.class) { + return (O) Integer.valueOf((int) counter.incrementAndGet()); + } else if (type == Long.class || type == long.class) { + return (O) Long.valueOf(counter.incrementAndGet()); + } else if (type == Float.class || type == float.class) { + return (O) Float.valueOf(counter.incrementAndGet()); + } else if (type == Double.class || type == double.class) { + return (O) Double.valueOf(counter.incrementAndGet()); + } else if (type == String.class) { + return (O) ("test-" + counter.incrementAndGet()); + } else if (type == Set.class) { + final Set set = new HashSet<>(); +// set.add(forType(createType)); + return (O) set; + } else if (type.isEnum()) { + final O[] constants = type.getEnumConstants(); + return constants[(int) (counter.incrementAndGet() % constants.length)]; + } else { + // entity classes for created events are root level classes + if ("org.eclipse.hawkbit.repository.model".equals(type.getPackageName()) && BaseEntity.class.isAssignableFrom(type)) { + try { + incrementEvents(type, EventType.CREATED); + return (O) entityFactory.apply(type); + } catch (final Exception e) { + log.error("Could not create instance of {} using entity factory", type.getName(), e); + } + } + try { // try with constructor + final Constructor[] constructors = type.getConstructors(); + if (ObjectUtils.isEmpty(constructors)) { + throw new NoSuchMethodException("No public constructor found for " + type); + } + // prefer empty constructor + for (final Constructor constructor : constructors) { + if (constructor.getParameterCount() == 0) { + return (O) constructor.newInstance(); + } + } + constructors[0].setAccessible(true); + return (O) constructors[0].newInstance(Stream.of(constructors[0].getParameterTypes()) + .map(this::forType) + .toArray()); + } catch (final ReflectiveOperationException e) { // try with builder + // try builder pattern + try { + return forBuildableType(type, null); + } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e1) { + log.debug("{} is not a builder. Throws could not instantiate", type.getName()); + } + log.error("Could not instantiate {}", type.getName(), e); + throw new IllegalStateException("Could not create instance of " + createType.getName(), e); + } + } + } + + protected Object builderParameterValue(final Method builderSetter) { + return forType(builderSetter.getParameterTypes()[0]); + } + + private O forBuildableTypeRe(final Class type, final Long id) { + try { + return forBuildableType(type, id); + } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + log.debug("{} is not a buildable type", type.getName()); + throw new IllegalStateException("Could not create instance of " + createType.getName(), e); + } + } + + private O forBuildableType(final Class type, final Long id) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + return forBuildableType(type, id, null); + } + + @SuppressWarnings("unchecked") + private O forBuildableType(final Class type, final Long id, final BiFunction, Object> propertyValueSupplier) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + final Object builder = type.getMethod("builder").invoke(null); + Arrays.stream(builder.getClass().getMethods()) + .filter(method -> method.getParameterCount() == 1) + .filter(method -> method.getDeclaringClass() != Object.class) + .forEach(method -> { + try { + if (id != null && "id".equals(method.getName()) && method.getParameterTypes()[0] == Long.class) { + method.invoke(builder, id); + } else { + if (propertyValueSupplier == null) { + method.invoke(builder, builderParameterValue(method)); + } else { + final Object propertyValue = propertyValueSupplier.apply(method.getName(), method.getParameterTypes()[0]); + if (propertyValue != null) { + method.invoke(builder, propertyValue); + } + } + } + } catch (final IllegalAccessException ex) { + throw new RuntimeException(ex); + } catch (final InvocationTargetException ex) { + throw new RuntimeException(ex.getTargetException() == null ? ex : ex.getTargetException()); + } + }); + final Method build = builder.getClass().getDeclaredMethod("build"); + build.setAccessible(true); + return (O) build.invoke(builder); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private RepoMan resolveRepositoryManagement() { + final Type[] abstractTestTypeArgs = genericTypes(getClass(), AbstractRepositoryManagementTest.class); + final List fields = fields(getClass(), new ArrayList<>()).stream() + .peek(field -> field.setAccessible(true)) + .filter(field -> RepositoryManagement.class.isAssignableFrom(field.getType())) + .filter(field -> field.getDeclaringClass() != AbstractRepositoryManagementTest.class || + !"repositoryManagement".equals(field.getName())) + .toList(); + final Map, Supplier> entityCreators = new HashMap<>(); + fields.forEach(field -> { + try { + field.setAccessible(true); + final RepositoryManagement entityManager = (RepositoryManagement) field.get(this); + final Type[] fieldGenericTypes = genericTypes(field.getType(), RepositoryManagement.class); + entityCreators.put((Class) fieldGenericTypes[0], () -> entityManager.create(forType((Class) fieldGenericTypes[1]))); + } catch (final IllegalAccessException e) { + throw new IllegalStateException("Could not access field " + field.getName(), e); + } + }); + return new RepoMan( + fields.stream() + .peek(field -> System.out.println("Found RepositoryManagement field: " + field.getName() + "-> " + Arrays.toString( + genericTypes(field.getType(), RepositoryManagement.class)))) + .filter(field -> Arrays.equals(genericTypes(field.getType(), RepositoryManagement.class), abstractTestTypeArgs)) + .map(field -> { + field.setAccessible(true); + try { + return field.get(this); + } catch (final IllegalAccessException e) { + throw new IllegalStateException("Could not access field " + field.getName(), e); + } + }) + .map(RepositoryManagement.class::cast) + .findAny() + .orElseThrow(() -> new IllegalStateException( + "No field matching the generic types found: " + Arrays.toString(abstractTestTypeArgs))), + (Class) abstractTestTypeArgs[0], + (Class) abstractTestTypeArgs[1], + (Class) abstractTestTypeArgs[2], + entityClass -> { + final Supplier entitySupplier = entityCreators.get(entityClass); + if (entitySupplier == null) { + throw new IllegalStateException("No entity creator found for " + entityClass.getName()); + } + return entitySupplier.get(); + }); + } + + private List fields(final Class clazz, final List fields) { + Collections.addAll(fields, clazz.getDeclaredFields()); + final Class superClass = clazz.getSuperclass(); + if (superClass != null) { + fields(superClass, fields); + } + return fields; + } + + private static Optional findGenericSuperType(final ResolvableType resolvableType, final Class targetSuperClass) { + if (resolvableType.getRawClass() == targetSuperClass) { + return Optional.of(resolvableType); + } + final Optional inInterfaces = Arrays.stream(resolvableType.getInterfaces()) + .filter(superInterface -> superInterface.getRawClass() == targetSuperClass) + .findAny(); + if (inInterfaces.isPresent()) { + return inInterfaces; + } else if (resolvableType.getSuperType() != ResolvableType.NONE) { + return findGenericSuperType(resolvableType.getSuperType(), targetSuperClass); + } else { + return Optional.empty(); + } + } + + private void assertEquals(final Object actual, final Object expected, final Deque path) { + if (actual == expected) { + return; // same reference + } + if (expected == null || actual == null) { + throw new AssertionError("One of the expected and actual is null the other is not"); + } + if (expected.equals(actual)) { + return; // equal + } + final Class expectedClass = expected.getClass(); + final Class actualClass = actual.getClass(); + if (actualClass == expectedClass) { + if (actual.equals(expected)) { + if (actualClass == Boolean.class || + actualClass == Integer.class || actualClass == Long.class || + actualClass == Float.class || actualClass == Double.class || + actualClass == String.class) { + return; + } + } else if (!(Collection.class.isAssignableFrom(actualClass))) { + throw new AssertionError("Expected:\n\t" + expected + "but got:\n\t" + actual); + } + } + if (expected instanceof Collection expectedCollection) { + if (actual instanceof Collection actualCollection) { + if (actualCollection.size() != expectedCollection.size()) { + throw new AssertionError("Expected collection size " + expectedCollection.size() + " but got " + actualCollection.size()); + } + final AtomicInteger index = new AtomicInteger(0); + expectedCollection.iterator().forEachRemaining(expectedElement -> { + index.incrementAndGet(); + actualCollection.stream() + .filter(actualElement -> { + try { + assertEquals(actualElement, expectedElement, path); + return true; + } catch (final AssertionError e) { + return false; + } + }) + .findFirst() + .orElseThrow( + () -> { + path.addLast("[" + index.get() + ", not found]"); + return new AssertionError("Expected collection to contain " + expectedElement + " but it does not"); + }); + }); + return; + } else { + throw new AssertionError("Expected a collection but got " + actual.getClass().getName()); + } + } else if (expectedClass.isArray()) { + if (actual.getClass().isArray()) { + final int expectedLength = Array.getLength(expected); + final int actualLength = Array.getLength(actual); + if (expectedLength != actualLength) { + throw new AssertionError("Expected array length " + expectedLength + " but got " + actualLength); + } + for (int i = 0; i < expectedLength; i++) { + final Object expectedElement = Array.get(expected, i); + path.addLast("[" + i + ", not found]"); + assertEquals(Array.get(actual, i), expectedElement, path); + path.removeLast(); + } + return; + } else { + throw new AssertionError("Expected an array but got " + actual.getClass().getName()); + } + } else if (expected instanceof Optional expectedOptional) { + if (actual instanceof Optional actualOptional) { + if (expectedOptional.isPresent() && actualOptional.isPresent()) { + path.addLast(".get()"); + assertEquals(actualOptional.get(), expectedOptional.get(), path); + + } else if (expectedOptional.isEmpty() && actualOptional.isEmpty()) { + return; // both are empty + } else { + throw new AssertionError( + "Expected optional to be " + (expectedOptional.isPresent() ? "present" : "empty") + + " but got " + (actualOptional.isPresent() ? "present" : "empty")); + } + return; + } else { + throw new AssertionError("Expected an Optional but got " + actual.getClass().getName()); + } + } else if (expected instanceof Map expectedMap) { + if (actual instanceof Map actualMap) { + if (actualMap.size() != expectedMap.size()) { + throw new AssertionError("Expected map size " + expectedMap.size() + " but got " + actualMap.size()); + } + expectedMap.forEach((key, expectedValue) -> { + path.add("[" + key + "]"); + assertEquals(actualMap.get(key), expectedValue, path); + path.removeLast(); + }); + return; + } else { + throw new AssertionError("Expected a map but got " + actual.getClass().getName()); + } + } + // compare fields + for (final Method expectedGetter : expectedClass.getMethods()) { + if (expectedGetter.getDeclaringClass() != Object.class && + expectedGetter.getParameterCount() == 0 && + (expectedGetter.getName().startsWith("get") || expectedGetter.getName().startsWith("is"))) { + final Object expectedValue; + try { + expectedValue = expectedGetter.invoke(expected); + } catch (final IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Could not invoke expectedGetter " + expectedGetter.getName(), e); + } + var actualGetter = getActualGetter(expectedGetter, expectedClass, actualClass); + final Object actualValue; + try { + actualValue = actualGetter.invoke(actual); + } catch (final IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Could not invoke expectedGetter " + expectedGetter.getName(), e); + } + log.debug("Comparing actual={} vs expected={}: expectedGetter {}", actualValue, expectedValue, expectedGetter.getName()); + path.addLast("." + expectedGetter.getName() + "()"); + assertEquals(actualValue, expectedValue, path); + path.removeLast(); + } + } + } + + private static Method getActualGetter(final Method expectedGetter, final Class expectedClass, final Class actualClass) { + Method actualGetter; + if (expectedClass == actualClass) { + actualGetter = expectedGetter; + } else { + try { + actualGetter = actualClass.getMethod(expectedGetter.getName()); + } catch (final NoSuchMethodException e) { + try { + actualGetter = actualClass.getMethod(expectedGetter.getName().startsWith("get") + ? expectedGetter.getName().replaceFirst("get", "is") + : expectedGetter.getName().replaceFirst("is", "get")); + } catch (final NoSuchMethodException nop) { + throw new RuntimeException( + "Could not find expectedGetter " + expectedGetter.getName() + " in " + actualClass.getName(), e); + } + } + } + return actualGetter; + } + + protected enum EventType { + + CREATED("CreatedEvent"), + UPDATED("UpdatedEvent"), + DELETED("DeletedEvent"); + + private static final Map> EVENT_TYPE_MAP = new ConcurrentHashMap<>(); + + private final String suffix; + + EventType(String suffix) { + this.suffix = suffix; + } + + private Class getEventType(final Class entityType) { + return EVENT_TYPE_MAP.computeIfAbsent( + new EventTypeKey(entityType, this), + key -> { + try { + return Class.forName( + "org.eclipse.hawkbit.repository.event.remote." + (this == DELETED ? "" : "entity.") + + entityType.getSimpleName() + + suffix); + } catch (final ClassNotFoundException e) { + throw new IllegalStateException( + "Could not find event class for " + entityType.getName() + " and event type " + this, e); + } + }); + + } + + private record EventTypeKey(Class entityClass, EventType eventType) {} + } + + private record RepoMan>( + RepositoryManagement repo, + Class entityType, + Class createType, + Class updateType, + Function, ?> entityFactory) {} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/AbstractRepositoryManagementWithMetadataTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/AbstractRepositoryManagementWithMetadataTest.java new file mode 100644 index 000000000..6e913b3b6 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/AbstractRepositoryManagementWithMetadataTest.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.management; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +import lombok.SneakyThrows; +import org.eclipse.hawkbit.repository.Identifiable; +import org.eclipse.hawkbit.repository.MetadataSupport; +import org.eclipse.hawkbit.repository.QuotaManagement; +import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; +import org.eclipse.hawkbit.repository.model.BaseEntity; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("java:S119") // java:S119 - better self explainable +public abstract class AbstractRepositoryManagementWithMetadataTest, MV, MVI extends MV> + extends AbstractRepositoryManagementTest { + + protected MetadataSupport metadataSupport; // repository management, just casted to MetadataSupport + private Class metadataValueImplType; + private int maxMetaDataEntries; + + @SneakyThrows + @SuppressWarnings("unchecked") + @BeforeEach + @Override + void setup() { + super.setup(); + + synchronized (AbstractRepositoryManagementWithMetadataTest.class) { + if (metadataSupport == null) { + setMetadataSupport(); + final Type[] genericTypes = genericTypes(getClass(), AbstractRepositoryManagementWithMetadataTest.class); + metadataValueImplType = (Class) genericTypes[4]; + maxMetaDataEntries = (Integer)QuotaManagement.class.getMethod("getMaxMetaDataEntriesPer" + getEntityType().getSimpleName()).invoke(quotaManagement); + } + } + } + + @SuppressWarnings("unchecked") + protected void setMetadataSupport() { + metadataSupport = (MetadataSupport) repositoryManagement; + } + + @Test + void createMetadata() { + final String key = forType(String.class); + final MV createValue = getCreateValue(); + // since based on counter, forType returns always different values (unless boolean), so this is different value + final MV updateValue = getCreateValue(); + + // create an entity + final T instance = instance(); + // initial opt lock revision must be 1 + assertThat(instance.getOptLockRevision()).isEqualTo(1); + + // create an entity meta data entry + waitNextMillis(); // wait that last modified at is different so the opt lock revision is increased + metadataSupport.createMetadata(instance.getId(), key, createValue); + + final T createResult = repositoryManagement.get(instance.getId()); + assertThat(createResult.getOptLockRevision()).isEqualTo(2); + assertThat(createResult.getLastModifiedAt()).isPositive(); + T reloaded = repositoryManagement.get(instance.getId()); + assertThat(reloaded.getOptLockRevision()).isEqualTo(2); + assertThat(reloaded.getLastModifiedAt()).isPositive(); + + // update the entity metadata + waitNextMillis(); // wait that last modified at is different so the opt lock revision is increased + metadataSupport.createMetadata(instance.getId(), key, updateValue); + reloaded = repositoryManagement.get(instance.getId()); + assertThat(reloaded.getOptLockRevision()).isEqualTo(3); + assertThat(reloaded.getLastModifiedAt()).isPositive(); + + // verify updated metadata is with the updated value + assertEquals(metadataSupport.getMetadata(instance.getId()).get(key), updateValue); + } + + @Test + void getMetadata() { + final T instance = instance(); + for (int i = 0; i < maxMetaDataEntries; i++) { + metadataSupport.createMetadata(instance.getId(), "key" + i, getCreateValue()); + } + + final T instance2 = instance(); + for (int i = 0; i < maxMetaDataEntries - 1; i++) { + metadataSupport.createMetadata(instance2.getId(), "key" + i, getCreateValue()); + } + + assertThat(metadataSupport.getMetadata(instance.getId())).hasSize(maxMetaDataEntries); + assertThat(metadataSupport.getMetadata(instance2.getId())).hasSize(maxMetaDataEntries - 1); + } + + @Test + void deleteMetadata() { + final T instance = instance(); + for (int i = 0; i < maxMetaDataEntries; i++) { + metadataSupport.createMetadata(instance.getId(), "key" + i, getCreateValue()); + } + assertThat(metadataSupport.getMetadata(instance.getId())).hasSize(maxMetaDataEntries); + + for (int i = 0; i < 2; i++) { + metadataSupport.deleteMetadata(instance.getId(), "key" + i); + } + assertThat(metadataSupport.getMetadata(instance.getId())).hasSize(maxMetaDataEntries - 2); + } + + @Test + void failIfMetadataQuotaExceeded() { + // add metadata one by one + final Long id1 = instance().getId(); + for (int i = 0; i < maxMetaDataEntries; ++i) { + metadataSupport.createMetadata(id1, "k" + i, getCreateValue()); + } + // verify quota exceeded exception + final MV exceedValue = getCreateValue(); + assertThatExceptionOfType(AssignmentQuotaExceededException.class) + .isThrownBy(() -> metadataSupport.createMetadata(id1, "k" + maxMetaDataEntries, exceedValue)); + + // add multiple meta data entries at once + final T instance2 = instance(); + final Map metaData2 = new HashMap<>(); + for (int i = 0; i < maxMetaDataEntries + 1; ++i) { + metaData2.put("k" + i, getCreateValue()); + } + // verify quota exceeded exception + final Long id2 = instance2.getId(); + assertThatExceptionOfType(AssignmentQuotaExceededException.class).isThrownBy(() -> metadataSupport.createMetadata(id2, metaData2)); + + // add some meta data entries + final int firstHalf = Math.round((maxMetaDataEntries) / 2.f); + final Long id3 = instance().getId(); + for (int i = 0; i < firstHalf; ++i) { + metadataSupport.createMetadata(id3, "k" + i, getCreateValue()); + } + // add too many data entries + final int secondHalf = maxMetaDataEntries - firstHalf; + final Map metaData3 = new HashMap<>(); + for (int i = 0; i < secondHalf + 1; ++i) { + metaData3.put("kk" + i, getCreateValue()); + } + // verify quota is exceeded + assertThatExceptionOfType(AssignmentQuotaExceededException.class).isThrownBy(() -> metadataSupport.createMetadata(id3, metaData3)); + } + + private MVI getCreateValue() { + return forType(metadataValueImplType); + } +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementTest.java index 584002382..9aa2663a0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementTest.java @@ -16,23 +16,21 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import jakarta.validation.ConstraintViolationException; import org.assertj.core.api.Condition; import org.eclipse.hawkbit.repository.DistributionSetManagement; +import org.eclipse.hawkbit.repository.DistributionSetManagement.Create; +import org.eclipse.hawkbit.repository.DistributionSetManagement.Update; import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; +import org.eclipse.hawkbit.repository.Identifiable; import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.SoftwareModuleManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; @@ -47,16 +45,12 @@ import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetExcepti import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.LockedException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.ActionCancellationType; import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSetFilter; -import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation; import org.eclipse.hawkbit.repository.model.DistributionSetTag; -import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedVersionedEntity; import org.eclipse.hawkbit.repository.model.Rollout; @@ -65,7 +59,6 @@ import org.eclipse.hawkbit.repository.model.Statistic; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; -import org.eclipse.hawkbit.repository.test.util.WithUser; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -75,12 +68,12 @@ import org.springframework.beans.factory.annotation.Autowired; * Feature: Component Tests - Repository
* Story: DistributionSet Management */ -class DistributionSetManagementTest extends AbstractJpaIntegrationTest { +class DistributionSetManagementTest extends AbstractRepositoryManagementWithMetadataTest { private static final String TAG1_NAME = "Tag1"; @Autowired - RepositoryProperties repositoryProperties; + private RepositoryProperties repositoryProperties; /** * Verifies that management get access react as specified on calls for non existing entities by means of Optional not present. @@ -145,7 +138,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { verifyThrownExceptionBy(() -> distributionSetManagement.getMetadata(NOT_EXIST_IDL), "DistributionSet"); verifyThrownExceptionBy( - () -> distributionSetManagement.update(DistributionSetManagement.Update.builder().id(NOT_EXIST_IDL).build()), + () -> distributionSetManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), "DistributionSet"); verifyThrownExceptionBy(() -> distributionSetManagement.createMetadata(NOT_EXIST_IDL, "xxx", "xxx"), "DistributionSet"); @@ -186,7 +179,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { @Test void createDistributionSetWithImplicitType() { final DistributionSet set = distributionSetManagement - .create(DistributionSetManagement.Create.builder().type(defaultDsType()).name("newtypesoft").version("1").build()); + .create(Create.builder().type(defaultDsType()).name("newtypesoft").version("1").build()); assertThat(set.getType()) .as("Type should be equal to default type of tenant") @@ -199,8 +192,8 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { */ @Test void createDistributionSetWithDuplicateNameAndVersionFails() { - final DistributionSetManagement.Create distributionSetCreate = - DistributionSetManagement.Create.builder().type(defaultDsType()).name("newtypesoft").version("1").build(); + final Create distributionSetCreate = + Create.builder().type(defaultDsType()).name("newtypesoft").version("1").build(); distributionSetManagement.create(distributionSetCreate); assertThatExceptionOfType(EntityAlreadyExistsException.class).isThrownBy(() -> distributionSetManagement.create(distributionSetCreate)); @@ -211,9 +204,9 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { */ @Test void createMultipleDistributionSetsWithImplicitType() { - final List creates = new ArrayList<>(10); + final List creates = new ArrayList<>(10); for (int i = 0; i < 10; i++) { - creates.add(DistributionSetManagement.Create.builder().type(defaultDsType()).name("newtypesoft" + i).version("1" + i).build()); + creates.add(Create.builder().type(defaultDsType()).name("newtypesoft" + i).version("1" + i).build()); } assertThat(distributionSetManagement.create(creates)) @@ -227,52 +220,6 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { }); } - /** - * Verifies the enforcement of the metadata quota per distribution set. - */ - @Test - void createMetadataUntilQuotaIsExceeded() { - - // add meta data one by one - final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1"); - final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerDistributionSet(); - for (int i = 0; i < maxMetaData; ++i) { - insertMetadata("k" + i, "v" + i, ds1); - } - - // quota exceeded - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> insertMetadata("k" + maxMetaData, "v" + maxMetaData, ds1)); - - // add multiple meta data entries at once - final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2"); - final Map metaData2 = new HashMap<>(); - for (int i = 0; i < maxMetaData + 1; ++i) { - metaData2.put("k" + i, "v" + i); - } - // verify quota is exceeded - final Long ds2Id = ds2.getId(); - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> distributionSetManagement.createMetadata(ds2Id, metaData2)); - - // add some meta data entries - final DistributionSet ds3 = testdataFactory.createDistributionSet("ds3"); - final int firstHalf = Math.round((maxMetaData) / 2.f); - final Long ds3Id = ds3.getId(); - for (int i = 0; i < firstHalf; ++i) { - insertMetadata("k" + i, "v" + i, ds3); - } - // add too many data entries - final int secondHalf = maxMetaData - firstHalf; - final Map metaData3 = new HashMap<>(); - for (int i = 0; i < secondHalf + 1; ++i) { - metaData3.put("kk" + i, "vv" + i); - } - // verify quota is exceeded - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> distributionSetManagement.createMetadata(ds3Id, metaData3)); - } - /** * Ensures that distribution sets can assigned and unassigned to a distribution set tag. */ @@ -292,7 +239,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { .as("ds has wrong tag size") .hasSize(1)); - final DistributionSetTag findDistributionSetTag = getOrThrow(distributionSetTagManagement.find(tag.getId())); + final DistributionSetTag findDistributionSetTag = distributionSetTagManagement.get(tag.getId()); assertThat(assignedDS) .as("assigned ds has wrong size") @@ -330,7 +277,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { // assign target assignDistributionSet(ds.getId(), target.getControllerId()); - ds = getOrThrow(distributionSetManagement.getWithDetails(ds.getId())); + ds = distributionSetManagement.getWithDetails(ds.getId()).orElseThrow(); final Long dsId = ds.getId(); // not allowed as it is assigned now @@ -338,7 +285,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { .isInstanceOf(EntityReadOnlyException.class); // not allowed as it is assigned now - final Long appId = getOrThrow(findFirstModuleByType(ds, appType)).getId(); + final Long appId = findFirstModuleByType(ds, appType).map(Identifiable::getId).orElseThrow(); assertThatThrownBy(() -> distributionSetManagement.unassignSoftwareModule(dsId, appId)) .isInstanceOf(EntityReadOnlyException.class); } @@ -349,7 +296,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { @Test void updateDistributionSetUnsupportedModuleFails() { final Long setId = distributionSetManagement.create( - DistributionSetManagement.Create.builder() + Create.builder() .type(distributionSetTypeManagement.create( DistributionSetTypeManagement.Create.builder() .key("test") @@ -375,45 +322,62 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { * Legal updates of a DS, e.g. name or description and module addition, removal while still unassigned. */ @Test - void updateDistributionSet() { + void assignAndUnassignSm() { // prepare data - DistributionSet ds = testdataFactory.createDistributionSet(""); + final DistributionSet ds = testdataFactory.createDistributionSet(""); final SoftwareModule os = testdataFactory.createSoftwareModuleOs(); - // update data - // legal update of module addition + // legal update of module - addition distributionSetManagement.assignSoftwareModules(ds.getId(), Set.of(os.getId())); - ds = getOrThrow(distributionSetManagement.getWithDetails(ds.getId())); - assertThat(getOrThrow(findFirstModuleByType(ds, osType))).isEqualTo(os); + assertThat(findFirstModuleByType(distributionSetManagement.get(ds.getId()), osType)).hasValue(os); - // legal update of module removal - distributionSetManagement.unassignSoftwareModule(ds.getId(), - getOrThrow(findFirstModuleByType(ds, appType)).getId()); - ds = getOrThrow(distributionSetManagement.getWithDetails(ds.getId())); - assertThat(findFirstModuleByType(ds, appType)).isNotPresent(); - - // Update description - distributionSetManagement.update(DistributionSetManagement.Update.builder().id(ds.getId()).name("a new name") - .description("a new description").version("a new version").requiredMigrationStep(true).build()); - ds = getOrThrow(distributionSetManagement.getWithDetails(ds.getId())); - assertThat(ds.getDescription()).isEqualTo("a new description"); - assertThat(ds.getName()).isEqualTo("a new name"); - assertThat(ds.getVersion()).isEqualTo("a new version"); - assertThat(ds.isRequiredMigrationStep()).isTrue(); + // legal update of module - removal + distributionSetManagement.unassignSoftwareModule(ds.getId(), findFirstModuleByType(ds, appType).map(Identifiable::getId).orElseThrow()); + assertThat(findFirstModuleByType(distributionSetManagement.get(ds.getId()), appType)).isNotPresent(); } /** * Verifies that an exception is thrown when trying to update an invalid distribution set */ @Test - void updateInvalidDistributionSet() { + void failToUpdateInvalidDistributionSet() { final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet(); - final DistributionSetManagement.Update update = - DistributionSetManagement.Update.builder().id(distributionSet.getId()).name("new_name").build(); + final Update update = + Update.builder().id(distributionSet.getId()).name("new_name").build(); assertThatExceptionOfType(InvalidDistributionSetException.class) .as("Invalid distributionSet should throw an exception").isThrownBy(() -> distributionSetManagement.update(update)); } + @Test + void failToModifyMetadataForInvalidDistributionSet() { + final String key = forType(String.class); + + final Long instanceId = instance().getId(); + distributionSetManagement.createMetadata(instanceId, Map.of(key, forType(String.class))); + + distributionSetManagement.invalidate(distributionSetManagement.get(instanceId)); + + // assert that no new metadata can be created + final String key2 = forType(String.class); + final String createValue = forType(String.class); + assertThatExceptionOfType(InvalidDistributionSetException.class) + .as("Create metadata of an invalid entity should throw an exception") + .isThrownBy(() -> distributionSetManagement.createMetadata(instanceId, key2, createValue)); + final Map createMetadata = Map.of(key2, createValue); + assertThatExceptionOfType(InvalidDistributionSetException.class) + .as("Create metadata of an invalid entity should throw an exception") + .isThrownBy(() -> distributionSetManagement.createMetadata(instanceId, createMetadata)); + + // assert that an existing metadata can not be updated + final String updateValue = forType(String.class); + assertThatExceptionOfType(InvalidDistributionSetException.class) + .as("Update metadata of an invalid entity should throw an exception") + .isThrownBy(() -> distributionSetManagement.createMetadata(instanceId, key, updateValue)); + final Map updateMetadata = Map.of(key, updateValue); + assertThatExceptionOfType(InvalidDistributionSetException.class) + .as("Update metadata of an invalid entity should throw an exception") + .isThrownBy(() -> distributionSetManagement.createMetadata(instanceId, updateMetadata)); + } /** * Verifies the enforcement of the software module quota per distribution set. @@ -489,111 +453,6 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { .unassignSoftwareModule(distributionSetId, softwareModuleId)); } - /** - * Checks that metadata for a distribution set can be updated. - */ - @Test - @WithUser(allSpPermissions = true) - void createMetadata() { - final String knownKey = "myKnownKey"; - final String knownValue = "myKnownValue"; - final String knownUpdateValue = "myNewUpdatedValue"; - - // create a DS - final DistributionSet ds = testdataFactory.createDistributionSet("testDs"); - // initial opt lock revision must be zero - assertThat(ds.getOptLockRevision()).isEqualTo(1); - - waitNextMillis(); - // create an DS meta data entry - insertMetadata(knownKey, knownValue, ds); - - final DistributionSet changedLockRevisionDS = getOrThrow(distributionSetManagement.find(ds.getId())); - assertThat(changedLockRevisionDS.getOptLockRevision()).isEqualTo(2); - - waitNextMillis(); - // update the DS metadata - distributionSetManagement.createMetadata(ds.getId(), knownKey, knownUpdateValue); - // we are updating the sw metadata so also modifying the base software - // module so opt lock revision must be three - final DistributionSet reloadedDS = getOrThrow(distributionSetManagement.find(ds.getId())); - assertThat(reloadedDS.getOptLockRevision()).isEqualTo(3); - assertThat(reloadedDS.getLastModifiedAt()).isPositive(); - - // verify updated meta data is the updated value - assertThat(distributionSetManagement.getMetadata(ds.getId())).containsEntry(knownKey, knownUpdateValue); - } - - /** - * searches for distribution sets based on the various filter options, e.g. name, version, desc., tags. - */ - @Test - void searchDistributionSetsOnFilters() { - DistributionSetTag dsTagA = distributionSetTagManagement - .create(DistributionSetTagManagement.Create.builder().name("DistributionSetTag-A").build()); - final DistributionSetTag dsTagB = distributionSetTagManagement - .create(DistributionSetTagManagement.Create.builder().name("DistributionSetTag-B").build()); - final DistributionSetTag dsTagC = distributionSetTagManagement - .create(DistributionSetTagManagement.Create.builder().name("DistributionSetTag-C").build()); - distributionSetTagManagement.create(DistributionSetTagManagement.Create.builder().name("DistributionSetTag-D").build()); - - List dsGroup1 = testdataFactory.createDistributionSets("", 5); - final String dsGroup2Prefix = "test"; - List dsGroup2 = testdataFactory.createDistributionSets(dsGroup2Prefix, 5); - DistributionSet dsDeleted = testdataFactory.createDistributionSet("testDeleted"); - final DistributionSet dsInComplete = distributionSetManagement.create( - DistributionSetManagement.Create.builder() - .name("notcomplete").version("1").type(standardDsType).build()); - - DistributionSetType newType = distributionSetTypeManagement - .create(DistributionSetTypeManagement.Create.builder().key("foo").name("bar").description("test").build()); - - distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(newType.getId(), - singletonList(osType.getId())); - newType = distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(newType.getId(), - Arrays.asList(appType.getId(), runtimeType.getId())); - - final DistributionSet dsNewType = distributionSetManagement.create( - DistributionSetManagement.Create.builder() - .type(newType) - .name("newtype").version("1") - .modules(new HashSet<>(dsDeleted.getModules())) - .build()); - - assignDistributionSet(dsDeleted, testdataFactory.createTargets(5)); - distributionSetManagement.delete(dsDeleted.getId()); - dsDeleted = getOrThrow(distributionSetManagement.find(dsDeleted.getId())); - - dsGroup1 = assignTag(dsGroup1, dsTagA); - dsTagA = getOrThrow(distributionSetTagRepository.findById(dsTagA.getId())); - dsGroup1 = assignTag(dsGroup1, dsTagB); - dsTagA = getOrThrow(distributionSetTagRepository.findById(dsTagA.getId())); - dsGroup2 = assignTag(dsGroup2, dsTagA); - dsTagA = getOrThrow(distributionSetTagRepository.findById(dsTagA.getId())); - - final List allDistributionSets = Stream - .of(dsGroup1, dsGroup2, Arrays.asList(dsDeleted, dsInComplete, dsNewType)).flatMap(Collection::stream) - .toList(); - final List dsGroup1WithGroup2 = Stream.of(dsGroup1, dsGroup2).flatMap(Collection::stream) - .toList(); - final int sizeOfAllDistributionSets = allDistributionSets.size(); - - // check setup - assertThat(distributionSetRepository.findAll()).hasSize(sizeOfAllDistributionSets); - - validateFindAll(allDistributionSets); - validateDeleted(dsDeleted, sizeOfAllDistributionSets - 1); - validateCompleted(dsInComplete, sizeOfAllDistributionSets - 1); - validateType(newType, dsNewType, sizeOfAllDistributionSets - 1); - validateSearchText(allDistributionSets, dsGroup2Prefix); - validateTags(dsTagA, dsTagB, dsTagC, dsGroup1WithGroup2, dsGroup1); - validateDeletedAndCompleted(dsGroup1WithGroup2, dsNewType, dsDeleted); - validateDeletedAndCompletedAndType(dsGroup1WithGroup2, dsDeleted, newType, dsNewType); - validateDeletedAndCompletedAndTypeAndSearchText(dsGroup2, newType, dsGroup2Prefix); - validateDeletedAndCompletedAndTypeAndSearchText(dsGroup1WithGroup2, dsDeleted, dsInComplete, dsNewType, newType, ":1"); - validateDeletedAndCompletedAndTypeAndSearchTextAndTag(dsGroup2, dsTagA, dsGroup2Prefix); - } - /** * Locks a DS. */ @@ -688,10 +547,9 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { /** * Test implicit locks for a DS and skip tags. */ - @SuppressWarnings("rawtypes") @Test void shouldLockImplicitlyForDistributionSet() { - final JpaDistributionSetManagement distributionSetManagement = (JpaDistributionSetManagement) (DistributionSetManagement) this.distributionSetManagement; + final JpaDistributionSetManagement distributionSetManagement = (JpaDistributionSetManagement) this.distributionSetManagement; final DistributionSet distributionSet = testdataFactory.createDistributionSet("ds-non-skip"); // assert that implicit lock is applicable for non skip tags assertThat(distributionSetManagement.shouldLockImplicitly(distributionSet)).isTrue(); @@ -703,7 +561,8 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { // remove same in case-insensitive terms tags // in of case-insensitive db's it will end up as same names and constraint violation (?) .distinct() - .map(skipTag -> (DistributionSetTagManagement.Create)DistributionSetTagManagement.Create.builder().name(skipTag).build()) + .map(skipTag -> (DistributionSetTagManagement.Create) DistributionSetTagManagement.Create.builder().name(skipTag) + .build()) .toList()); // assert that implicit lock locks for every skip tag skipTags.forEach(skipTag -> { @@ -729,21 +588,6 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { .isFalse(); } - /** - * Deletes a DS that is no in use. Expected behaviour is a hard delete on the database. - */ - @Test - void deleteUnassignedDistributionSet() { - final DistributionSet ds1 = testdataFactory.createDistributionSet("ds-1"); - testdataFactory.createDistributionSet("ds-2"); - - // delete a ds - assertThat(distributionSetRepository.findAll()).hasSize(2); - distributionSetManagement.delete(ds1.getId()); - // not assigned so not marked as deleted but fully deleted - assertThat(distributionSetRepository.findAll()).hasSize(1); - } - /** * Deletes an invalid distribution set */ @@ -766,27 +610,6 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { assertThat(distributionSetRepository.findById(set.getId())).isEmpty(); } - /** - * Queries and loads the metadata related to a given distribution set. - */ - @Test - void getMetadata() { - // create a DS - final DistributionSet ds1 = testdataFactory.createDistributionSet("testDs1"); - final DistributionSet ds2 = testdataFactory.createDistributionSet("testDs2"); - - for (int index = 0; index < quotaManagement.getMaxMetaDataEntriesPerDistributionSet(); index++) { - insertMetadata("key" + index, "value" + index, ds1); - } - - for (int index = 0; index <= quotaManagement.getMaxMetaDataEntriesPerDistributionSet() - 2; index++) { - insertMetadata("key" + index, "value" + index, ds2); - } - - assertThat(distributionSetManagement.getMetadata(ds1.getId())).hasSize(quotaManagement.getMaxMetaDataEntriesPerDistributionSet()); - assertThat(distributionSetManagement.getMetadata(ds2.getId())).hasSize(quotaManagement.getMaxMetaDataEntriesPerDistributionSet() - 1); - } - /** * Deletes a DS that is in use by either target assignment or rollout. Expected behaviour is a soft delete on the database, i.e. only marked as * deleted, kept as reference but unavailable for future use.. @@ -862,33 +685,6 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { .isThrownBy(() -> distributionSetManagement.getValidAndComplete(distributionSetId)); } - /** - * Verify that an exception is thrown when trying to create or update metadata for an invalid distribution set. - */ - @Test - void createMetadataForInvalidDistributionSet() { - final String knownKey1 = "myKnownKey1"; - final String knownKey2 = "myKnownKey2"; - final String knownValue = "myKnownValue"; - final String knownUpdateValue = "knownUpdateValue"; - - final Long dsId = testdataFactory.createDistributionSet().getId(); - distributionSetManagement.createMetadata(dsId, Map.of(knownKey1, knownValue)); - - distributionSetInvalidationManagement.invalidateDistributionSet( - new DistributionSetInvalidation(singletonList(dsId), ActionCancellationType.NONE)); - - // assert that no new metadata can be created - assertThatExceptionOfType(InvalidDistributionSetException.class) - .as("Invalid distributionSet should throw an exception") - .isThrownBy(() -> distributionSetManagement.createMetadata(dsId, Map.of(knownKey2, knownValue))); - - // assert that an existing metadata can not be updated - assertThatExceptionOfType(InvalidDistributionSetException.class) - .as("Invalid distributionSet should throw an exception") - .isThrownBy(() -> distributionSetManagement.createMetadata(dsId, knownKey1, knownUpdateValue)); - } - /** * Get the Rollouts count by status statistics for a specific Distribution Set */ @@ -964,286 +760,86 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { } private void createAndUpdateDistributionSetWithInvalidDescription(final DistributionSet set) { - final DistributionSetManagement.Create distributionSetCreate = - DistributionSetManagement.Create.builder().name("a").version("a").description(randomString(513)).build(); + final Create distributionSetCreate = + Create.builder().name("a").version("a").description(randomString(513)).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with too long description should not be created") .isThrownBy(() -> distributionSetManagement.create(distributionSetCreate)); - final DistributionSetManagement.Create distributionSetCreate2 = - DistributionSetManagement.Create.builder().name("a").version("a").description(INVALID_TEXT_HTML).build(); + final Create distributionSetCreate2 = + Create.builder().name("a").version("a").description(INVALID_TEXT_HTML).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with invalid description should not be created") .isThrownBy(() -> distributionSetManagement.create(distributionSetCreate2)); - final DistributionSetManagement.Update distributionSetUpdate = - DistributionSetManagement.Update.builder().id(set.getId()).description(randomString(513)).build(); + final Update distributionSetUpdate = + Update.builder().id(set.getId()).description(randomString(513)).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with too long description should not be updated") .isThrownBy(() -> distributionSetManagement.update(distributionSetUpdate)); - final DistributionSetManagement.Update distributionSetUpdate2 = - DistributionSetManagement.Update.builder().id(set.getId()).description(INVALID_TEXT_HTML).build(); + final Update distributionSetUpdate2 = + Update.builder().id(set.getId()).description(INVALID_TEXT_HTML).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with invalid characters should not be updated") .isThrownBy(() -> distributionSetManagement.update(distributionSetUpdate2)); } private void createAndUpdateDistributionSetWithInvalidName(final DistributionSet set) { - final DistributionSetManagement.Create distributionSetCreate = DistributionSetManagement.Create.builder() + final Create distributionSetCreate = Create.builder() .version("a").name(randomString(NamedEntity.NAME_MAX_SIZE + 1)).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with too long name should not be created") .isThrownBy(() -> distributionSetManagement.create(distributionSetCreate)); - final DistributionSetManagement.Create distributionSetCreate2 = DistributionSetManagement.Create.builder().version("a").name("").build(); + final Create distributionSetCreate2 = Create.builder().version("a").name("").build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with too short name should not be created") .isThrownBy(() -> distributionSetManagement.create(distributionSetCreate2)); - final DistributionSetManagement.Create distributionSetCreate3 = DistributionSetManagement.Create.builder().version("a").name(INVALID_TEXT_HTML).build(); + final Create distributionSetCreate3 = Create.builder().version("a").name(INVALID_TEXT_HTML).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with invalid characters in name should not be created") .isThrownBy(() -> distributionSetManagement.create(distributionSetCreate3)); - final DistributionSetManagement.Update distributionSetUpdate = DistributionSetManagement.Update.builder().id(set.getId()) + final Update distributionSetUpdate = Update.builder().id(set.getId()) .name(randomString(NamedEntity.NAME_MAX_SIZE + 1)).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with too long name should not be updated") .isThrownBy(() -> distributionSetManagement.update(distributionSetUpdate)); - final DistributionSetManagement.Update distributionSetUpdate2 = DistributionSetManagement.Update.builder().id(set.getId()).name(INVALID_TEXT_HTML).build(); + final Update distributionSetUpdate2 = Update.builder().id(set.getId()).name(INVALID_TEXT_HTML).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with invalid characters should not be updated") .isThrownBy(() -> distributionSetManagement.update(distributionSetUpdate2)); - final DistributionSetManagement.Update distributionSetUpdate3 = DistributionSetManagement.Update.builder().id(set.getId()).name("").build(); + final Update distributionSetUpdate3 = Update.builder().id(set.getId()).name("").build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with too short name should not be updated") .isThrownBy(() -> distributionSetManagement.update(distributionSetUpdate3)); } private void createAndUpdateDistributionSetWithInvalidVersion(final DistributionSet set) { - final DistributionSetManagement.Create distributionSetCreate = DistributionSetManagement.Create.builder() + final Create distributionSetCreate = Create.builder() .name("a").version(randomString(NamedVersionedEntity.VERSION_MAX_SIZE + 1)).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with too long version should not be created") .isThrownBy(() -> distributionSetManagement.create(distributionSetCreate)); - final DistributionSetManagement.Create distributionSetCreate2 = DistributionSetManagement.Create.builder().name("a").version("").build(); + final Create distributionSetCreate2 = Create.builder().name("a").version("").build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with too short version should not be created") .isThrownBy(() -> distributionSetManagement.create(distributionSetCreate2)); - final DistributionSetManagement.Update distributionSetUpdate = DistributionSetManagement.Update.builder().id(set.getId()) + final Update distributionSetUpdate = Update.builder().id(set.getId()) .version(randomString(NamedVersionedEntity.VERSION_MAX_SIZE + 1)).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with too long version should not be updated") .isThrownBy(() -> distributionSetManagement.update(distributionSetUpdate)); - final DistributionSetManagement.Update distributionSetUpdate2 = DistributionSetManagement.Update.builder().id(set.getId()).version("").build(); + final Update distributionSetUpdate2 = Update.builder().id(set.getId()).version("").build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("entity with too short version should not be updated") .isThrownBy(() -> distributionSetManagement.update(distributionSetUpdate2)); } - - private void validateFindAll(final List expectedDistributionSets) { - - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder(), expectedDistributionSets); - } - - private void validateDeleted(final DistributionSet deletedDistributionSet, final int notDeletedSize) { - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().isDeleted(Boolean.TRUE), - singletonList(deletedDistributionSet)); - - assertThatFilterHasSizeAndDoesNotContainDistributionSet( - DistributionSetFilter.builder().isDeleted(Boolean.FALSE), notDeletedSize, deletedDistributionSet); - } - - private void validateCompleted(final DistributionSet dsIncomplete, final int completedSize) { - assertThatFilterHasSizeAndDoesNotContainDistributionSet( - DistributionSetFilter.builder().isComplete(Boolean.TRUE), completedSize, dsIncomplete); - - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().isComplete(Boolean.FALSE), singletonList(dsIncomplete)); - } - - private void validateType(final DistributionSetType newType, final DistributionSet dsNewType, final int standardDsTypeSize) { - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().typeId(newType.getId()), - singletonList(dsNewType)); - assertThatFilterHasSizeAndDoesNotContainDistributionSet( - DistributionSetFilter.builder().typeId(standardDsType.getId()), standardDsTypeSize, dsNewType); - } - - private void validateSearchText(final List allDistributionSets, final String dsNamePrefix) { - final List withTestNamePrefix = allDistributionSets.stream() - .filter(ds -> ds.getName().startsWith(dsNamePrefix)).toList(); - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().searchText(dsNamePrefix), - withTestNamePrefix); - - final List withTestNameExact = withTestNamePrefix.stream() - .filter(ds -> ds.getName().equals(dsNamePrefix)).toList(); - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().searchText(dsNamePrefix + ":"), withTestNameExact); - - final List withTestNameExactAndVersionPrefix = withTestNameExact.stream() - .filter(ds -> ds.getVersion().startsWith("1")).toList(); - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().searchText(dsNamePrefix + ":1"), - withTestNameExactAndVersionPrefix); - - final List dsWithExactNameAndVersion = withTestNameExactAndVersionPrefix.stream() - .filter(ds -> ds.getVersion().equals("1.0.0")).toList(); - assertThat(dsWithExactNameAndVersion).hasSize(1); - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().searchText(dsNamePrefix + ":1.0.0"), dsWithExactNameAndVersion); - - final List withVersionPrefix = allDistributionSets.stream() - .filter(ds -> ds.getVersion().startsWith("1.0.")).toList(); - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().searchText(":1.0."), - withVersionPrefix); - - final List withVersionExact = withVersionPrefix.stream() - .filter(ds -> ds.getVersion().equals("1.0.0")).toList(); - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().searchText(":1.0.0"), withVersionExact); - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().searchText(":"), allDistributionSets); - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().searchText(" : "), allDistributionSets); - } - - private void validateTags(final DistributionSetTag dsTagA, final DistributionSetTag dsTagB, - final DistributionSetTag dsTagC, final List dsWithTagA, - final List dsWithTagB) { - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().tagNames(singletonList(dsTagA.getName())), dsWithTagA); - - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().tagNames(singletonList(dsTagB.getName())), dsWithTagB); - - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().tagNames(Arrays.asList(dsTagA.getName(), dsTagB.getName())), - dsWithTagA); - - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().tagNames(Arrays.asList(dsTagC.getName(), dsTagB.getName())), - dsWithTagB); - - assertThatFilterDoesNotContainAnyDistributionSet( - DistributionSetFilter.builder().tagNames(singletonList(dsTagC.getName()))); - } - - private void validateDeletedAndCompleted(final List completedStandardType, - final DistributionSet dsNewType, final DistributionSet dsDeleted) { - - final List completedNotDeleted = new ArrayList<>(completedStandardType); - completedNotDeleted.add(dsNewType); - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().isComplete(Boolean.TRUE).isDeleted(Boolean.FALSE), - completedNotDeleted); - - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().isComplete(Boolean.TRUE).isDeleted(Boolean.TRUE), - singletonList(dsDeleted)); - - assertThatFilterDoesNotContainAnyDistributionSet( - DistributionSetFilter.builder().isComplete(Boolean.FALSE).isDeleted(Boolean.TRUE)); - } - - private void validateDeletedAndCompletedAndType(final List deletedAndCompletedAndStandardType, - final DistributionSet dsDeleted, final DistributionSetType newType, final DistributionSet dsNewType) { - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().isDeleted(Boolean.FALSE) - .isComplete(Boolean.TRUE).typeId(standardDsType.getId()), deletedAndCompletedAndStandardType); - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().isComplete(Boolean.TRUE) - .typeId(standardDsType.getId()).isDeleted(Boolean.TRUE), singletonList(dsDeleted)); - assertThatFilterDoesNotContainAnyDistributionSet(DistributionSetFilter.builder().isDeleted(Boolean.TRUE) - .isComplete(Boolean.FALSE).typeId(standardDsType.getId())); - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().isComplete(Boolean.TRUE).typeId(newType.getId()), - singletonList(dsNewType)); - } - - private void validateDeletedAndCompletedAndTypeAndSearchText( - final List completedAndStandardTypeAndSearchText, final DistributionSetType newType, final String text) { - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().isDeleted(Boolean.FALSE) - .isComplete(Boolean.TRUE).typeId(standardDsType.getId()).searchText(text), - completedAndStandardTypeAndSearchText); - - assertThatFilterDoesNotContainAnyDistributionSet(DistributionSetFilter.builder().isComplete(Boolean.TRUE) - .isDeleted(Boolean.TRUE).typeId(standardDsType.getId()).searchText(text + ":")); - - assertThatFilterDoesNotContainAnyDistributionSet( - DistributionSetFilter.builder().typeId(standardDsType.getId()).searchText(text) - .isComplete(Boolean.FALSE).isDeleted(Boolean.FALSE)); - - assertThatFilterDoesNotContainAnyDistributionSet(DistributionSetFilter.builder().typeId(newType.getId()) - .searchText(text).isComplete(Boolean.TRUE).isDeleted(Boolean.FALSE)); - } - - private void validateDeletedAndCompletedAndTypeAndSearchText( - final List completedAndNotDeletedStandardTypeAndFilterString, - final DistributionSet dsDeleted, final DistributionSet dsInComplete, final DistributionSet dsNewType, - final DistributionSetType newType, final String filterString) { - - final List completedAndStandardTypeAndFilterString = new ArrayList<>( - completedAndNotDeletedStandardTypeAndFilterString); - completedAndStandardTypeAndFilterString.add(dsDeleted); - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().isComplete(Boolean.TRUE) - .typeId(standardDsType.getId()).searchText(filterString), - completedAndStandardTypeAndFilterString); - - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().isComplete(Boolean.TRUE).isDeleted(Boolean.FALSE) - .typeId(standardDsType.getId()).searchText(filterString), - completedAndNotDeletedStandardTypeAndFilterString); - - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().isComplete(Boolean.TRUE) - .isDeleted(Boolean.TRUE).typeId(standardDsType.getId()).searchText(filterString), - singletonList(dsDeleted)); - - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().typeId(standardDsType.getId()).searchText(filterString) - .isComplete(Boolean.FALSE).isDeleted(Boolean.FALSE), - singletonList(dsInComplete)); - - assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().typeId(newType.getId()) - .searchText(filterString).isComplete(Boolean.TRUE).isDeleted(Boolean.FALSE), - singletonList(dsNewType)); - } - - private void validateDeletedAndCompletedAndTypeAndSearchTextAndTag( - final List completedAndStandartTypeAndSearchTextAndTagA, final DistributionSetTag dsTagA, final String text) { - assertThatFilterContainsOnlyGivenDistributionSets( - DistributionSetFilter.builder().isComplete(Boolean.TRUE).typeId(standardDsType.getId()) - .searchText(text).tagNames(singletonList(dsTagA.getName())), - completedAndStandartTypeAndSearchTextAndTagA); - - assertThatFilterDoesNotContainAnyDistributionSet(DistributionSetFilter.builder() - .typeId(standardDsType.getId()).searchText(text).tagNames(singletonList(dsTagA.getName())) - .isComplete(Boolean.FALSE).isDeleted(Boolean.FALSE)); - } - - private void insertMetadata(final String knownKey, final String knownValue, final DistributionSet distributionSet) { - distributionSetManagement.createMetadata(distributionSet.getId(), Map.of(knownKey, knownValue)); - assertThat(distributionSetManagement.getMetadata(distributionSet.getId()).get(knownKey)).isEqualTo(knownValue); - } - - private void assertThatFilterContainsOnlyGivenDistributionSets(final DistributionSetFilterBuilder filterBuilder, - final List distributionSets) { - final int expectedDsSize = distributionSets.size(); - assertThat(findDsByDistributionSetFilter(filterBuilder.build(), PAGE).getContent()) - .hasSize(expectedDsSize).containsOnly(distributionSets.toArray(new JpaDistributionSet[expectedDsSize])); - } - - private void assertThatFilterDoesNotContainAnyDistributionSet(final DistributionSetFilterBuilder filterBuilder) { - assertThat(findDsByDistributionSetFilter(filterBuilder.build(), PAGE).getContent()).isEmpty(); - } - - private void assertThatFilterHasSizeAndDoesNotContainDistributionSet( - final DistributionSetFilterBuilder filterBuilder, final int size, final DistributionSet ds) { - assertThat((List)findDsByDistributionSetFilter(filterBuilder.build(), PAGE).getContent()).hasSize(size).doesNotContain(ds); - } - - // can be removed with java-11 - private T getOrThrow(final Optional opt) { - return opt.orElseThrow(NoSuchElementException::new); - } -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTagManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTagManagementTest.java index db30e4d60..27feb3c06 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTagManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTagManagementTest.java @@ -14,27 +14,20 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Stream; +import org.assertj.core.api.Assertions; import org.eclipse.hawkbit.repository.DistributionSetTagManagement; -import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTagUpdatedEvent; -import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; -import org.eclipse.hawkbit.repository.event.remote.entity.TargetTagUpdatedEvent; +import org.eclipse.hawkbit.repository.DistributionSetTagManagement.Create; +import org.eclipse.hawkbit.repository.DistributionSetTagManagement.Update; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSetFilter; import org.eclipse.hawkbit.repository.model.DistributionSetTag; -import org.eclipse.hawkbit.repository.model.Tag; -import org.eclipse.hawkbit.repository.test.matcher.Expect; -import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.junit.jupiter.api.Test; -import org.springframework.data.domain.Pageable; /** * {@link DistributionSetTagManagement} tests. @@ -42,78 +35,40 @@ import org.springframework.data.domain.Pageable; * Feature: Component Tests - Repository
* Story: DistributionSet Tag Management */ -class DistributionSetTagManagementTest extends AbstractJpaIntegrationTest { +class DistributionSetTagManagementTest extends AbstractRepositoryManagementTest { + + private static final String TAG_1 = "tag1"; + private static final String TAG_DESCRIPTION_1 = "tag_description_1"; - /** - * Verifies that management get access reacts as specified on calls for non existing entities by means of Optional not present. - */ @Test - @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) - void nonExistingEntityAccessReturnsNotPresent() { - assertThat(distributionSetTagManagement.find(NOT_EXIST_IDL)).isNotPresent(); - } + void createAssignAndDeleteTags() { + final Collection dsAs = testdataFactory.createDistributionSets("DS-A", 1); + final Collection dsBs = testdataFactory.createDistributionSets("DS-B", 2); + final Collection dsCs = testdataFactory.createDistributionSets("DS-C", 4); + final Collection dsABs = testdataFactory.createDistributionSets("DS-AB", 8); + final Collection dsACs = testdataFactory.createDistributionSets("DS-AC", 16); + final Collection dsBCs = testdataFactory.createDistributionSets("DS-BC", 32); + final Collection dsABCs = testdataFactory.createDistributionSets("DS-ABC", 64); - /** - * Verifies that management queries react as specified on calls for non existing entities by means of throwing - * EntityNotFoundException. - */ - @Test - @ExpectEvents({ - @Expect(type = DistributionSetTagUpdatedEvent.class, count = 0), - @Expect(type = TargetTagUpdatedEvent.class, count = 0) }) - void entityQueriesReferringToNotExistingEntitiesThrowsException() { - verifyThrownExceptionBy(() -> distributionSetTagManagement.delete(NOT_EXIST_IDL), "DistributionSetTag"); - verifyThrownExceptionBy(() -> distributionSetTagManagement.update( - DistributionSetTagManagement.Update.builder().id(NOT_EXIST_IDL).build()), "DistributionSetTag"); - } - - /** - * Full DS tag lifecycle tested. Create tags, assign them to sets and delete the tags. - */ - @Test - void createAndAssignAndDeleteDistributionSetTags() { - final Collection dsAs = testdataFactory.createDistributionSets("DS-A", 20); - final Collection dsBs = testdataFactory.createDistributionSets("DS-B", 10); - final Collection dsCs = testdataFactory.createDistributionSets("DS-C", 25); - final Collection dsABs = testdataFactory.createDistributionSets("DS-AB", 5); - final Collection dsACs = testdataFactory.createDistributionSets("DS-AC", 11); - final Collection dsBCs = testdataFactory.createDistributionSets("DS-BC", 13); - final Collection dsABCs = testdataFactory.createDistributionSets("DS-ABC", 9); - - final DistributionSetTag tagA = distributionSetTagManagement.create(DistributionSetTagManagement.Create.builder().name("A").build()); - final DistributionSetTag tagB = distributionSetTagManagement.create(DistributionSetTagManagement.Create.builder().name("B").build()); - final DistributionSetTag tagC = distributionSetTagManagement.create(DistributionSetTagManagement.Create.builder().name("C").build()); - final DistributionSetTag tagX = distributionSetTagManagement.create(DistributionSetTagManagement.Create.builder().name("X").build()); - final DistributionSetTag tagY = distributionSetTagManagement.create(DistributionSetTagManagement.Create.builder().name("Y").build()); + final DistributionSetTag tagA = distributionSetTagManagement.create(Create.builder().name("A").build()); + final DistributionSetTag tagB = distributionSetTagManagement.create(Create.builder().name("B").build()); + final DistributionSetTag tagC = distributionSetTagManagement.create(Create.builder().name("C").build()); + final DistributionSetTag tagX = distributionSetTagManagement.create(Create.builder().name("X").build()); + final DistributionSetTag tagY = distributionSetTagManagement.create(Create.builder().name("Y").build()); assignTag(dsAs, tagA); assignTag(dsBs, tagB); assignTag(dsCs, tagC); - - assignTag(dsABs, distributionSetTagManagement.find(tagA.getId()).orElseThrow()); - assignTag(dsABs, distributionSetTagManagement.find(tagB.getId()).orElseThrow()); - - assignTag(dsACs, distributionSetTagManagement.find(tagA.getId()).orElseThrow()); - assignTag(dsACs, distributionSetTagManagement.find(tagC.getId()).orElseThrow()); - - assignTag(dsBCs, distributionSetTagManagement.find(tagB.getId()).orElseThrow()); - assignTag(dsBCs, distributionSetTagManagement.find(tagC.getId()).orElseThrow()); - - assignTag(dsABCs, distributionSetTagManagement.find(tagA.getId()).orElseThrow()); - assignTag(dsABCs, distributionSetTagManagement.find(tagB.getId()).orElseThrow()); - assignTag(dsABCs, distributionSetTagManagement.find(tagC.getId()).orElseThrow()); + assignTags(dsABs, tagA, tagB); + assignTags(dsACs, tagA, tagC); + assignTags(dsBCs, tagB, tagC); + assignTags(dsABCs, tagA, tagB, tagC); // search for not deleted - final DistributionSetFilter.DistributionSetFilterBuilder distributionSetFilterBuilder = getDistributionSetFilterBuilder() - .isComplete(true); - verifyExpectedFilteredDistributionSets(distributionSetFilterBuilder.tagNames(Arrays.asList(tagA.getName())), - Stream.of(dsAs, dsABs, dsACs, dsABCs)); - verifyExpectedFilteredDistributionSets(distributionSetFilterBuilder.tagNames(Arrays.asList(tagB.getName())), - Stream.of(dsBs, dsABs, dsBCs, dsABCs)); - verifyExpectedFilteredDistributionSets(distributionSetFilterBuilder.tagNames(Arrays.asList(tagC.getName())), - Stream.of(dsCs, dsACs, dsBCs, dsABCs)); - verifyExpectedFilteredDistributionSets(distributionSetFilterBuilder.tagNames(Arrays.asList(tagX.getName())), - Stream.empty()); + assertTaggedWithATagAreAsExpected(tagA, dsAs, dsABs, dsACs, dsABCs); + assertTaggedWithATagAreAsExpected(tagB, dsBs, dsABs, dsBCs, dsABCs); + assertTaggedWithATagAreAsExpected(tagC, dsCs, dsACs, dsBCs, dsABCs); + assertTaggedWithATagAreAsExpected(tagX); assertThat(distributionSetTagRepository.findAll()).hasSize(5); distributionSetTagManagement.delete(tagY.getId()); @@ -123,64 +78,58 @@ class DistributionSetTagManagementTest extends AbstractJpaIntegrationTest { distributionSetTagManagement.delete(tagB.getId()); assertThat(distributionSetTagRepository.findAll()).hasSize(2); - verifyExpectedFilteredDistributionSets(distributionSetFilterBuilder.tagNames(Arrays.asList(tagA.getName())), - Stream.of(dsAs, dsABs, dsACs, dsABCs)); - verifyExpectedFilteredDistributionSets(distributionSetFilterBuilder.tagNames(Arrays.asList(tagB.getName())), - Stream.empty()); - verifyExpectedFilteredDistributionSets(distributionSetFilterBuilder.tagNames(Arrays.asList(tagC.getName())), - Stream.of(dsCs, dsACs, dsBCs, dsABCs)); + assertTaggedWithATagAreAsExpected(tagA, dsAs, dsABs, dsACs, dsABCs); + assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> assertTaggedWithATagAreAsExpected(tagB)); + assertTaggedWithATagAreAsExpected(tagC, dsCs, dsACs, dsBCs, dsABCs); } /** * Verifies assign/unassign. */ @Test - void assignAndUnassignDistributionSetTags() { - final Collection groupA = testdataFactory.createDistributionSets(20); - final Collection groupB = testdataFactory.createDistributionSets("unassigned", 20); + void assignAndUnassign() { + final DistributionSetTag tag = distributionSetTagManagement.create(Create.builder().name(TAG_1).description(TAG_DESCRIPTION_1).build()); - final DistributionSetTag tag = distributionSetTagManagement - .create(DistributionSetTagManagement.Create.builder().name("tag1").description("tagdesc1").build()); + final Collection groupA = testdataFactory.createDistributionSets("A_", 5); + final Collection groupB = testdataFactory.createDistributionSets("B_", 5); + final Collection groupAB = concat(groupA, groupB); - // toggle A only -> A is now assigned + // set A only -> A is now assigned List result = assignTag(groupA, tag); - assertThat(result) - .hasSize(20) - .containsAll((Collection) distributionSetManagement.get(groupA.stream().map(DistributionSet::getId).toList())); + Assertions. assertThat(result) + .hasSameSizeAs(groupA) + .containsAll(distributionSetManagement.get(groupA.stream().map(DistributionSet::getId).toList())); assertThat( - distributionSetManagement.findByTag(tag.getId(), Pageable.unpaged()).getContent().stream() + distributionSetManagement.findByTag(tag.getId(), UNPAGED).getContent().stream() .map(DistributionSet::getId) .sorted() .toList()) .isEqualTo(groupA.stream().map(DistributionSet::getId).sorted().toList()); - final Collection groupAB = concat(groupA, groupB); - // toggle A+B -> A is still assigned and B is assigned as well + // set to A+B -> A is still assigned and B is assigned as well result = assignTag(groupAB, tag); - assertThat((List) result) - .hasSize(40) + Assertions. assertThat(result) + .hasSameSizeAs(groupAB) .containsAll(distributionSetManagement.get(groupAB.stream().map(DistributionSet::getId).toList())); assertThat( - distributionSetManagement.findByTag( - tag.getId(), Pageable.unpaged()).getContent().stream().map(DistributionSet::getId).sorted().toList()) + distributionSetManagement.findByTag(tag.getId(), UNPAGED).getContent().stream() + .map(DistributionSet::getId) + .sorted() + .toList()) .isEqualTo(groupAB.stream().map(DistributionSet::getId).sorted().toList()); // toggle A+B -> both unassigned - result = unassignTag(concat(groupA, groupB), tag); - assertThat(result) - .hasSize(40) - .containsAll((List) distributionSetManagement.get(concat(groupB, groupA).stream().map(DistributionSet::getId).toList())); - assertThat(distributionSetManagement.findByTag(tag.getId(), Pageable.unpaged()).getContent()).isEmpty(); + result = unassignTag(groupAB, tag); + Assertions. assertThat(result) + .hasSameSizeAs(groupAB) + .containsAll(distributionSetManagement.get(groupAB.stream().map(DistributionSet::getId).toList())); + assertThat(distributionSetManagement.findByTag(tag.getId(), UNPAGED).getContent()).isEmpty(); } - /** - * Verifies that tagging of set containing missing DS throws meaningful and correct exception. - */ @Test - void failOnMissingDs() { + void failToAssignTagIfMissingDs() { final Collection group = testdataFactory.createDistributionSets(5).stream().map(DistributionSet::getId).toList(); - final DistributionSetTag tag = distributionSetTagManagement.create( - DistributionSetTagManagement.Create.builder().name("tag1").description("tagdesc1").build()); + final DistributionSetTag tag = distributionSetTagManagement.create(Create.builder().name(TAG_1).description(TAG_DESCRIPTION_1).build()); final List missing = new ArrayList<>(); for (int i = 0; i < 5; i++) { while (true) { @@ -204,142 +153,33 @@ class DistributionSetTagManagementTest extends AbstractJpaIntegrationTest { }); } - /** - * Ensures that a created tag is persisted in the repository as defined. - */ @Test - void createDistributionSetTag() { - final Tag tag = distributionSetTagManagement - .create(DistributionSetTagManagement.Create.builder().name("kai1").description("kai2").colour("colour").build()); - - assertThat(distributionSetTagRepository.findById(tag.getId()).orElseThrow().getDescription()).as("wrong tag found") - .isEqualTo("kai2"); - assertThat(distributionSetTagManagement.find(tag.getId()).orElseThrow().getColour()).as("wrong tag found") - .isEqualTo("colour"); - assertThat(distributionSetTagManagement.find(tag.getId()).orElseThrow().getColour()).as("wrong tag found") - .isEqualTo("colour"); - } - - /** - * Ensures that a deleted tag is removed from the repository as defined. - */ - @Test - void deleteDistributionSetTag() { - // create test data - final Iterable tags = createDsSetsWithTags(); - final DistributionSetTag toDelete = tags.iterator().next(); - - for (final DistributionSet set : distributionSetRepository.findAll()) { - assertThat(distributionSetRepository.findById(set.getId()).get().getTags()).as("Wrong tag found") - .contains(toDelete); - } - - // delete - distributionSetTagManagement.delete(tags.iterator().next().getId()); - - // check - assertThat(distributionSetTagRepository.findById(toDelete.getId())).as("Deleted tag should be null") - .isNotPresent(); - assertThat(distributionSetTagManagement.findAll(PAGE).getContent()).as("Wrong size of tags after deletion") - .hasSize(19); - - for (final DistributionSet set : distributionSetRepository.findAll()) { - assertThat(distributionSetRepository.findById(set.getId()).get().getTags()).as("Wrong found tags") - .doesNotContain(toDelete); - } - } - - /** - * Ensures that a tag cannot be created if one exists already with that name (ecpects EntityAlreadyExistsException). - */ - @Test - void failedDuplicateDsTagNameException() { - final DistributionSetTagManagement.Create tag = DistributionSetTagManagement.Create.builder().name("A").build(); + void failedToCreateIfNameAlreadyExists() { + final Create tag = Create.builder().name("A").build(); distributionSetTagManagement.create(tag); - - assertThatExceptionOfType(EntityAlreadyExistsException.class).as("should not have worked as tag already exists") + assertThatExceptionOfType(EntityAlreadyExistsException.class) + .as("should not have worked as tag already exists") .isThrownBy(() -> distributionSetTagManagement.create(tag)); } - /** - * Ensures that a tag cannot be updated to a name that already exists on another tag (ecpects EntityAlreadyExistsException). - */ @Test - void failedDuplicateDsTagNameExceptionAfterUpdate() { - distributionSetTagManagement.create(DistributionSetTagManagement.Create.builder().name("A").build()); - final DistributionSetTag tag = distributionSetTagManagement.create(DistributionSetTagManagement.Create.builder().name("B").build()); - - final DistributionSetTagManagement.Update tagUpdate = DistributionSetTagManagement.Update.builder().id(tag.getId()).name("A").build(); - assertThatExceptionOfType(EntityAlreadyExistsException.class).as("should not have worked as tag already exists") + void failedToUpdateIfNameAlreadyExists() { + distributionSetTagManagement.create(Create.builder().name("A").build()); + final DistributionSetTag tag = distributionSetTagManagement.create(Create.builder().name("B").build()); + final Update tagUpdate = Update.builder().id(tag.getId()).name("A").build(); + assertThatExceptionOfType(EntityAlreadyExistsException.class) + .as("Constraint not applied - tag with same name already exists") .isThrownBy(() -> distributionSetTagManagement.update(tagUpdate)); } - /** - * Tests the name update of a target tag. - */ - @Test - void updateDistributionSetTag() { - // create test data - final List tags = createDsSetsWithTags(); - // change data - final DistributionSetTag savedAssigned = tags.iterator().next(); - // persist - distributionSetTagManagement.update(DistributionSetTagManagement.Update.builder().id(savedAssigned.getId()).name("test123").build()); - // check data - assertThat(distributionSetTagManagement.findAll(PAGE).getContent()).as("Wrong size of ds tags") - .hasSize(tags.size()); - assertThat(distributionSetTagRepository.findById(savedAssigned.getId()).get().getName()) - .as("Wrong ds tag found").isEqualTo("test123"); - } - - /** - * Ensures that all tags are retrieved through repository. - */ - @Test - void findDistributionSetTagsAll() { - final List tags = createDsSetsWithTags(); - - // test - assertThat(distributionSetTagManagement.findAll(PAGE).getContent()).as("Wrong size of tags") - .hasSize(tags.size()); - assertThat(distributionSetTagRepository.findAll()).as("Wrong size of tags").hasSize(20); - } - - /** - * Ensures that a created tags are persisted in the repository as defined. - */ - @Test - void createDistributionSetTags() { - final List tags = createDsSetsWithTags(); - assertThat(distributionSetTagRepository.findAll()).as("Wrong size of tags created").hasSize(tags.size()); - } - - private void verifyExpectedFilteredDistributionSets(final DistributionSetFilter.DistributionSetFilterBuilder distributionSetFilterBuilder, - final Stream> expectedFilteredDistributionSets) { - final Collection retrievedFilteredDsIds = findDsByDistributionSetFilter(distributionSetFilterBuilder.build(), PAGE).stream() - .map(DistributionSet::getId).toList(); - final Collection expectedFilteredDsIds = expectedFilteredDistributionSets.flatMap(Collection::stream) - .map(DistributionSet::getId).toList(); + @SafeVarargs + private void assertTaggedWithATagAreAsExpected(final DistributionSetTag tag, final Collection... expectedDistributionSets) { + final Collection retrievedFilteredDsIds = distributionSetManagement.findByTag(tag.getId(), UNPAGED).getContent().stream() + .map(DistributionSet::getId) + .toList(); + final Collection expectedFilteredDsIds = Stream.of(expectedDistributionSets).flatMap(Collection::stream) + .map(DistributionSet::getId) + .toList(); assertThat(retrievedFilteredDsIds).hasSameElementsAs(expectedFilteredDsIds); } - - private List createDsSetsWithTags() { - final Collection sets = testdataFactory.createDistributionSets(20); - final Iterable tags = testdataFactory.createDistributionSetTags(20); - - tags.forEach(tag -> assignTag(sets, tag)); - - return distributionSetTagManagement.findAll(PAGE).getContent().stream().map(DistributionSetTag.class::cast).toList(); - } - - private DistributionSetFilter.DistributionSetFilterBuilder getDistributionSetFilterBuilder() { - return DistributionSetFilter.builder(); - } - - @SafeVarargs - private Collection concat(final Collection... targets) { - final List result = new ArrayList<>(); - Arrays.asList(targets).forEach(result::addAll); - return result; - } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTypeManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTypeManagementTest.java index ef1592325..056806eab 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTypeManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTypeManagementTest.java @@ -22,7 +22,8 @@ import java.util.Set; import jakarta.validation.ConstraintViolationException; import org.eclipse.hawkbit.repository.DistributionSetManagement; -import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; +import org.eclipse.hawkbit.repository.DistributionSetTypeManagement.Create; +import org.eclipse.hawkbit.repository.DistributionSetTypeManagement.Update; import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeCreatedEvent; @@ -30,7 +31,6 @@ import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdated import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; @@ -47,67 +47,9 @@ import org.junit.jupiter.api.Test; * Feature: Component Tests - Repository
* Story: DistributionSet Management */ -class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTest { +class DistributionSetTypeManagementTest extends AbstractRepositoryManagementTest { - /** - * Verifies that management get access react as specified on calls for non existing entities by means - * of Optional not present. - */ - @Test - @ExpectEvents({ @Expect(type = DistributionSetCreatedEvent.class, count = 0) }) - void nonExistingEntityAccessReturnsNotPresent() { - assertThat(distributionSetTypeManagement.find(NOT_EXIST_IDL)).isNotPresent(); - assertThat(distributionSetTypeManagement.findByKey(NOT_EXIST_ID)).isNotPresent(); - assertThat(distributionSetTypeManagement.findByName(NOT_EXIST_ID)).isNotPresent(); - } - - /** - * Verifies that management queries react as specified on calls for non existing entities - * by means of throwing EntityNotFoundException. - */ - @Test - @ExpectEvents({ - @Expect(type = DistributionSetCreatedEvent.class, count = 0), - @Expect(type = DistributionSetTypeCreatedEvent.class, count = 1) }) - void entityQueriesReferringToNotExistingEntitiesThrowsException() { - - final List softwareModuleTypes = Collections.singletonList(osType.getId()); - - verifyThrownExceptionBy(() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(NOT_EXIST_IDL, - softwareModuleTypes), "DistributionSetType"); - final List notExistingSwModuleTypeIds = Collections.singletonList(NOT_EXIST_IDL); - verifyThrownExceptionBy(() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes( - testdataFactory.findOrCreateDistributionSetType("xxx", "xxx").getId(), notExistingSwModuleTypeIds), - "SoftwareModuleType"); - - verifyThrownExceptionBy(() -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(NOT_EXIST_IDL, - softwareModuleTypes), "DistributionSetType"); - verifyThrownExceptionBy(() -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes( - testdataFactory.findOrCreateDistributionSetType("xxx", "xxx").getId(), notExistingSwModuleTypeIds), - "SoftwareModuleType"); - - verifyThrownExceptionBy(() -> distributionSetTypeManagement.delete(NOT_EXIST_IDL), "DistributionSetType"); - - verifyThrownExceptionBy( - () -> distributionSetTypeManagement.update(DistributionSetTypeManagement.Update.builder().id(NOT_EXIST_IDL).build()), - "DistributionSetType"); - } - - /** - * Verify that a DistributionSet with invalid properties cannot be created or updated - */ - @Test - @ExpectEvents({ - @Expect(type = DistributionSetCreatedEvent.class, count = 1), - @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), - @Expect(type = DistributionSetUpdatedEvent.class, count = 0) }) - void createAndUpdateDistributionSetWithInvalidFields() { - final DistributionSet set = testdataFactory.createDistributionSet(); - - createAndUpdateDistributionSetWithInvalidDescription(set); - createAndUpdateDistributionSetWithInvalidName(set); - createAndUpdateDistributionSetWithInvalidVersion(set); - } + public static final String USED_BY_DS_TYPE_KEY = "updatableType"; /** * Tests the successful module update of unused distribution set type which is in fact allowed. @@ -115,150 +57,50 @@ class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTest { @Test void updateUnassignedDistributionSetTypeModules() { final DistributionSetType updatableType = distributionSetTypeManagement - .create(DistributionSetTypeManagement.Create.builder().key("updatableType").name("to be deleted").build()); - assertThat(distributionSetTypeManagement.findByKey("updatableType").get().getMandatoryModuleTypes()).isEmpty(); + .create(Create.builder().key(USED_BY_DS_TYPE_KEY).name("to be deleted").build()); + assertThat(distributionSetTypeManagement.findByKey(USED_BY_DS_TYPE_KEY).orElseThrow().getMandatoryModuleTypes()).isEmpty(); // add OS - distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(updatableType.getId(), - Set.of(osType.getId())); - assertThat(distributionSetTypeManagement.findByKey("updatableType").get().getMandatoryModuleTypes()) + distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(updatableType.getId(), Set.of(osType.getId())); + assertThat(distributionSetTypeManagement.findByKey(USED_BY_DS_TYPE_KEY).orElseThrow().getMandatoryModuleTypes()) .containsOnly(osType); // add JVM - distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(updatableType.getId(), - Set.of(runtimeType.getId())); - assertThat(distributionSetTypeManagement.findByKey("updatableType").get().getMandatoryModuleTypes()) + distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(updatableType.getId(), Set.of(runtimeType.getId())); + assertThat(distributionSetTypeManagement.findByKey(USED_BY_DS_TYPE_KEY).orElseThrow().getMandatoryModuleTypes()) .containsOnly(osType, runtimeType); // remove OS distributionSetTypeManagement.unassignSoftwareModuleType(updatableType.getId(), osType.getId()); - assertThat(distributionSetTypeManagement.findByKey("updatableType").get().getMandatoryModuleTypes()) - .containsOnly(runtimeType); + assertThat(distributionSetTypeManagement.findByKey(USED_BY_DS_TYPE_KEY).orElseThrow().getMandatoryModuleTypes()).containsOnly(runtimeType); } /** - * Verifies that the quota for software module types per distribution set type is enforced as expected. + * Tests the successful update of used distribution set type properties that are allowed. */ @Test - void quotaMaxSoftwareModuleTypes() { - final int quota = quotaManagement.getMaxSoftwareModuleTypesPerDistributionSetType(); - // create software module types - final List moduleTypeIds = new ArrayList<>(); - for (int i = 0; i < quota + 1; ++i) { - final SoftwareModuleTypeManagement.Create smCreate = SoftwareModuleTypeManagement.Create.builder().name("smType_" + i) - .description("smType_" + i).maxAssignments(1).colour("blue").key("smType_" + i).build(); - moduleTypeIds.add(softwareModuleTypeManagement.create(smCreate).getId()); - } - - // assign all types at once - final DistributionSetType dsType1 = distributionSetTypeManagement - .create(DistributionSetTypeManagement.Create.builder().key("dst1").name("dst1").build()); - final Long dsType1Id = dsType1.getId(); - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType1Id, moduleTypeIds)); - assertThatExceptionOfType(AssignmentQuotaExceededException.class).isThrownBy( - () -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType1Id, moduleTypeIds)); - - // assign as many mandatory modules as possible - final DistributionSetType dsType2 = distributionSetTypeManagement - .create(DistributionSetTypeManagement.Create.builder().key("dst2").name("dst2").build()); - final Long dsType2Id = dsType2.getId(); - distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType2Id, - moduleTypeIds.subList(0, quota)); - assertThat(distributionSetTypeManagement.find(dsType2Id)).isNotEmpty(); - assertThat(distributionSetTypeManagement.find(dsType2Id).get().getMandatoryModuleTypes()).hasSize(quota); - // assign one more to trigger the quota exceeded error - final List softwareModuleTypeIds = Collections.singletonList(moduleTypeIds.get(quota)); - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType2Id, softwareModuleTypeIds)); - - // assign as many optional modules as possible - final DistributionSetType dsType3 = distributionSetTypeManagement - .create(DistributionSetTypeManagement.Create.builder().key("dst3").name("dst3").build()); - final Long dsType3Id = dsType3.getId(); - distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType3Id, moduleTypeIds.subList(0, quota)); - assertThat(distributionSetTypeManagement.find(dsType3Id)).isNotEmpty(); - assertThat(distributionSetTypeManagement.find(dsType3Id).get().getOptionalModuleTypes()).hasSize(quota); - // assign one more to trigger the quota exceeded error - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType3Id, softwareModuleTypeIds)); - + void updateAssignedDistributionSetTypeProperties() { + final DistributionSetType dsType = createDistributionSetTypeUsedByDs(); + distributionSetTypeManagement.update(Update.builder().id(dsType.getId()).description("a new description").build()); + assertThat(distributionSetTypeManagement.findByKey(USED_BY_DS_TYPE_KEY).orElseThrow().getDescription()).isEqualTo("a new description"); + assertThat(distributionSetTypeManagement.findByKey(USED_BY_DS_TYPE_KEY).orElseThrow().getColour()).isEqualTo("test123"); } /** - * Tests the successfull update of used distribution set type meta data which is in fact allowed. + * Tests the successful deletion of used (soft delete) distribution set types. */ @Test - void updateAssignedDistributionSetTypeMetaData() { - final DistributionSetType nonUpdatableType = createDistributionSetTypeUsedByDs(); - - distributionSetTypeManagement.update( - DistributionSetTypeManagement.Update.builder().id(nonUpdatableType.getId()).description("a new description").build()); - - assertThat(distributionSetTypeManagement.findByKey("updatableType").orElseThrow().getDescription()).isEqualTo("a new description"); - assertThat(distributionSetTypeManagement.findByKey("updatableType").orElseThrow().getColour()).isEqualTo("test123"); - } - - /** - * Tests the unsuccessful update of used distribution set type (module addition). - */ - @Test - void addModuleToAssignedDistributionSetTypeFails() { - final Long nonUpdatableTypeId = createDistributionSetTypeUsedByDs().getId(); - final Set osTypeId = Set.of(osType.getId()); - assertThatThrownBy(() -> distributionSetTypeManagement - .assignMandatorySoftwareModuleTypes(nonUpdatableTypeId, osTypeId)) - .isInstanceOf(EntityReadOnlyException.class); - } - - /** - * Tests the unsuccessful update of used distribution set type (module removal). - */ - @Test - void removeModuleToAssignedDistributionSetTypeFails() { - DistributionSetType nonUpdatableType = distributionSetTypeManagement - .create(DistributionSetTypeManagement.Create.builder().key("updatableType").name("to be deleted").build()); - assertThat(distributionSetTypeManagement.findByKey("updatableType").get().getMandatoryModuleTypes()).isEmpty(); - - final Long osTypeId = osType.getId(); - nonUpdatableType = distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(nonUpdatableType.getId(), Set.of(osTypeId)); - distributionSetManagement.create( - DistributionSetManagement.Create.builder().type(nonUpdatableType).name("newtypesoft").version("1").build()); - - final Long typeId = nonUpdatableType.getId(); - assertThatThrownBy(() -> distributionSetTypeManagement.unassignSoftwareModuleType(typeId, osTypeId)) - .isInstanceOf(EntityReadOnlyException.class); - } - - /** - * Tests the successfull deletion of unused (hard delete) distribution set types. - */ - @Test - void deleteUnassignedDistributionSetType() { - final JpaDistributionSetType hardDelete = (JpaDistributionSetType) distributionSetTypeManagement - .create(DistributionSetTypeManagement.Create.builder().key("delete").name("to be deleted").build()); - - assertThat(distributionSetTypeRepository.findAll()).contains(hardDelete); - distributionSetTypeManagement.delete(hardDelete.getId()); - - assertThat(distributionSetTypeRepository.findAll()).doesNotContain(hardDelete); - } - - /** - * Tests the successfull deletion of used (soft delete) distribution set types. - */ - @Test - void deleteAssignedDistributionSetType() { + void softDeleteAssignedDistributionSetType() { final int existing = (int) distributionSetTypeManagement.count(); final JpaDistributionSetType toBeDeleted = (JpaDistributionSetType) distributionSetTypeManagement - .create(DistributionSetTypeManagement.Create.builder().key("softdeleted").name("to be deleted").build()); + .create(Create.builder().key("softDeleted").name("to be deleted").build()); assertThat(distributionSetTypeRepository.findAll()).contains(toBeDeleted); distributionSetManagement.create( - DistributionSetManagement.Create.builder().type(toBeDeleted).name("softdeleted").version("1").build()); + DistributionSetManagement.Create.builder().type(toBeDeleted).name("softDeleted").version("1").build()); distributionSetTypeManagement.delete(toBeDeleted.getId()); - final Optional softDeleted = distributionSetTypeManagement.findByKey("softdeleted"); + final Optional softDeleted = distributionSetTypeManagement.findByKey("softDeleted"); assertThat(softDeleted).isPresent(); assertThat(softDeleted.get().isDeleted()).isTrue(); assertThat(distributionSetTypeManagement.findAll(PAGE)).hasSize(existing); @@ -270,18 +112,139 @@ class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTest { * Verifies that when no SoftwareModules are assigned to a Distribution then the DistributionSet is not complete. */ @Test - void shouldFailWhenDistributionSetHasNoSoftwareModulesAssigned() { + void incompleteIfDistributionSetHasNoSoftwareModulesAssigned() { final JpaDistributionSetType jpaDistributionSetType = (JpaDistributionSetType) distributionSetTypeManagement - .create(DistributionSetTypeManagement.Create.builder().key("newType").name("new Type").build()); - - final List softwareModules = new ArrayList<>(); - - final DistributionSet distributionSet = testdataFactory.createDistributionSet("DistributionOne", "3.1.2", - jpaDistributionSetType, softwareModules); - + .create(Create.builder().key("newType").name("new Type").build()); + final DistributionSet distributionSet = testdataFactory.createDistributionSet( + "DistributionOne", "3.1.2", jpaDistributionSetType, new ArrayList<>()); assertThat(jpaDistributionSetType.checkComplete(distributionSet)).isFalse(); } + /** + * Verifies that the quota for software module types per distribution set type is enforced as expected. + */ + @Test + void failIfQuotaExceeded() { + final int quota = quotaManagement.getMaxSoftwareModuleTypesPerDistributionSetType(); + // create software module types + final List moduleTypeIds = new ArrayList<>(); + for (int i = 0; i < quota + 1; ++i) { + final SoftwareModuleTypeManagement.Create smCreate = SoftwareModuleTypeManagement.Create.builder().name("smType_" + i) + .description("smType_" + i).maxAssignments(1).colour("blue").key("smType_" + i).build(); + moduleTypeIds.add(softwareModuleTypeManagement.create(smCreate).getId()); + } + + // assign all types at once + final DistributionSetType dsType1 = distributionSetTypeManagement + .create(Create.builder().key("dst1").name("dst1").build()); + final Long dsType1Id = dsType1.getId(); + assertThatExceptionOfType(AssignmentQuotaExceededException.class) + .isThrownBy(() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType1Id, moduleTypeIds)); + assertThatExceptionOfType(AssignmentQuotaExceededException.class).isThrownBy( + () -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType1Id, moduleTypeIds)); + + // assign as many mandatory modules as possible + final DistributionSetType dsType2 = distributionSetTypeManagement + .create(Create.builder().key("dst2").name("dst2").build()); + final Long dsType2Id = dsType2.getId(); + distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType2Id, + moduleTypeIds.subList(0, quota)); + assertThat(distributionSetTypeManagement.find(dsType2Id)).isNotEmpty(); + assertThat(distributionSetTypeManagement.find(dsType2Id).get().getMandatoryModuleTypes()).hasSize(quota); + // assign one more to trigger the quota exceeded error + final List softwareModuleTypeIds = Collections.singletonList(moduleTypeIds.get(quota)); + assertThatExceptionOfType(AssignmentQuotaExceededException.class) + .isThrownBy(() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType2Id, softwareModuleTypeIds)); + + // assign as many optional modules as possible + final DistributionSetType dsType3 = distributionSetTypeManagement.create(Create.builder().key("dst3").name("dst3").build()); + final Long dsType3Id = dsType3.getId(); + distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType3Id, moduleTypeIds.subList(0, quota)); + assertThat(distributionSetTypeManagement.find(dsType3Id)).isNotEmpty(); + assertThat(distributionSetTypeManagement.find(dsType3Id).get().getOptionalModuleTypes()).hasSize(quota); + // assign one more to trigger the quota exceeded error + assertThatExceptionOfType(AssignmentQuotaExceededException.class) + .isThrownBy(() -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType3Id, softwareModuleTypeIds)); + } + + @Test + @ExpectEvents({ + @Expect(type = DistributionSetCreatedEvent.class, count = 0), + @Expect(type = DistributionSetTypeCreatedEvent.class, count = 1) }) + void failIfReferNotExistingEntity() { + final List softwareModuleTypes = Collections.singletonList(osType.getId()); + + verifyThrownExceptionBy( + () -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(NOT_EXIST_IDL, softwareModuleTypes), + "DistributionSetType"); + final List notExistingSwModuleTypeIds = Collections.singletonList(NOT_EXIST_IDL); + verifyThrownExceptionBy( + () -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes( + testdataFactory.findOrCreateDistributionSetType("xxx", "xxx").getId(), notExistingSwModuleTypeIds), + "SoftwareModuleType"); + + verifyThrownExceptionBy( + () -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(NOT_EXIST_IDL, softwareModuleTypes), + "DistributionSetType"); + verifyThrownExceptionBy( + () -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes( + testdataFactory.findOrCreateDistributionSetType("xxx", "xxx").getId(), notExistingSwModuleTypeIds), + "SoftwareModuleType"); + + verifyThrownExceptionBy(() -> distributionSetTypeManagement.delete(NOT_EXIST_IDL), "DistributionSetType"); + + verifyThrownExceptionBy( + () -> distributionSetTypeManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), + "DistributionSetType"); + } + + /** + * Verify that a DistributionSet with invalid properties cannot be created or updated + */ + @Test + @ExpectEvents({ + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetUpdatedEvent.class, count = 0) }) + void failToCreateAndUpdateDistributionSetWithInvalidFields() { + final DistributionSet set = testdataFactory.createDistributionSet(); + + createAndUpdateDistributionSetWithInvalidDescription(set); + createAndUpdateDistributionSetWithInvalidName(set); + createAndUpdateDistributionSetWithInvalidVersion(set); + } + + /** + * Tests the unsuccessful update of used distribution set type (module addition). + */ + @Test + void failAddModuleIfAssignedDistributionSetType() { + final Long nonUpdatableTypeId = createDistributionSetTypeUsedByDs().getId(); + final Set osTypeId = Set.of(osType.getId()); + assertThatThrownBy(() -> distributionSetTypeManagement + .assignMandatorySoftwareModuleTypes(nonUpdatableTypeId, osTypeId)) + .isInstanceOf(EntityReadOnlyException.class); + } + + /** + * Tests the unsuccessful update of used distribution set type (module removal). + */ + @Test + void failToRemoveModuleIfAssignedDistributionSetType() { + DistributionSetType nonUpdatableType = distributionSetTypeManagement + .create(Create.builder().key(USED_BY_DS_TYPE_KEY).name("to be deleted").build()); + assertThat(distributionSetTypeManagement.findByKey(USED_BY_DS_TYPE_KEY).get().getMandatoryModuleTypes()).isEmpty(); + + final Long osTypeId = osType.getId(); + nonUpdatableType = distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(nonUpdatableType.getId(), Set.of(osTypeId)); + distributionSetManagement.create( + DistributionSetManagement.Create.builder().type(nonUpdatableType).name("newtypesoft").version("1").build()); + + final Long typeId = nonUpdatableType.getId(); + assertThatThrownBy(() -> distributionSetTypeManagement.unassignSoftwareModuleType(typeId, osTypeId)) + .isInstanceOf(EntityReadOnlyException.class); + } + private void createAndUpdateDistributionSetWithInvalidDescription(final DistributionSet set) { final DistributionSetManagement.Create distributionSetCreate = DistributionSetManagement.Create.builder() .name("a").version("a").description(randomString(513)).build(); @@ -301,7 +264,8 @@ class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTest { .as("set with too long description should not be updated") .isThrownBy(() -> distributionSetManagement.update(distributionSetUpdate)); - final DistributionSetManagement.Update distributionSetUpdate2 = DistributionSetManagement.Update.builder().id(set.getId()).description(INVALID_TEXT_HTML).build(); + final DistributionSetManagement.Update distributionSetUpdate2 = DistributionSetManagement.Update.builder().id(set.getId()) + .description(INVALID_TEXT_HTML).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("set with invalid description should not be updated") .isThrownBy(() -> distributionSetManagement.update(distributionSetUpdate2)); @@ -396,13 +360,13 @@ class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTest { } private DistributionSetType createDistributionSetTypeUsedByDs() { - final DistributionSetType nonUpdatableType = distributionSetTypeManagement.create( - DistributionSetTypeManagement.Create.builder().key("updatableType").name("to be deleted").colour("test123").build()); - assertThat(distributionSetTypeManagement.findByKey("updatableType").orElseThrow().getMandatoryModuleTypes()).isEmpty(); + final DistributionSetType dsType = distributionSetTypeManagement.create( + Create.builder().key(USED_BY_DS_TYPE_KEY).name("to be deleted").colour("test123").build()); + assertThat(distributionSetTypeManagement.findByKey(USED_BY_DS_TYPE_KEY).orElseThrow().getMandatoryModuleTypes()).isEmpty(); distributionSetManagement.create(DistributionSetManagement.Create.builder() - .type(nonUpdatableType) - .name("newtypesoft").version("1") + .type(dsType) + .name("newTypeSoft").version("1") .build()); - return nonUpdatableType; + return dsType; } -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ManagementSecurityTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ManagementSecurityTest.java index 622b1e411..e83e0c3f3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ManagementSecurityTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ManagementSecurityTest.java @@ -352,9 +352,6 @@ class ManagementSecurityTest extends AbstractJpaIntegrationTest { } else { try { final Constructor[] constructors = clazz.getDeclaredConstructors(); - if (ObjectUtils.isEmpty(constructors)) { - throw new IllegalStateException("No public constructor found for " + clazz); - } // prefer empty constructor for (final Constructor constructor : constructors) { if (constructor.getParameterCount() == 0) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutGroupManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutGroupManagementTest.java index 7f17cacf0..16859685a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutGroupManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutGroupManagementTest.java @@ -64,7 +64,6 @@ class RolloutGroupManagementTest extends AbstractJpaIntegrationTest { void entityQueriesReferringToNotExistingEntitiesThrowsException() { testdataFactory.createRollout(); - verifyThrownExceptionBy(() -> rolloutGroupManagement.countByRollout(NOT_EXIST_IDL), "Rollout"); verifyThrownExceptionBy(() -> rolloutGroupManagement.countTargetsOfRolloutsGroup(NOT_EXIST_IDL), "RolloutGroup"); verifyThrownExceptionBy(() -> rolloutGroupManagement.findByRolloutWithDetailedStatus(NOT_EXIST_IDL, PAGE), "Rollout"); verifyThrownExceptionBy(() -> rolloutGroupManagement.findByRolloutAndRsql(NOT_EXIST_IDL, "name==*", PAGE), "Rollout"); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleManagementTest.java index 2e34816d6..57c6381a5 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleManagementTest.java @@ -14,32 +14,24 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; +import java.lang.reflect.Method; import java.util.Collections; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; import jakarta.validation.ConstraintViolationException; import org.eclipse.hawkbit.repository.DistributionSetManagement; -import org.eclipse.hawkbit.repository.SoftwareModuleManagement; +import org.eclipse.hawkbit.repository.SoftwareModuleManagement.Create; +import org.eclipse.hawkbit.repository.SoftwareModuleManagement.Update; import org.eclipse.hawkbit.repository.artifact.exception.ArtifactBinaryNotFoundException; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; -import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; -import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; -import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.LockedException; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.RandomGeneratedInputStream; -import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; -import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; -import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; -import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; -import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; -import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.ArtifactUpload; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -48,162 +40,34 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValue; import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValueCreate; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; -import org.eclipse.hawkbit.repository.test.util.WithUser; import org.junit.jupiter.api.Test; /** * Feature: Component Tests - Repository
* Story: Software Module Management */ -class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { +class SoftwareModuleManagementTest + extends AbstractRepositoryManagementWithMetadataTest { - /** - * Verifies that management get access reacts as specified on calls for non existing entities by means - * of Optional not present. - */ - @Test - @ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class, count = 1) }) - void nonExistingEntityAccessReturnsNotPresent() { - final SoftwareModule module = testdataFactory.createSoftwareModuleApp(); - assertThat(softwareModuleManagement.find(1234L)).isNotPresent(); - final Long moduleId = module.getId(); - assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> softwareModuleManagement.getMetadata(moduleId, NOT_EXIST_ID)); + @SuppressWarnings("unchecked") + @Override + protected O forType(final Class type) { + if (type == MetadataValueCreate.class) { + return (O) new MetadataValueCreate(forType(String.class), forType(Boolean.class)); + } + return super.forType(type); } - /** - * Verifies that management queries react as specfied on calls for non existing entities - * by means of throwing EntityNotFoundException. - */ - @Test - @ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class, count = 1) }) - void entityQueriesReferringToNotExistingEntitiesThrowsException() { - testdataFactory.createSoftwareModuleApp(); + @Override + protected Object builderParameterValue(final Method builderSetter) { + // encrypted true is not supported + if (builderSetter.getDeclaringClass() == Create.CreateBuilder.class && "encrypted".equals(builderSetter.getName())) { + return Boolean.FALSE; + } - final SoftwareModuleManagement.Create noType = SoftwareModuleManagement.Create.builder().name("xxx").type(null).build(); - final List noTypeList = List.of(noType); - assertThatExceptionOfType(ConstraintViolationException.class) - .isThrownBy(() -> softwareModuleManagement.create(noTypeList)); - assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> softwareModuleManagement.create(noType)); - - verifyThrownExceptionBy( - () -> softwareModuleManagement.createMetadata( - NOT_EXIST_IDL, "xxx", new MetadataValueCreate("xxx")), "SoftwareModule"); - verifyThrownExceptionBy( - () -> softwareModuleManagement.createMetadata( - NOT_EXIST_IDL, Map.of("xxx", new MetadataValueCreate("xxx"))), "SoftwareModule"); - - verifyThrownExceptionBy(() -> softwareModuleManagement.delete(NOT_EXIST_IDL), "SoftwareModule"); - verifyThrownExceptionBy(() -> softwareModuleManagement.delete(Collections.singletonList(NOT_EXIST_IDL)), "SoftwareModule"); - verifyThrownExceptionBy(() -> softwareModuleManagement.deleteMetadata(NOT_EXIST_IDL, "xxx"), "SoftwareModule"); - - verifyThrownExceptionBy( - () -> softwareModuleManagement.createMetadata(NOT_EXIST_IDL, "xxx", new MetadataValueCreate("xxx")), "SoftwareModule"); - - verifyThrownExceptionBy(() -> softwareModuleManagement.findByAssignedTo(NOT_EXIST_IDL, PAGE), "DistributionSet"); - verifyThrownExceptionBy(() -> softwareModuleManagement.getMetadata(NOT_EXIST_IDL, NOT_EXIST_ID), "SoftwareModule"); - verifyThrownExceptionBy(() -> softwareModuleManagement.getMetadata(NOT_EXIST_IDL), "SoftwareModule"); - verifyThrownExceptionBy(() -> softwareModuleManagement.update(SoftwareModuleManagement.Update.builder().id(NOT_EXIST_IDL).build()), - "SoftwareModule"); - } - - /** - * Calling update without changing fields results in no recorded change in the repository including unchanged audit fields. - */ - @Test - void updateNothingResultsInUnchangedRepository() { - final SoftwareModule ah = testdataFactory.createSoftwareModuleOs(); - - final SoftwareModule updated = softwareModuleManagement - .update(SoftwareModuleManagement.Update.builder().id(ah.getId()).build()); - - assertThat(updated.getOptLockRevision()) - .as("Expected version number of updated entity to be equal to created version") - .isEqualTo(ah.getOptLockRevision()); - } - - /** - * Calling update for changed fields results in change in the repository. - */ - @Test - void updateSoftwareModuleFieldsToNewValue() { - final SoftwareModule ah = testdataFactory.createSoftwareModuleOs(); - - final SoftwareModule updated = softwareModuleManagement - .update(SoftwareModuleManagement.Update.builder().id(ah.getId()).description("changed").vendor("changed").build()); - - assertThat(updated.getOptLockRevision()) - .as("Expected version number of updated entitity is") - .isEqualTo(ah.getOptLockRevision() + 1); - assertThat(updated.getDescription()).as("Updated description is").isEqualTo("changed"); - assertThat(updated.getVendor()).as("Updated vendor is").isEqualTo("changed"); - } - - /** - * Create Software Module call fails when called for existing entity. - */ - @Test - void createModuleCallFailsForExistingModule() { - testdataFactory.createSoftwareModuleOs(); - assertThatExceptionOfType(EntityAlreadyExistsException.class) - .as("Should not have worked as module already exists.") - .isThrownBy(() -> testdataFactory.createSoftwareModuleOs()); - } - - /** - * searched for software modules based on the various filter options, e.g. name,desc,type, version. - */ - @Test - void findSoftwareModuleByFilters() { - softwareModuleManagement - .create(SoftwareModuleManagement.Create.builder().type(appType).name("agent-hub").version("1.0.1").build()); - final SoftwareModule jvm = softwareModuleManagement - .create(SoftwareModuleManagement.Create.builder().type(runtimeType).name("oracle-jre").version("1.7.2").build()); - final SoftwareModule os = softwareModuleManagement - .create(SoftwareModuleManagement.Create.builder().type(osType).name("poky").version("3.0.2").build()); - - final SoftwareModule ah2 = softwareModuleManagement - .create(SoftwareModuleManagement.Create.builder().type(appType).name("agent-hub").version("1.0.2").build()); - JpaDistributionSet ds = (JpaDistributionSet) distributionSetManagement - .create(DistributionSetManagement.Create.builder() - .type(standardDsType) - .name("ds-1").version("1.0.1") - .modules(Set.of(os, jvm, ah2)) - .build()); - - final JpaTarget target = (JpaTarget) testdataFactory.createTarget(); - ds = (JpaDistributionSet) assignSet(target, ds).getDistributionSet(); - implicitLock(os); - implicitLock(jvm); - - distributionSetManagement.unlock(ds); // otherwise delete will be rejected as a part of a locked DS - softwareModuleManagement.delete(ah2.getId()); - } - - /** - * Searches for software modules based on a list of IDs. - */ - @Test - void findSoftwareModulesById() { - final List modules = List.of(testdataFactory.createSoftwareModuleOs().getId(), testdataFactory.createSoftwareModuleApp().getId()); - assertThat(softwareModuleManagement.get(modules)).hasSize(2); - } - - /** - * Counts all software modules in the repsitory that are not marked as deleted. - */ - @Test - void countSoftwareModulesAll() { - // found in test - testdataFactory.createSoftwareModuleOs("one"); - testdataFactory.createSoftwareModuleOs("two"); - final SoftwareModule deleted = testdataFactory.createSoftwareModuleOs("deleted"); - // ignored - softwareModuleManagement.delete(deleted.getId()); - - assertThat(softwareModuleManagement.count()).as("Expected to find the following number of modules:").isEqualTo(2); + return super.builderParameterValue(builderSetter); } /** @@ -211,7 +75,6 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { */ @Test void hardDeleteOfNotAssignedArtifact() { - // [STEP1]: Create SoftwareModuleX with Artifacts final SoftwareModule unassignedModule = createSoftwareModuleWithArtifacts(osType, "moduleX", "3.0.2", 2); final Iterator artifactsIt = unassignedModule.getArtifacts().iterator(); @@ -273,7 +136,6 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { */ @Test void softDeleteOfHistoricalAssignedArtifact() { - // Init target final Target target = testdataFactory.createTarget(); @@ -311,11 +173,10 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { } /** - * Delete an software module with an artifact, which is also used by another software module. + * Delete a software module with an artifact, which is also used by another software module. */ @Test void deleteSoftwareModulesWithSharedArtifact() { - // Init artifact binary data, target and DistributionSets final int artifactSize = 1024; final byte[] source = randomBytes(artifactSize); @@ -355,11 +216,10 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { // verify: meta data of artifactY is not deleted assertThat(artifactRepository.findById(artifactY.getId())).isPresent(); - } /** - * Delete two assigned softwaremodules which share an artifact. + * Delete two assigned software modules which share an artifact. */ @Test void deleteMultipleSoftwareModulesWhichShareAnArtifact() { @@ -419,10 +279,10 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { } /** - * Verifies that all undeleted software modules are found in the repository. + * Verifies that all soft deleted software modules are found in the repository. */ @Test - void countSoftwareModuleTypesAll() { + void countSoftwareModules() { testdataFactory.createSoftwareModuleOs(); // one soft deleted @@ -454,187 +314,6 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { .as("Found this number of modules").hasSize(2); } - /** - * Checks that metadata for a software module can be created. - */ - @Test - void createSoftwareModuleMetadata() { - final String knownKey1 = "myKnownKey1"; - final String knownValue1 = "myKnownValue1"; - - final String knownKey2 = "myKnownKey2"; - final String knownValue2 = "myKnownValue2"; - - final SoftwareModule softwareModule = testdataFactory.createSoftwareModuleApp(); - - assertThat(softwareModule.getOptLockRevision()).isEqualTo(1); - - softwareModuleManagement.createMetadata( - softwareModule.getId(), - Map.of( - knownKey1, new MetadataValueCreate(knownValue1, true), - knownKey2, new MetadataValueCreate(knownValue2))); - - final SoftwareModule changedLockRevisionModule = softwareModuleManagement.find(softwareModule.getId()).get(); - assertThat(changedLockRevisionModule.getOptLockRevision()).isEqualTo(2); - - assertThat(softwareModuleManagement.getMetadata(softwareModule.getId(), knownKey1)).satisfies(metadata -> { - assertThat(metadata.getValue()).isEqualTo(knownValue1); - assertThat(metadata.isTargetVisible()).isTrue(); - }); - - assertThat(softwareModuleManagement.getMetadata(softwareModule.getId(), knownKey2)).satisfies(metadata -> { - assertThat(metadata.getValue()).isEqualTo(knownValue2); - assertThat(metadata.isTargetVisible()).isFalse(); - }); - } - - /** - * Verifies the enforcement of the metadata quota per software module. - */ - @Test - void createSoftwareModuleMetadataUntilQuotaIsExceeded() { - // add meta-data one by one - final SoftwareModule module = testdataFactory.createSoftwareModuleApp("m1"); - final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule(); - for (int i = 0; i < maxMetaData; ++i) { - softwareModuleManagement.createMetadata(module.getId(), "k" + i, new MetadataValueCreate("v" + i)); - } - - // quota exceeded - final Long moduleId = module.getId(); - final MetadataValueCreate metadata = new MetadataValueCreate("v" + maxMetaData); - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> softwareModuleManagement.createMetadata(moduleId, "k" + maxMetaData, metadata)); - - // add multiple meta data entries at once - final SoftwareModule module2 = testdataFactory.createSoftwareModuleApp("m2"); - final Long moduleId2 = module2.getId(); - final Map create = new HashMap<>(); - for (int i = 0; i < maxMetaData + 1; ++i) { - create.put("k" + i, new MetadataValueCreate("v" + i)); - } - // quota exceeded - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> softwareModuleManagement.createMetadata(moduleId2, create)); - - // add some meta data entries - final SoftwareModule module3 = testdataFactory.createSoftwareModuleApp("m3"); - final int firstHalf = Math.round((maxMetaData) / 2.f); - for (int i = 0; i < firstHalf; ++i) { - softwareModuleManagement.createMetadata(module3.getId(), "k" + i, new MetadataValueCreate("v" + i)); - } - // add too many data entries - final Long moduleId3 = module3.getId(); - final int secondHalf = maxMetaData - firstHalf; - final Map create2 = new HashMap<>(); - for (int i = 0; i < secondHalf + 1; ++i) { - create2.put("kk" + i, new MetadataValueCreate("vv" + i)); - } - // quota exceeded - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> softwareModuleManagement.createMetadata(moduleId3, create2)); - - } - - /** - * Checks that metadata for a software module can be updated. - */ - @Test - @WithUser(allSpPermissions = true) - void updateSoftwareModuleMetadata() { - final String knownKey = "myKnownKey"; - final String knownValue = "myKnownValue"; - final String knownUpdateValue = "myNewUpdatedValue"; - - // create a base software module - final SoftwareModule ah = testdataFactory.createSoftwareModuleApp(); - // initial opt lock revision must be 1 - assertThat(ah.getOptLockRevision()).isEqualTo(1); - - // create an software module meta data entry - softwareModuleManagement.createMetadata(ah.getId(), knownKey, new MetadataValueCreate(knownValue)); - final MetadataValue softwareModuleMetadata = softwareModuleManagement.getMetadata(ah.getId(), knownKey); - assertThat(softwareModuleMetadata.isTargetVisible()).isFalse(); - assertThat(softwareModuleMetadata.getValue()).isEqualTo(knownValue); - - // base software module should have now the opt lock revision one - // because we are modifying the base software module - SoftwareModule changedLockRevisionModule = softwareModuleManagement.find(ah.getId()).get(); - assertThat(changedLockRevisionModule.getOptLockRevision()).isEqualTo(2); - - // update the software module metadata - softwareModuleManagement.createMetadata(ah.getId(), knownKey, new MetadataValueCreate(knownUpdateValue, true)); - final MetadataValue updated = softwareModuleManagement.getMetadata(ah.getId(), knownKey); - - // we are updating the sw metadata so also modifying the base software - // module so opt lock revision must be two - changedLockRevisionModule = softwareModuleManagement.find(ah.getId()).get(); - assertThat(changedLockRevisionModule.getOptLockRevision()).isEqualTo(3); - - // verify updated meta data contains the updated value - assertThat(updated).isNotNull(); - assertThat(updated.getValue()).isEqualTo(knownUpdateValue); - assertThat(updated.isTargetVisible()).isTrue(); - } - - /** - * Verifies that existing metadata can be deleted. - */ - @Test - void deleteSoftwareModuleMetadata() { - final String knownKey1 = "myKnownKey1"; - final String knownValue1 = "myKnownValue1"; - - final SoftwareModule swModule = testdataFactory.createSoftwareModuleApp(); - - softwareModuleManagement.createMetadata(swModule.getId(), knownKey1, new MetadataValueCreate(knownValue1)); - - assertThat(softwareModuleManagement.getMetadata(swModule.getId()).entrySet()) - .as("Contains the created metadata element").allSatisfy(metadata -> { - assertThat(metadata.getKey()).isEqualTo(knownKey1); - assertThat(metadata.getValue().getValue()).isEqualTo(knownValue1); - }); - - softwareModuleManagement.deleteMetadata(swModule.getId(), knownKey1); - assertThat(softwareModuleManagement.getMetadata(swModule.getId())).as("Metadata elements are").isEmpty(); - } - - /** - * Queries and loads the metadata related to a given software module. - */ - @Test - void findAllSoftwareModuleMetadataBySwId() { - final SoftwareModule sw1 = testdataFactory.createSoftwareModuleApp(); - final int metadataCountSw1 = 8; - - final SoftwareModule sw2 = testdataFactory.createSoftwareModuleOs(); - final int metadataCountSw2 = 10; - - for (int index = 0; index < metadataCountSw1; index++) { - softwareModuleManagement.createMetadata(sw1.getId(), "key" + index, new MetadataValueCreate("value" + index, true)); - } - - for (int index = 0; index < metadataCountSw2; index++) { - softwareModuleManagement.createMetadata(sw2.getId(), "key" + index, new MetadataValueCreate("value" + index, false)); - } - - final Map metadataSw1 = softwareModuleManagement.getMetadata(sw1.getId()); - final Map metadataSw2 = softwareModuleManagement.getMetadata(sw2.getId()); - assertThat(metadataSw1).hasSize(metadataCountSw1); - assertThat(metadataSw2).hasSize(metadataCountSw2); - - final Map metadataSw1V = softwareModuleManagement - .findMetaDataBySoftwareModuleIdsAndTargetVisible(List.of(sw1.getId())).get(sw1.getId()); - final Map metadataSw2V = softwareModuleManagement - .findMetaDataBySoftwareModuleIdsAndTargetVisible(List.of(sw2.getId())).get(sw2.getId()); - assertThat(metadataSw1V).hasSize(metadataCountSw1); - assertThat(metadataSw2V).isNull(); - } - - /** - * Locks a SM. - */ @Test void lockSoftwareModule() { final SoftwareModule softwareModule = testdataFactory.createSoftwareModule("sm-1"); @@ -643,9 +322,6 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { assertThat(softwareModuleManagement.find(softwareModule.getId()).map(SoftwareModule::isLocked).orElse(false)).isTrue(); } - /** - * Unlocks a SM. - */ @Test void unlockSoftwareModule() { SoftwareModule softwareModule = testdataFactory.createSoftwareModule("sm-1"); @@ -711,32 +387,40 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { .isThrownBy(() -> softwareModuleManagement.delete(moduleId)); } - private Action assignSet(final JpaTarget target, final JpaDistributionSet ds) { - assignDistributionSet(ds.getId(), target.getControllerId()); - implicitLock(ds); - assertThat(targetManagement.getByControllerId(target.getControllerId()).orElseThrow().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.PENDING); - final Optional assignedDistributionSet = deploymentManagement - .getAssignedDistributionSet(target.getControllerId()); - assertThat(assignedDistributionSet).contains(ds); - final Action action = actionRepository - .findAll( - (root, query, cb) -> - cb.and( - cb.equal(root.get(JpaAction_.target).get(JpaTarget_.id), target.getId()), - cb.equal(root.get(JpaAction_.distributionSet).get(JpaDistributionSet_.id), ds.getId())), - PAGE).getContent().get(0); - assertThat(action).isNotNull(); - return action; + @Test + @ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class, count = 1) }) + void failIfReferNotExistingEntity() { + testdataFactory.createSoftwareModuleApp(); + + final Create noType = Create.builder().name("xxx").type(null).build(); + final List noTypeList = List.of(noType); + assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> softwareModuleManagement.create(noTypeList)); + assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> softwareModuleManagement.create(noType)); + + verifyThrownExceptionBy( + () -> softwareModuleManagement.createMetadata(NOT_EXIST_IDL, "xxx", new MetadataValueCreate("xxx")), "SoftwareModule"); + verifyThrownExceptionBy( + () -> softwareModuleManagement.createMetadata(NOT_EXIST_IDL, Map.of("xxx", new MetadataValueCreate("xxx"))), "SoftwareModule"); + + verifyThrownExceptionBy(() -> softwareModuleManagement.delete(NOT_EXIST_IDL), "SoftwareModule"); + verifyThrownExceptionBy(() -> softwareModuleManagement.delete(Collections.singletonList(NOT_EXIST_IDL)), "SoftwareModule"); + verifyThrownExceptionBy(() -> softwareModuleManagement.deleteMetadata(NOT_EXIST_IDL, "xxx"), "SoftwareModule"); + + verifyThrownExceptionBy( + () -> softwareModuleManagement.createMetadata(NOT_EXIST_IDL, "xxx", new MetadataValueCreate("xxx")), "SoftwareModule"); + + verifyThrownExceptionBy(() -> softwareModuleManagement.findByAssignedTo(NOT_EXIST_IDL, PAGE), "DistributionSet"); + verifyThrownExceptionBy(() -> softwareModuleManagement.getMetadata(NOT_EXIST_IDL, NOT_EXIST_ID), "SoftwareModule"); + verifyThrownExceptionBy(() -> softwareModuleManagement.getMetadata(NOT_EXIST_IDL), "SoftwareModule"); + verifyThrownExceptionBy(() -> softwareModuleManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), "SoftwareModule"); } - private SoftwareModule createSoftwareModuleWithArtifacts(final SoftwareModuleType type, final String name, - final String version, final int numberArtifacts) { - + private SoftwareModule createSoftwareModuleWithArtifacts( + final SoftwareModuleType type, final String name, final String version, final int numberArtifacts) { final long countSoftwareModule = softwareModuleRepository.count(); // create SoftwareModule - SoftwareModule softwareModule = softwareModuleManagement.create(SoftwareModuleManagement.Create.builder() + SoftwareModule softwareModule = softwareModuleManagement.create(Create.builder() .type(type).name(name).version(version).description("description of artifact " + name).build()); final int artifactSize = 5 * 1024; @@ -764,8 +448,7 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { assertThat(artifactRepository.findAll()).hasSize(results.length); for (final Artifact result : results) { assertThat(result.getId()).isNotNull(); - assertThat(binaryArtifactRepository.getBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash())) - .isNotNull(); + assertThat(binaryArtifactRepository.getBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash())).isNotNull(); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleTypeManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleTypeManagementTest.java index 5470fef5b..b9f956e32 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleTypeManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleTypeManagementTest.java @@ -12,7 +12,6 @@ package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import java.util.Arrays; import java.util.List; import jakarta.validation.ConstraintViolationException; @@ -21,133 +20,75 @@ import org.assertj.core.api.Assertions; import org.eclipse.hawkbit.repository.SoftwareModuleManagement; import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement; import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement.Create; -import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; +import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement.Update; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; -import org.eclipse.hawkbit.repository.test.matcher.Expect; -import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.junit.jupiter.api.Test; /** * Feature: Component Tests - Repository
* Story: Software Module Management */ -class SoftwareModuleTypeManagementTest extends AbstractJpaIntegrationTest { +class SoftwareModuleTypeManagementTest extends AbstractRepositoryManagementTest { - /** - * Verifies that management get access reacts as specfied on calls for non existing entities by means - * of Optional not present. - */ @Test - @ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class) }) - void nonExistingEntityAccessReturnsNotPresent() { - - assertThat(softwareModuleTypeManagement.find(NOT_EXIST_IDL)).isNotPresent(); + void failIfReferNotExistingEntity() { assertThat(softwareModuleTypeManagement.findByKey(NOT_EXIST_ID)).isNotPresent(); assertThat(softwareModuleTypeManagement.findByName(NOT_EXIST_ID)).isNotPresent(); - } - - /** - * Verifies that management queries react as specfied on calls for non existing entities - * by means of throwing EntityNotFoundException. - */ - @Test - @ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class) }) - void entityQueriesReferringToNotExistingEntitiesThrowsException() { verifyThrownExceptionBy(() -> softwareModuleTypeManagement.delete(NOT_EXIST_IDL), "SoftwareModuleType"); - - verifyThrownExceptionBy( - () -> softwareModuleTypeManagement.update(SoftwareModuleTypeManagement.Update.builder().id(NOT_EXIST_IDL).build()), - "SoftwareModuleType"); + verifyThrownExceptionBy(() -> softwareModuleTypeManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), "SoftwareModuleType"); } /** - * Calling update without changing fields results in no recorded change in the repository including unchanged audit fields. - */ - @Test - void updateNothingResultsInUnchangedRepositoryForType() { - final SoftwareModuleType created = softwareModuleTypeManagement.create(Create.builder().key("test-key").name("test-name").build()); - - final SoftwareModuleType updated = softwareModuleTypeManagement.update(SoftwareModuleTypeManagement.Update.builder().id(created.getId()) - .build()); - - assertThat(updated.getOptLockRevision()) - .as("Expected version number of updated entitity to be equal to created version") - .isEqualTo(created.getOptLockRevision()); - } - - /** - * Calling update for changed fields results in change in the repository. - */ - @Test - void updateSoftwareModuleTypeFieldsToNewValue() { - final SoftwareModuleType created = softwareModuleTypeManagement - .create(Create.builder().key("test-key").name("test-name").build()); - - final SoftwareModuleType updated = softwareModuleTypeManagement.update( - SoftwareModuleTypeManagement.Update.builder().id(created.getId()).description("changed").colour("changed").build()); - - assertThat(updated.getOptLockRevision()).as("Expected version number of updated entities is") - .isEqualTo(created.getOptLockRevision() + 1); - assertThat(updated.getDescription()).as("Updated description is").isEqualTo("changed"); - assertThat(updated.getColour()).as("Updated vendor is").isEqualTo("changed"); - } - - /** - * Create Software Module Types call fails when called for existing entities. - */ - @Test - void createModuleTypesCallFailsForExistingTypes() { - final List created = Arrays.asList( - Create.builder().key("test-key").name("test-name").build(), - Create.builder().key("test-key2").name("test-name2").build()); - softwareModuleTypeManagement.create(created); - assertThatExceptionOfType(EntityAlreadyExistsException.class) - .as("should not have worked as module type already exists") - .isThrownBy(() -> softwareModuleTypeManagement.create(created)); - } - - /** - * Tests the successfull deletion of software module types. Both unused (hard delete) and used ones (soft delete). + * Tests the successful deletion of software module types. Both unused (hard delete) and used ones (soft delete). */ @Test void deleteAssignedAndUnassignedSoftwareModuleTypes() { - Assertions.assertThat(softwareModuleTypeManagement.findAll(PAGE)).hasSize(3).contains(osType, runtimeType, appType); + Assertions. assertThat(softwareModuleTypeManagement.findAll(PAGE)).hasSize(3) + .contains(osType, runtimeType, appType); - SoftwareModuleType type = softwareModuleTypeManagement - .create(Create.builder().key("bundle").name("OSGi Bundle").build()); + SoftwareModuleType type = softwareModuleTypeManagement.create(Create.builder().key("bundle").name("OSGi Bundle").build()); - Assertions.assertThat(softwareModuleTypeManagement.findAll(PAGE)).hasSize(4).contains(osType, runtimeType, appType, type); + Assertions. assertThat(softwareModuleTypeManagement.findAll(PAGE)) + .hasSize(4) + .contains(osType, runtimeType, appType, type); // delete unassigned softwareModuleTypeManagement.delete(type.getId()); - Assertions.assertThat(softwareModuleTypeManagement.findAll(PAGE)).hasSize(3).contains(osType, runtimeType, appType); - Assertions.assertThat(softwareModuleTypeRepository.findAll()).hasSize(3).contains(osType, runtimeType, appType); + Assertions. assertThat(softwareModuleTypeManagement.findAll(PAGE)) + .hasSize(3) + .contains(osType, runtimeType, appType); + Assertions. assertThat(softwareModuleTypeRepository.findAll()).hasSize(3).contains(osType, runtimeType, appType); - type = softwareModuleTypeManagement - .create(Create.builder().key("bundle2").name("OSGi Bundle2").build()); + type = softwareModuleTypeManagement.create(Create.builder().key("bundle2").name("OSGi Bundle2").build()); - Assertions.assertThat(softwareModuleTypeManagement.findAll(PAGE)).hasSize(4).contains(osType, runtimeType, appType, type); + Assertions. assertThat(softwareModuleTypeManagement.findAll(PAGE)) + .hasSize(4) + .contains(osType, runtimeType, appType, type); - softwareModuleManagement - .create(SoftwareModuleManagement.Create.builder().type(type).name("Test SM").version("1.0").build()); + softwareModuleManagement.create(SoftwareModuleManagement.Create.builder().type(type).name("Test SM").version("1.0").build()); // delete assigned softwareModuleTypeManagement.delete(type.getId()); - Assertions.assertThat(softwareModuleTypeManagement.findAll(PAGE)).hasSize(3).contains(osType, runtimeType, appType); - Assertions.assertThat(softwareModuleTypeManagement.findByRsql("name==*", PAGE)).hasSize(3).contains(osType, runtimeType, appType); + Assertions. assertThat(softwareModuleTypeManagement.findAll(PAGE)) + .hasSize(3) + .contains(osType, runtimeType, appType); + Assertions. assertThat(softwareModuleTypeManagement.findByRsql("name==*", PAGE)) + .hasSize(3) + .contains(osType, runtimeType, appType); assertThat(softwareModuleTypeManagement.count()).isEqualTo(3); - assertThat(softwareModuleTypeRepository.findAll()).hasSize(4).contains((JpaSoftwareModuleType) osType, - (JpaSoftwareModuleType) runtimeType, (JpaSoftwareModuleType) appType, - softwareModuleTypeRepository.findById(type.getId()).orElseThrow()); + assertThat(softwareModuleTypeRepository.findAll()) + .hasSize(4) + .contains( + (JpaSoftwareModuleType) osType, + (JpaSoftwareModuleType) runtimeType, + (JpaSoftwareModuleType) appType, + softwareModuleTypeRepository.findById(type.getId()).orElseThrow()); } - /** - * Checks that software module typeis found based on given name. - */ + @SuppressWarnings("unchecked") @Test void findSoftwareModuleTypeByName() { testdataFactory.createSoftwareModuleOs(); @@ -156,59 +97,18 @@ class SoftwareModuleTypeManagementTest extends AbstractJpaIntegrationTest { softwareModuleTypeManagement .create(Create.builder().key("thetype2").name("anothername").build()); - Assertions.assertThat(((SoftwareModuleTypeManagement) softwareModuleTypeManagement).findByName("thename")) + Assertions.assertThat(((SoftwareModuleTypeManagement) softwareModuleTypeManagement).findByName("thename")) .as("Type with given name").contains(found); } - /** - * Verifies that it is not possible to create a type that alrady exists. - */ - @Test - void createSoftwareModuleTypeFailsWithExistingEntity() { - final Create create = Create.builder().key("thetype").name("thename").build(); - softwareModuleTypeManagement.create(create); - assertThatExceptionOfType(EntityAlreadyExistsException.class) - .as("should not have worked as module type already exists") - .isThrownBy(() -> softwareModuleTypeManagement.create(create)); - } - - /** - * Verifies that it is not possible to create a list of types where one already exists. - */ - @Test - void createSoftwareModuleTypesFailsWithExistingEntity() { - softwareModuleTypeManagement.create(Create.builder().key("thetype").name("thename").build()); - final List creates = List.of( - Create.builder().key("thetype").name("thename").build(), - Create.builder().key("anothertype").name("anothername").build()); - assertThatExceptionOfType(EntityAlreadyExistsException.class) - .as("should not have worked as module type already exists") - .isThrownBy(() -> softwareModuleTypeManagement.create(creates)); - } - /** * Verifies that the creation of a softwareModuleType is failing because of invalid max assignment */ @Test void createSoftwareModuleTypesFailsWithInvalidMaxAssignment() { - final Create create = - Create.builder().key("type").name("name").maxAssignments(0).build(); + final Create create = Create.builder().key("type").name("name").maxAssignments(0).build(); assertThatExceptionOfType(ConstraintViolationException.class) .as("should not have worked as max assignment is invalid. Should be greater than 0") .isThrownBy(() -> softwareModuleTypeManagement.create(create)); } - - /** - * Verifies that multiple types are created as requested. - */ - @Test - void createMultipleSoftwareModuleTypes() { - final List created = softwareModuleTypeManagement - .create(List.of( - Create.builder().key("thetype").name("thename").build(), - Create.builder().key("thetype2").name("thename2").build())); - - assertThat(created).as("Number of created types").hasSize(2); - assertThat(softwareModuleTypeManagement.count()).as("Number of types in repository").isEqualTo(5); - } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetFilterQueryManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetFilterQueryManagementTest.java index 5ad0ac508..ba73a1d0f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetFilterQueryManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetFilterQueryManagementTest.java @@ -11,14 +11,16 @@ package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.eclipse.hawkbit.repository.model.TargetFilterQuery.ALLOWED_AUTO_ASSIGN_ACTION_TYPES; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.function.Supplier; import jakarta.validation.ConstraintViolationException; @@ -30,22 +32,21 @@ import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement.AutoAssignDistributionSetUpdate; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement.Create; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement.Update; +import org.eclipse.hawkbit.repository.TargetFilterQueryManagement.UpdateCreate; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; -import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryCreatedEvent; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.DeletedException; -import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.test.matcher.Expect; @@ -61,64 +62,31 @@ import org.springframework.data.domain.Slice; * Feature: Component Tests - Repository
* Story: Target Filter Query Management */ -class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { +class TargetFilterQueryManagementTest extends AbstractRepositoryManagementTest { - /** - * Verifies that management get access reacts as specfied on calls for non existing entities by means of Optional not present. - */ - @Test - @ExpectEvents({ @Expect(type = TargetCreatedEvent.class) }) - void nonExistingEntityAccessReturnsNotPresent() { - assertThat(targetFilterQueryManagement.find(NOT_EXIST_IDL)).isNotPresent(); + @SuppressWarnings("unchecked") + @Override + protected O forType(final Class type) { + if (DistributionSet.class.isAssignableFrom(type)) { + // need to be completed in order to be assigned + incrementEvents(DistributionSet.class, EventType.CREATED); + incrementEvents(SoftwareModule.class, EventType.CREATED, 3); + return (O) testdataFactory.createDistributionSet(); + } else if (type == Action.ActionType.class) { + return (O) ALLOWED_AUTO_ASSIGN_ACTION_TYPES.toArray()[RND.nextInt(ALLOWED_AUTO_ASSIGN_ACTION_TYPES.size())]; + } + + return super.forType(type); } - /** - * Verifies that management queries react as specfied on calls for non existing entities by means of throwing EntityNotFoundException. - */ - @Test - @ExpectEvents({ - @Expect(type = DistributionSetCreatedEvent.class, count = 1), - @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), - @Expect(type = TargetFilterQueryCreatedEvent.class, count = 1) }) - void entityQueriesReferringToNotExistingEntitiesThrowsException() { - final DistributionSet set = testdataFactory.createDistributionSet(); - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create( - Create.builder().name("test filter").query("name==PendingTargets001").build()); + @Override + protected Object builderParameterValue(final Method builderSetter) { + // encrypted true is not supported + if (builderSetter.getDeclaringClass() == UpdateCreate.UpdateCreateBuilder.class && "query".equals(builderSetter.getName())) { + return "controllerId==PendingTargets001"; + } - verifyThrownExceptionBy(() -> targetFilterQueryManagement.delete(NOT_EXIST_IDL), "TargetFilterQuery"); - - verifyThrownExceptionBy( - () -> targetFilterQueryManagement.findByAutoAssignDSAndRsql(NOT_EXIST_IDL, "name==*", PAGE), - "DistributionSet"); - - verifyThrownExceptionBy( - () -> targetFilterQueryManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), - "TargetFilterQuery"); - - verifyThrownExceptionBy(() -> targetFilterQueryManagement.updateAutoAssignDS( - new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()).ds(NOT_EXIST_IDL)), - "DistributionSet"); - - verifyThrownExceptionBy( - () -> targetFilterQueryManagement.updateAutoAssignDS( - new AutoAssignDistributionSetUpdate(NOT_EXIST_IDL).ds(set.getId())), - "TargetFilterQuery"); - - verifyThrownExceptionBy(() -> targetFilterQueryManagement.updateAutoAssignDS( - new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()).ds(NOT_EXIST_IDL)), - "DistributionSet"); - } - - /** - * Test creation of target filter query. - */ - @Test - void createTargetFilterQuery() { - final String filterName = "new target filter"; - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement - .create(Create.builder().name(filterName).query("name==PendingTargets001").build()); - assertEquals(targetFilterQuery, targetFilterQueryManagement.find(targetFilterQuery.getId()).get(), - "Retrieved newly created custom target filter"); + return super.builderParameterValue(builderSetter); } /** @@ -126,7 +94,6 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { */ @Test void createTargetFilterQueryThatExceedsQuota() { - // create targets final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment(); testdataFactory.createTargets(maxTargets + 1, "target%s"); @@ -134,16 +101,13 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { // creation is supposed to work as there is no distribution set final Create targetFilterQueryCreate = Create.builder() - .name("testfilter").autoAssignDistributionSet(set).query("name==target*").build(); + .name("testFilter").autoAssignDistributionSet(set).query("name==target*").build(); assertThatExceptionOfType(AssignmentQuotaExceededException.class) .isThrownBy(() -> targetFilterQueryManagement.create(targetFilterQueryCreate)); } - /** - * Test searching a target filter query. - */ @Test - void searchTargetFilterQuery() { + void findByRsqlTargetFilterQuery() { final String filterName = "targetFilterQueryName"; final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement .create(Create.builder().name(filterName).query("name==PendingTargets001").build()); @@ -152,33 +116,8 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { final List results = targetFilterQueryManagement .findByRsql("name==" + filterName, PageRequest.of(0, 10)).getContent(); - assertEquals(1, results.size(), "Search result should have 1 result"); - assertEquals(targetFilterQuery, results.get(0), "Retrieved newly created custom target filter"); - } - - /** - * Test searching a target filter query with an invalid filter. - */ - @Test - void searchTargetFilterQueryInvalidField() { - final PageRequest pageRequest = PageRequest.of(0, 10); - Assertions.assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class) - .isThrownBy(() -> targetFilterQueryManagement.findByRsql("unknownField==testValue", pageRequest)); - } - - /** - * Checks if the EntityAlreadyExistsException is thrown if a targetFilterQuery with the same name are created more than once. - */ - @Test - void createDuplicateTargetFilterQuery() { - final String filterName = "new target filter duplicate"; - final Create targetFilterQueryCreate = Create.builder().name(filterName).query("name==PendingTargets001").build(); - - targetFilterQueryManagement.create(targetFilterQueryCreate); - - assertThatExceptionOfType(EntityAlreadyExistsException.class) - .as("should not have worked as query already exists") - .isThrownBy(() -> targetFilterQueryManagement.create(targetFilterQueryCreate)); + assertThat(results).hasSize(1); + assertThat(results.get(0)).isEqualTo(targetFilterQuery); } /** @@ -195,29 +134,14 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { "Returns null as the target filter is deleted"); } - /** - * Test update of a target filter query. - */ - @Test - void updateTargetFilterQuery() { - final String filterName = "target_filter_01"; - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement - .create(Create.builder().name(filterName).query("name==PendingTargets001").build()); - - final String newQuery = "name==PendingTargets002"; - targetFilterQueryManagement.update(Update.builder().id(targetFilterQuery.getId()).query(newQuery).build()); - assertEquals(newQuery, targetFilterQueryManagement.find(targetFilterQuery.getId()).get().getQuery(), - "Returns updated target filter query"); - } - /** * Test assigning a distribution set for auto assignment with different action types */ @Test void assignDistributionSet() { final String filterName = "target_filter_02"; - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement - .create(Create.builder().name(filterName).query("name==PendingTargets001").build()); + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create( + Create.builder().name(filterName).query("name==PendingTargets001").build()); final DistributionSet distributionSet = testdataFactory.createDistributionSet(); verifyAutoAssignmentWithDefaultActionType(targetFilterQuery, distributionSet); @@ -228,48 +152,6 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { verifyAutoAssignmentWithSoftDeletedDs(targetFilterQuery); } - /** - * Assigns a distribution set to an existing filter query and verifies that the quota 'max targets per auto assignment' is enforced. - */ - @Test - void assignDistributionSetToTargetFilterQueryThatExceedsQuota() { - // create targets - final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment(); - testdataFactory.createTargets(maxTargets + 1, "target%s"); - final DistributionSet distributionSet = testdataFactory.createDistributionSet(); - - // creation is supposed to work as there is no distribution set - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement - .create(Create.builder().name("testfilter").query("name==target*").build()); - - // assigning a distribution set is supposed to fail as the query - // addresses too many targets - final AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate = - new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()).ds(distributionSet.getId()); - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(autoAssignDistributionSetUpdate)); - } - - /** - * Updates an existing filter query with a query string that addresses too many targets. - */ - @Test - void updateTargetFilterQueryWithQueryThatExceedsQuota() { - // create targets - final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment(); - testdataFactory.createTargets(maxTargets + 1, "target%s"); - final DistributionSet set = testdataFactory.createDistributionSet(); - - // creation is supposed to work as the query does not exceed the quota - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create( - Create.builder().name("testfilter").autoAssignDistributionSet(set).query("name==foo").build()); - - // update with a query string that addresses too many targets - final Update targetFilterQueryUpdate = Update.builder().id(targetFilterQuery.getId()).query("name==target*").build(); - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> targetFilterQueryManagement.update(targetFilterQueryUpdate)); - } - /** * Test removing distribution set while it has a relation to a target filter query */ @@ -286,14 +168,12 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { implicitLock(distributionSet); // Check if target filter query is there - TargetFilterQuery tfq = targetFilterQueryManagement.find(targetFilterQuery.getId()).get(); - assertEquals(distributionSet, tfq.getAutoAssignDistributionSet(), "Returns correct distribution set"); - assertEquals(ActionType.FORCED, tfq.getAutoAssignActionType(), "Return correct action type"); + verifyAutoAssignDsAndActionType(targetFilterQuery.getId(), distributionSet, ActionType.FORCED); distributionSetManagement.delete(distributionSet.getId()); // Check if auto assign distribution set is null - tfq = targetFilterQueryManagement.find(targetFilterQuery.getId()).get(); + final TargetFilterQuery tfq = targetFilterQueryManagement.get(targetFilterQuery.getId()); assertNotNull(tfq, "Returns target filter query"); assertNull(tfq.getAutoAssignDistributionSet(), "Returns distribution set as null"); assertNull(tfq.getAutoAssignActionType(), "Returns action type as null"); @@ -319,17 +199,15 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { implicitLock(distributionSet); // Check if target filter query is there with the distribution set - TargetFilterQuery tfq = targetFilterQueryManagement.find(filterId).get(); - assertEquals(distributionSet, tfq.getAutoAssignDistributionSet(), "Returns correct distribution set"); - assertEquals(ActionType.FORCED, tfq.getAutoAssignActionType(), "Return correct action type"); + verifyAutoAssignDsAndActionType(filterId, distributionSet, ActionType.FORCED); distributionSetManagement.delete(distributionSet.getId()); // Check if distribution set is still in the database with deleted flag - assertTrue(distributionSetManagement.find(distributionSet.getId()).get().isDeleted(), "Distribution set should be deleted"); + assertTrue(distributionSetManagement.get(distributionSet.getId()).isDeleted(), "Distribution set should be deleted"); // Check if auto assign distribution set is null - tfq = targetFilterQueryManagement.find(filterId).get(); + final TargetFilterQuery tfq = targetFilterQueryManagement.get(filterId); assertNotNull(tfq, "Returns target filter query"); assertNull(tfq.getAutoAssignDistributionSet(), "Returns distribution set as null"); assertNull(tfq.getAutoAssignActionType(), "Returns action type as null"); @@ -416,9 +294,6 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { .isNotNull(); } - /** - * Weight is validated and saved to the Filter. - */ @Test void weightValidatedAndSaved() { enableMultiAssignments(); @@ -431,7 +306,7 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { final Long filterId = targetFilterQueryManagement.create(Create.builder().name("a") .query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(Action.WEIGHT_MAX).build()).getId(); - assertThat(targetFilterQueryManagement.find(filterId).get().getAutoAssignWeight()).contains(Action.WEIGHT_MAX); + assertThat(targetFilterQueryManagement.get(filterId).getAutoAssignWeight()).contains(Action.WEIGHT_MAX); final AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate = new AutoAssignDistributionSetUpdate(filterId) .ds(ds.getId()).weight(Action.WEIGHT_MAX + 1); @@ -445,80 +320,14 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { new AutoAssignDistributionSetUpdate(filterId).ds(ds.getId()).weight(Action.WEIGHT_MAX)); targetFilterQueryManagement.updateAutoAssignDS( new AutoAssignDistributionSetUpdate(filterId).ds(ds.getId()).weight(Action.WEIGHT_MIN)); - assertThat(targetFilterQueryManagement.find(filterId).get().getAutoAssignWeight()).contains(Action.WEIGHT_MIN); - } - - /** - * Verifies that an exception is thrown when trying to create a target filter with an invalidated distribution set. - */ - @Test - void createTargetFilterWithInvalidDistributionSet() { - final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet(); - - final Create targetFilterQueryCreate = Create.builder() - .name("createTargetFilterWithInvalidDistributionSet").query("name==*").autoAssignDistributionSet(distributionSet) - .build(); - assertThatExceptionOfType(InvalidDistributionSetException.class) - .as("Invalid distributionSet should throw an exception") - .isThrownBy(() -> targetFilterQueryManagement.create(targetFilterQueryCreate)); - } - - /** - * Verifies that an exception is thrown when trying to create a target filter with an incomplete distribution set. - */ - @Test - void createTargetFilterWithIncompleteDistributionSet() { - final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet(); - - final Create targetFilterQueryCreate = Create.builder() - .name("createTargetFilterWithIncompleteDistributionSet").query("name==*") - .autoAssignDistributionSet(distributionSet) - .build(); - assertThatExceptionOfType(IncompleteDistributionSetException.class) - .as("Incomplete distributionSet should throw an exception") - .isThrownBy(() -> targetFilterQueryManagement.create(targetFilterQueryCreate)); - } - - /** - * Verifies that an exception is thrown when trying to update a target filter with an invalidated distribution set. - */ - @Test - void updateAutoAssignDsWithInvalidDistributionSet() { - final DistributionSet distributionSet = testdataFactory.createDistributionSet(); - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create(Create.builder() - .name("updateAutoAssignDsWithInvalidDistributionSet").query("name==*").autoAssignDistributionSet(distributionSet).build()); - final DistributionSet invalidDistributionSet = testdataFactory.createAndInvalidateDistributionSet(); - - final AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate = new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()) - .ds(invalidDistributionSet.getId()); - assertThatExceptionOfType(InvalidDistributionSetException.class) - .as("Invalid distributionSet should throw an exception") - .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(autoAssignDistributionSetUpdate)); - } - - /** - * Verifies that an exception is thrown when trying to update a target filter with an incomplete distribution set. - */ - @Test - void updateAutoAssignDsWithIncompleteDistributionSet() { - final DistributionSet distributionSet = testdataFactory.createDistributionSet(); - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create( - Create.builder().name("updateAutoAssignDsWithIncompleteDistributionSet") - .query("name==*").autoAssignDistributionSet(distributionSet).build()); - final DistributionSet incompleteDistributionSet = testdataFactory.createIncompleteDistributionSet(); - - final AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate = new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()) - .ds(incompleteDistributionSet.getId()); - assertThatExceptionOfType(IncompleteDistributionSetException.class) - .as("Incomplete distributionSet should throw an exception") - .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(autoAssignDistributionSetUpdate)); + assertThat(targetFilterQueryManagement.get(filterId).getAutoAssignWeight()).contains(Action.WEIGHT_MIN); } /** * Tests the auto assign action type mapping. */ @Test - void testAutoAssignActionTypeConvert() { + void autoAssignActionTypeConvert() { for (final ActionType actionType : ActionType.values()) { final Supplier create = () -> targetFilterQueryManagement.create( @@ -537,8 +346,147 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { final JpaTargetFilterQuery jpaTargetFilterQuery = (JpaTargetFilterQuery) targetFilterQueryManagement.create( Create.builder().name("testAutoAssignActionTypeConvert").query("name==*").build()); - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> - jpaTargetFilterQuery.setAutoAssignActionType(ActionType.TIMEFORCED)); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> jpaTargetFilterQuery.setAutoAssignActionType(ActionType.TIMEFORCED)); + } + + @Test + @ExpectEvents({ + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = TargetFilterQueryCreatedEvent.class, count = 1) }) + void failIfReferNotExistingEntity() { + final DistributionSet set = testdataFactory.createDistributionSet(); + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create( + Create.builder().name("test filter").query("name==PendingTargets001").build()); + + verifyThrownExceptionBy(() -> targetFilterQueryManagement.delete(NOT_EXIST_IDL), "TargetFilterQuery"); + + verifyThrownExceptionBy( + () -> targetFilterQueryManagement.findByAutoAssignDSAndRsql(NOT_EXIST_IDL, "name==*", PAGE), "DistributionSet"); + + verifyThrownExceptionBy( + () -> targetFilterQueryManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), "TargetFilterQuery"); + + verifyThrownExceptionBy( + () -> targetFilterQueryManagement.updateAutoAssignDS( + new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()).ds(NOT_EXIST_IDL)), + "DistributionSet"); + + verifyThrownExceptionBy( + () -> targetFilterQueryManagement.updateAutoAssignDS(new AutoAssignDistributionSetUpdate(NOT_EXIST_IDL).ds(set.getId())), + "TargetFilterQuery"); + + verifyThrownExceptionBy( + () -> targetFilterQueryManagement.updateAutoAssignDS( + new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()).ds(NOT_EXIST_IDL)), + "DistributionSet"); + } + + /** + * Test searching a target filter query with an invalid filter. + */ + @Test + void failToFindTargetFilterQueryByInvalidField() { + final PageRequest pageRequest = PageRequest.of(0, 10); + Assertions.assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class) + .isThrownBy(() -> targetFilterQueryManagement.findByRsql("unknownField==testValue", pageRequest)); + } + + @Test + void failToCreateTargetFilterWithInvalidDistributionSet() { + final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet(); + + final Create targetFilterQueryCreate = Create.builder() + .name("createTargetFilterWithInvalidDistributionSet").query("name==*").autoAssignDistributionSet(distributionSet) + .build(); + assertThatExceptionOfType(InvalidDistributionSetException.class) + .as("Invalid distributionSet should throw an exception") + .isThrownBy(() -> targetFilterQueryManagement.create(targetFilterQueryCreate)); + } + + @Test + void failToCreateTargetFilterWithIncompleteDistributionSet() { + final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet(); + + final Create targetFilterQueryCreate = Create.builder() + .name("createTargetFilterWithIncompleteDistributionSet").query("name==*") + .autoAssignDistributionSet(distributionSet) + .build(); + assertThatExceptionOfType(IncompleteDistributionSetException.class) + .as("Incomplete distributionSet should throw an exception") + .isThrownBy(() -> targetFilterQueryManagement.create(targetFilterQueryCreate)); + } + + @Test + void failToUpdateAutoAssignDsWithInvalidDistributionSet() { + final DistributionSet distributionSet = testdataFactory.createDistributionSet(); + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create(Create.builder() + .name("updateAutoAssignDsWithInvalidDistributionSet").query("name==*").autoAssignDistributionSet(distributionSet).build()); + final DistributionSet invalidDistributionSet = testdataFactory.createAndInvalidateDistributionSet(); + + final AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate = new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()) + .ds(invalidDistributionSet.getId()); + assertThatExceptionOfType(InvalidDistributionSetException.class) + .as("Invalid distributionSet should throw an exception") + .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(autoAssignDistributionSetUpdate)); + } + + @Test + void failToUpdateAutoAssignDsWithIncompleteDistributionSet() { + final DistributionSet distributionSet = testdataFactory.createDistributionSet(); + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create( + Create.builder().name("updateAutoAssignDsWithIncompleteDistributionSet") + .query("name==*").autoAssignDistributionSet(distributionSet).build()); + final DistributionSet incompleteDistributionSet = testdataFactory.createIncompleteDistributionSet(); + + final AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate = new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()) + .ds(incompleteDistributionSet.getId()); + assertThatExceptionOfType(IncompleteDistributionSetException.class) + .as("Incomplete distributionSet should throw an exception") + .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(autoAssignDistributionSetUpdate)); + } + + /** + * Assigns a distribution set to an existing filter query and verifies that the quota 'max targets per auto assignment' is enforced. + */ + @Test + void failToAssignDistributionSetToTargetFilterQueryOnQuotaExceeded() { + // create targets + final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment(); + testdataFactory.createTargets(maxTargets + 1, "target%s"); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(); + + // creation is supposed to work as there is no distribution set + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create( + Create.builder().name("testFilter").query("name==target*").build()); + + // assigning a distribution set is supposed to fail as the query + // addresses too many targets + final AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate = + new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()).ds(distributionSet.getId()); + assertThatExceptionOfType(AssignmentQuotaExceededException.class) + .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(autoAssignDistributionSetUpdate)); + } + + /** + * Updates an existing filter query with a query string that addresses too many targets. + */ + @Test + void failToUpdateTargetFilterQueryOnQuotaExceeded() { + // create targets + final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment(); + testdataFactory.createTargets(maxTargets + 1, "target%s"); + final DistributionSet set = testdataFactory.createDistributionSet(); + + // creation is supposed to work as the query does not exceed the quota + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create( + Create.builder().name("testfilter").autoAssignDistributionSet(set).query("name==foo").build()); + + // update with a query string that addresses too many targets + final Update targetFilterQueryUpdate = Update.builder().id(targetFilterQuery.getId()).query("name==target*").build(); + assertThatExceptionOfType(AssignmentQuotaExceededException.class) + .isThrownBy(() -> targetFilterQueryManagement.update(targetFilterQueryUpdate)); } private void verifyAutoAssignmentWithDefaultActionType(final TargetFilterQuery targetFilterQuery, final DistributionSet distributionSet) { @@ -626,9 +574,8 @@ class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { } private void verifyAutoAssignDsAndActionType(final Long filterId, final DistributionSet distributionSet, final ActionType actionType) { - final TargetFilterQuery tfq = targetFilterQueryManagement.find(filterId).get(); - - assertEquals(distributionSet, tfq.getAutoAssignDistributionSet(), "Returns correct distribution set"); - assertEquals(actionType, tfq.getAutoAssignActionType(), "Return correct action type"); + final TargetFilterQuery tfq = targetFilterQueryManagement.get(filterId); + assertThat(tfq.getAutoAssignDistributionSet()).isEqualTo(distributionSet); + assertThat(tfq.getAutoAssignActionType()).isEqualTo(actionType); } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java index 9eb836173..a7433e39a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java @@ -16,8 +16,6 @@ import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuil import java.net.URI; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -34,17 +32,15 @@ import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.SetJoin; import jakarta.validation.ConstraintViolationException; -import org.assertj.core.api.Assertions; -import org.awaitility.Awaitility; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpRole; import org.eclipse.hawkbit.repository.Identifiable; +import org.eclipse.hawkbit.repository.MetadataSupport; import org.eclipse.hawkbit.repository.TargetManagement.Create; import org.eclipse.hawkbit.repository.TargetManagement.Update; import org.eclipse.hawkbit.repository.TargetTagManagement; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; -import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent; import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent; @@ -56,12 +52,10 @@ 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.TargetTypeCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; -import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaNamedEntity_; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; @@ -87,7 +81,6 @@ import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch; import org.eclipse.hawkbit.repository.test.util.WithUser; import org.junit.jupiter.api.Test; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.domain.Specification; @@ -95,78 +88,48 @@ import org.springframework.data.jpa.domain.Specification; * Feature: Component Tests - Repository
* Story: Target Management */ -class TargetManagementTest extends AbstractJpaIntegrationTest { +class TargetManagementTest extends AbstractRepositoryManagementWithMetadataTest { private static final String WHITESPACE_ERROR = "target with whitespaces in controller id should not be created"; - /** - * Verifies that management get access react as specified on calls for non existing entities by means - * of Optional not present. - */ - @Test - @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) }) - void nonExistingEntityAccessReturnsNotPresent() { - final Target target = testdataFactory.createTarget(); - assertThat(targetManagement.getByControllerId(NOT_EXIST_ID)).isNotPresent(); - assertThat(targetManagement.find(NOT_EXIST_IDL)).isNotPresent(); - assertThat(targetManagement.getMetadata(target.getControllerId()).get(NOT_EXIST_ID)).isNull(); + @Override + protected void setMetadataSupport() { + metadataSupport = new MetadataSupport<>() { + + @Override + public void createMetadata(final Long id, final String key, final String value) { + targetManagement.createMetadata(toControllerId(id), key, value); + } + + @SuppressWarnings("unchecked") + @Override + public void createMetadata(final Long id, final Map metadata) { + targetManagement.createMetadata(toControllerId(id), (Map) metadata); + } + + @Override + public String getMetadata(final Long id, final String key) { + return targetManagement.getMetadata(targetManagement.get(id).getControllerId(), key); + } + + @Override + public Map getMetadata(final Long id) { + return targetManagement.getMetadata(toControllerId(id)); + } + + @Override + public void deleteMetadata(final Long id, final String key) { + targetManagement.deleteMetadata(toControllerId(id), key); + } + + private String toControllerId(final Long id) { + return targetManagement.get(id).getControllerId(); + } + }; } /** - * Verifies that management queries react as specified on calls for non existing entities - * by means of throwing EntityNotFoundException. - */ - @Test - @ExpectEvents({ - @Expect(type = TargetCreatedEvent.class, count = 1), - @Expect(type = TargetTagCreatedEvent.class, count = 1) }) - void entityQueriesReferringToNotExistingEntitiesThrowsException() { - final TargetTag tag = targetTagManagement.create(TargetTagManagement.Create.builder().name("A").build()); - final Target target = testdataFactory.createTarget(); - - verifyThrownExceptionBy( - () -> targetManagement.assignTag(Collections.singletonList(target.getControllerId()), NOT_EXIST_IDL), "TargetTag"); - verifyThrownExceptionBy(() -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), "Target"); - - verifyThrownExceptionBy(() -> targetManagement.findByTag(NOT_EXIST_IDL, PAGE), "TargetTag"); - verifyThrownExceptionBy(() -> targetManagement.findByRsqlAndTag("name==*", NOT_EXIST_IDL, PAGE), "TargetTag"); - - verifyThrownExceptionBy(() -> targetManagement.countByRsqlAndNonDsAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*"), - "DistributionSet"); - - verifyThrownExceptionBy(() -> targetManagement.deleteByControllerId(NOT_EXIST_ID), "Target"); - verifyThrownExceptionBy(() -> targetManagement.delete(Collections.singletonList(NOT_EXIST_IDL)), "Target"); - - verifyThrownExceptionBy( - () -> targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*", PAGE), - "DistributionSet"); - verifyThrownExceptionBy(() -> targetManagement.findByInRolloutGroupWithoutAction(NOT_EXIST_IDL, PAGE), "RolloutGroup"); - verifyThrownExceptionBy(() -> targetManagement.findByAssignedDistributionSet(NOT_EXIST_IDL, PAGE), "DistributionSet"); - verifyThrownExceptionBy( - () -> targetManagement.findByAssignedDistributionSetAndRsql(NOT_EXIST_IDL, "name==*", PAGE), "DistributionSet"); - - verifyThrownExceptionBy(() -> targetManagement.findByInstalledDistributionSet(NOT_EXIST_IDL, PAGE), "DistributionSet"); - verifyThrownExceptionBy( - () -> targetManagement.findByInstalledDistributionSetAndRsql(NOT_EXIST_IDL, "name==*", PAGE), "DistributionSet"); - - verifyThrownExceptionBy(() -> targetManagement - .assignTag(Collections.singletonList(target.getControllerId()), Long.parseLong(NOT_EXIST_ID)), "TargetTag"); - verifyThrownExceptionBy( - () -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), "Target"); - - verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(NOT_EXIST_ID), tag.getId()), "Target"); - verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(target.getControllerId()), NOT_EXIST_IDL), "TargetTag"); - verifyThrownExceptionBy(() -> targetManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), "Target"); - - verifyThrownExceptionBy(() -> targetManagement.createMetadata(NOT_EXIST_ID, Map.of("123", "123")), "Target"); - verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(NOT_EXIST_ID, "xxx"), "Target"); - verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(target.getControllerId(), NOT_EXIST_ID), "Target"); - verifyThrownExceptionBy(() -> targetManagement.getMetadata(NOT_EXIST_ID).get("xxx"), "Target"); - verifyThrownExceptionBy(() -> targetManagement.createMetadata(NOT_EXIST_ID, "xxx", "xxx"), "Target"); - } - - /** - * Ensures that retrieving the target security is only permitted with the necessary permissions. + * Ensures that retrieving the target security token is only permitted with the necessary permissions. */ @Test @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) }) @@ -199,41 +162,9 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { assertThat(securityTokenWithTargetAdminPermission).isNotNull(); assertThat(securityTokenWithTenantAdminPermission).isNotNull(); assertThat(securityTokenAsSystemCode).isNotNull(); - assertThat(securityTokenWithoutPermission).isNull(); } - /** - * Verify that a target with same controller ID than another device cannot be created. - */ - @Test - @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) }) - void createTargetThatViolatesUniqueConstraintFails() { - final Create targetCreate = Create.builder().controllerId("123").build(); - targetManagement.create(targetCreate); - assertThatExceptionOfType(EntityAlreadyExistsException.class).isThrownBy(() -> targetManagement.create(targetCreate)); - } - - /** - * Verify that a target with with invalid properties cannot be created or updated - */ - @Test - @ExpectEvents({ - @Expect(type = TargetCreatedEvent.class, count = 1), - @Expect(type = TargetUpdatedEvent.class) }) - void createAndUpdateTargetWithInvalidFields() { - final Target target = testdataFactory.createTarget(); - - createTargetWithInvalidControllerId(); - createAndUpdateTargetWithInvalidDescription(target); - createAndUpdateTargetWithInvalidName(target); - createAndUpdateTargetWithInvalidSecurityToken(target); - createAndUpdateTargetWithInvalidAddress(target); - } - - /** - * Ensures that targets can assigned and unassigned to a target tag. Not exists target will be ignored for the assignment. - */ @Test @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 4), @@ -253,7 +184,8 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { assignedTargets.forEach(target -> assertThat(getTargetTags(target.getControllerId())).hasSize(1)); final TargetTag findTargetTag = targetTagManagement.find(targetTag.getId()).orElseThrow(IllegalStateException::new); - assertThat(assignedTargets).as("Assigned targets are wrong") + assertThat(assignedTargets) + .as("Assigned targets are wrong") .hasSize(targetManagement.findByTag(targetTag.getId(), PAGE).getNumberOfElements()); final Target unAssignTarget = targetManagement.unassignTag(List.of("targetId123"), findTargetTag.getId()).get(0); @@ -268,36 +200,6 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { } - /** - * Ensures that targets can deleted e.g. test all cascades - */ - @Test - @ExpectEvents({ - @Expect(type = TargetCreatedEvent.class, count = 12), - @Expect(type = TargetDeletedEvent.class, count = 12), - @Expect(type = TargetUpdatedEvent.class, count = 6) }) - void deleteAndCreateTargets() { - Target target = targetManagement.create(Create.builder().controllerId("targetId123").build()); - assertThat(targetManagement.count()).as("target count is wrong").isEqualTo(1); - targetManagement.delete(Collections.singletonList(target.getId())); - assertThat(targetManagement.count()).as("target count is wrong").isZero(); - - target = createTargetWithAttributes("4711"); - assertThat(targetManagement.count()).as("target count is wrong").isEqualTo(1); - targetManagement.delete(Collections.singletonList(target.getId())); - assertThat(targetManagement.count()).as("target count is wrong").isZero(); - - final List targets = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - target = targetManagement.create(Create.builder().controllerId("" + i).build()); - targets.add(target.getId()); - targets.add(createTargetWithAttributes("" + (i * i + 1000)).getId()); - } - assertThat(targetManagement.count()).as("target count is wrong").isEqualTo(10); - targetManagement.delete(targets); - assertThat(targetManagement.count()).as("target count is wrong").isZero(); - } - /** * Finds a target by given ID and checks if all data is in the response (including the data defined as lazy). */ @@ -344,135 +246,6 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { assertThat(installedDs).as("Installed ds is wrong").isEqualTo(testDs1); } - /** - * Checks if the EntityAlreadyExistsException is thrown if the targets with the same controller ID are created twice. - */ - @Test - @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 5) }) - void createMultipleTargetsDuplicate() { - testdataFactory.createTargets(5, "mySimpleTargs", "my simple targets"); - assertThatExceptionOfType(EntityAlreadyExistsException.class) - .as("Targets already exists") - .isThrownBy(() -> testdataFactory.createTargets(5, "mySimpleTargs", "my simple targets")); - } - - /** - * Checks if the EntityAlreadyExistsException is thrown if a single target with the same controller ID are created twice. - */ - @Test - @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) }) - void createTargetDuplicate() { - final Create targetCreate = Create.builder().controllerId("4711").build(); - targetManagement.create(targetCreate); - assertThatExceptionOfType(EntityAlreadyExistsException.class) - .as("Target already exists") - .isThrownBy(() -> targetManagement.create(targetCreate)); - } - - /** - * Creates and updates a target and verifies the changes in the repository. - */ - @Test - @WithUser(allSpPermissions = true) - @ExpectEvents({ - @Expect(type = TargetCreatedEvent.class, count = 1), - @Expect(type = TargetUpdatedEvent.class, count = 1) }) - void singleTargetIsInsertedIntoRepo() { - final String myCtrlID = "myCtrlID"; - - Target savedTarget = testdataFactory.createTarget(myCtrlID); - assertThat(savedTarget).as("The target should not be null").isNotNull(); - final long createdAt = savedTarget.getCreatedAt(); - long modifiedAt = savedTarget.getLastModifiedAt(); - - assertThat(createdAt).as("CreatedAt compared with modifiedAt").isEqualTo(modifiedAt); - - Awaitility.await().until(() -> System.currentTimeMillis() > createdAt + 1); - - savedTarget = targetManagement.update(Update.builder().id(savedTarget.getId()).description("changed description").build()); - assertThat(createdAt).as("CreatedAt compared with saved modifiedAt").isNotEqualTo(savedTarget.getLastModifiedAt()); - assertThat(modifiedAt).as("ModifiedAt compared with saved modifiedAt").isNotEqualTo(savedTarget.getLastModifiedAt()); - modifiedAt = savedTarget.getLastModifiedAt(); - - final Target foundTarget = targetManagement.getByControllerId(savedTarget.getControllerId()) - .orElseThrow(IllegalStateException::new); - assertThat(foundTarget).as("The target should not be null").isNotNull(); - assertThat(myCtrlID).as("ControllerId compared with saved controllerId").isEqualTo(foundTarget.getControllerId()); - assertThat(savedTarget).as("Target compared with saved target").isEqualTo(foundTarget); - assertThat(createdAt).as("CreatedAt compared with saved createdAt").isEqualTo(foundTarget.getCreatedAt()); - assertThat(modifiedAt).as("LastModifiedAt compared with saved lastModifiedAt").isEqualTo(foundTarget.getLastModifiedAt()); - } - - /** - * Create multiple targets as bulk operation and delete them in bulk. - */ - @Test - @WithUser(allSpPermissions = true) - @ExpectEvents({ - @Expect(type = TargetCreatedEvent.class, count = 101), - @Expect(type = TargetUpdatedEvent.class, count = 100), - @Expect(type = TargetDeletedEvent.class, count = 51) }) - void bulkTargetCreationAndDelete() { - final String myCtrlID = "myCtrlID"; - List firstList = testdataFactory.createTargets(100, myCtrlID, "first description"); - - final Target extra = testdataFactory.createTarget("myCtrlID-00081XX"); - - final Iterable allFound = targetRepository.findAll(); - - assertThat(Long.valueOf(firstList.size())).as("List size of targets") - .isEqualTo(firstList.spliterator().getExactSizeIfKnown()); - assertThat(Long.valueOf(firstList.size() + 1)).as("LastModifiedAt compared with saved lastModifiedAt") - .isEqualTo(allFound.spliterator().getExactSizeIfKnown()); - - // change the objects and save to again to trigger a change on - // lastModifiedAt - firstList = firstList.stream() - .map(t -> targetManagement.update( - Update.builder().id(t.getId()).name(t.getName().concat("\tchanged")).build())) - .toList(); - - // verify that all entries are found - _founds: - for (final Target foundTarget : allFound) { - for (final Target changedTarget : firstList) { - if (changedTarget.getControllerId().equals(foundTarget.getControllerId())) { - assertThat(changedTarget.getDescription()) - .as("Description of changed target compared with description saved target") - .isEqualTo(foundTarget.getDescription()); - assertThat(changedTarget.getName()).as("Name of changed target starts with name of saved target") - .startsWith(foundTarget.getName()); - assertThat(changedTarget.getName()).as("Name of changed target ends with 'changed'") - .endsWith("changed"); - assertThat(changedTarget.getCreatedAt()).as("CreatedAt compared with saved createdAt") - .isEqualTo(foundTarget.getCreatedAt()); - assertThat(changedTarget.getLastModifiedAt()).as("LastModifiedAt compared with saved createdAt") - .isNotEqualTo(changedTarget.getCreatedAt()); - continue _founds; - } - } - - if (!foundTarget.getControllerId().equals(extra.getControllerId())) { - fail("The controllerId of the found target is not equal to the controllerId of the saved target"); - } - } - - targetManagement.deleteByControllerId(extra.getControllerId()); - - final int numberToDelete = 50; - final Collection targetsToDelete = firstList.subList(0, numberToDelete); - final Target[] deletedTargets = toArray(targetsToDelete, Target.class); - final List targetsIdsToDelete = targetsToDelete.stream().map(Target::getId).toList(); - - targetManagement.delete(targetsIdsToDelete); - - final List targetsLeft = targetManagement.findAll(PageRequest.of(0, 200)).getContent(); - assertThat(firstList.spliterator().getExactSizeIfKnown() - numberToDelete).as("Size of split list") - .isEqualTo(targetsLeft.spliterator().getExactSizeIfKnown()); - - Assertions. assertThat(targetsLeft).as("Not all undeleted found").doesNotContain(deletedTargets); - } - /** * Tests the assignment of tags to the a single target. */ @@ -581,14 +354,14 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { } /** - * Tests the unassigment of tags to multiple targets. + * Tests the un-assigment of tags to multiple targets. */ @Test @ExpectEvents({ @Expect(type = TargetTagCreatedEvent.class, count = 3), @Expect(type = TargetCreatedEvent.class, count = 109), @Expect(type = TargetUpdatedEvent.class, count = 227) }) - void targetTagBulkUnassignments() { + void targetTagBulkUnAssignments() { final TargetTag targTagA = targetTagManagement.create(TargetTagManagement.Create.builder().name("Targ-A-Tag").build()); final TargetTag targTagB = targetTagManagement.create(TargetTagManagement.Create.builder().name("Targ-B-Tag").build()); final TargetTag targTagC = targetTagManagement.create(TargetTagManagement.Create.builder().name("Targ-C-Tag").build()); @@ -641,9 +414,6 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { checkTargetHasNotTags(targABCs, targTagC); } - /** - * Test that NO TAG functionality which gives all targets with no tag assigned. - */ @Test @ExpectEvents({ @Expect(type = TargetTagCreatedEvent.class, count = 1), @@ -705,30 +475,11 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { .isEqualTo(27L); } - /** - * Verify that the find all targets by ids method contains the entities that we are looking for - */ - @Test - @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 12) }) - void verifyFindTargetAllById() { - final List searchIds = Arrays.asList(testdataFactory.createTarget("target-4").getId(), - testdataFactory.createTarget("target-5").getId(), testdataFactory.createTarget("target-6").getId()); - for (int i = 0; i < 9; i++) { - testdataFactory.createTarget("test" + i); - } - - final List foundDs = targetManagement.get(searchIds); - assertThat(foundDs).hasSize(3); - - final List collect = foundDs.stream().map(Target::getId).toList(); - assertThat(collect).containsAll(searchIds); - } - /** * Verify that the flag for requesting controller attributes is set correctly. */ @Test - void verifyRequestControllerAttributes() { + void requestControllerAttributes() { final String knownControllerId = "KnownControllerId"; final Target target = createTargetWithAttributes(knownControllerId); assertThat(target.isRequestControllerAttributes()).isFalse(); @@ -738,112 +489,6 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { assertThat(updated.isRequestControllerAttributes()).isTrue(); } - /** - * Verifies the enforcement of the metadata quota per target. - */ - @Test - void createMetadataUntilQuotaIsExceeded() { - // add meta-data one by one - final Target target1 = testdataFactory.createTarget("target1"); - final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerTarget(); - for (int i = 0; i < maxMetaData; ++i) { - insertMetadata("k" + i, "v" + i, target1); - } - - // quota exceeded - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> insertMetadata("k" + maxMetaData, "v" + maxMetaData, target1)); - - // add multiple meta data entries at once - final Target target2 = testdataFactory.createTarget("target2"); - final Map metaData2 = new HashMap<>(); - for (int i = 0; i < maxMetaData + 1; i++) { - metaData2.put("k" + i, "v" + i); - } - // verify quota is exceeded - final String target2ControllerId = target2.getControllerId(); - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> targetManagement.createMetadata(target2ControllerId, metaData2)); - - // add some meta data entries - final Target target3 = testdataFactory.createTarget("target3"); - final int firstHalf = maxMetaData / 2; - for (int i = 0; i < firstHalf; i++) { - insertMetadata("k" + i, "v" + i, target3); - } - // add too many data entries - final int secondHalf = maxMetaData - firstHalf; - final Map metaData3 = new HashMap<>(); - for (int i = 0; i < secondHalf + 1; i++) { - metaData3.put("kk" + i, "vv" + i); - } - // verify quota is exceeded - final String target3ControllerId = target3.getControllerId(); - assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> targetManagement.createMetadata(target3ControllerId, metaData3)); - } - - /** - * Queries and loads the metadata related to a given target. - */ - @Test - void getMetadata() { - // create targets - assertThat(targetManagement.getMetadata(testdataFactory.createTarget("target0").getControllerId())).isEmpty(); - assertThat(targetManagement.getMetadata(createTargetWithMetadata("target1", 10).getControllerId())).hasSize(10); - assertThat(targetManagement.getMetadata(createTargetWithMetadata("target2", 8).getControllerId())).hasSize(8); - } - - /** - * Checks that metadata for a target can be updated. - */ - @Test - @WithUser(allSpPermissions = true) - void createMetadata() { - final String knownKey = "myKnownKey"; - final String knownValue = "myKnownValue"; - final String knownUpdateValue = "myNewUpdatedValue"; - - // create a target - final Target target = testdataFactory.createTarget("target1"); - // initial opt lock revision must be zero - assertThat(target.getOptLockRevision()).isEqualTo(1); - - // create target meta data entry - insertMetadata(knownKey, knownValue, target); - - final Target changedLockRevisionTarget = targetManagement.find(target.getId()).orElseThrow(NoSuchElementException::new); - assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(2); - - // update the target metadata - targetManagement.createMetadata(target.getControllerId(), knownKey, knownUpdateValue); - // we are updating the target meta-data so also modifying the target so opt lock revision must be three - final Target changedLockRevisionTarget2 = targetManagement.find(target.getId()).orElseThrow(NoSuchElementException::new); - assertThat(changedLockRevisionTarget2.getOptLockRevision()).isEqualTo(3); - assertThat(changedLockRevisionTarget2.getLastModifiedAt()).isPositive(); - - // verify updated meta data contains the updated value - assertThat(targetManagement.getMetadata(target.getControllerId())).containsEntry(knownKey, knownUpdateValue); - } - - /** - * Queries and loads the metadata related to a given target. - */ - @Test - void deleteMetadata() { - final String knownKey = "myKnownKey"; - final String knownValue = "myKnownValue"; - - // create target - final String controllerId = createTargetWithMetadata("target1", 10).getControllerId(); - targetManagement.createMetadata(controllerId, Map.of(knownKey, knownValue)); - - targetManagement.deleteMetadata(controllerId, knownKey); - // already removed - assertThatExceptionOfType(EntityNotFoundException.class) - .isThrownBy(() -> targetManagement.deleteMetadata(controllerId, knownKey)); - } - /** * Checks that target type for a target can be created, updated and unassigned. */ @@ -939,36 +584,6 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { checkTargetsHaveType(typeBTargets, typeB); } - /** - * Checks that target type is not assigned to target if invalid. - */ - @Test - @WithUser(allSpPermissions = true) - void assignInvalidTargetTypeToTarget() { - // create a target - final Target target = testdataFactory.createTarget("target1", "testtarget"); - // initial opt lock revision must be one - final Optional targetFound = targetRepository.findById(target.getId()); - assertThat(targetFound).isPresent(); - assertThat(targetFound.get().getOptLockRevision()).isEqualTo(1); - assertThat(targetFound.get().getTargetType()).isNull(); - - // assign target type to target - final String controllerId = targetFound.get().getControllerId(); - assertThatExceptionOfType(ConstraintViolationException.class) - .as("target type with id=null cannot be assigned") - .isThrownBy(() -> targetManagement.assignType(controllerId, null)); - - assertThatExceptionOfType(EntityNotFoundException.class) - .as("target type with id that does not exists cannot be assigned") - .isThrownBy(() -> targetManagement.assignType(controllerId, 114L)); - - // opt lock revision is not changed - final Optional targetFound1 = targetRepository.findById(target.getId()); - assertThat(targetFound1).isPresent(); - assertThat(targetFound1.get().getOptLockRevision()).isEqualTo(1); - } - /** * Checks that target type can be unassigned from target. */ @@ -1055,8 +670,8 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { final DistributionSet ds = testdataFactory.createDistributionSet(); final String filter = "metadata.key1==target1-value1"; - assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target.getControllerId(), - ds.getId(), filter)).isTrue(); + assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable( + target.getControllerId(), ds.getId(), filter)).isTrue(); } /** @@ -1084,8 +699,8 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { assignDistributionSet(ds2, target); final String filter = "name==*"; - assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target.getControllerId(), - ds1.getId(), filter)).isFalse(); + assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable( + target.getControllerId(), ds1.getId(), filter)).isFalse(); } /** @@ -1097,8 +712,8 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { final Target target = testdataFactory.createTarget("target", "target", type); final DistributionSet ds = testdataFactory.createDistributionSet(); - assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target.getControllerId(), - ds.getId(), "name==*")).isFalse(); + assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable( + target.getControllerId(), ds.getId(), "name==*")).isFalse(); } /** @@ -1195,7 +810,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { * Test update status convert */ @Test - void testUpdateStatusConvert() { + void updateStatusConvert() { final long id = testdataFactory.createTarget().getId(); for (final TargetUpdateStatus status : TargetUpdateStatus.values()) { final JpaTarget target = targetRepository.findById(id).orElseThrow(() -> new IllegalStateException("Target not found")); @@ -1206,6 +821,108 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { } } + @Test + @ExpectEvents({ + @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetTagCreatedEvent.class, count = 1) }) + void failIfReferNotExistingEntity() { + final TargetTag tag = targetTagManagement.create(TargetTagManagement.Create.builder().name("A").build()); + final Target target = testdataFactory.createTarget(); + + verifyThrownExceptionBy( + () -> targetManagement.assignTag(Collections.singletonList(target.getControllerId()), NOT_EXIST_IDL), "TargetTag"); + verifyThrownExceptionBy(() -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), "Target"); + + verifyThrownExceptionBy(() -> targetManagement.findByTag(NOT_EXIST_IDL, PAGE), "TargetTag"); + verifyThrownExceptionBy(() -> targetManagement.findByRsqlAndTag("name==*", NOT_EXIST_IDL, PAGE), "TargetTag"); + + verifyThrownExceptionBy( + () -> targetManagement.countByRsqlAndNonDsAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*"), + "DistributionSet"); + + verifyThrownExceptionBy(() -> targetManagement.deleteByControllerId(NOT_EXIST_ID), "Target"); + verifyThrownExceptionBy(() -> targetManagement.delete(Collections.singletonList(NOT_EXIST_IDL)), "Target"); + + verifyThrownExceptionBy( + () -> targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*", PAGE), + "DistributionSet"); + verifyThrownExceptionBy(() -> targetManagement.findByInRolloutGroupWithoutAction(NOT_EXIST_IDL, PAGE), "RolloutGroup"); + verifyThrownExceptionBy(() -> targetManagement.findByAssignedDistributionSet(NOT_EXIST_IDL, PAGE), "DistributionSet"); + verifyThrownExceptionBy( + () -> targetManagement.findByAssignedDistributionSetAndRsql(NOT_EXIST_IDL, "name==*", PAGE), "DistributionSet"); + + verifyThrownExceptionBy(() -> targetManagement.findByInstalledDistributionSet(NOT_EXIST_IDL, PAGE), "DistributionSet"); + verifyThrownExceptionBy( + () -> targetManagement.findByInstalledDistributionSetAndRsql(NOT_EXIST_IDL, "name==*", PAGE), "DistributionSet"); + + verifyThrownExceptionBy(() -> targetManagement + .assignTag(Collections.singletonList(target.getControllerId()), Long.parseLong(NOT_EXIST_ID)), "TargetTag"); + verifyThrownExceptionBy( + () -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), "Target"); + + verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(NOT_EXIST_ID), tag.getId()), "Target"); + verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(target.getControllerId()), NOT_EXIST_IDL), "TargetTag"); + verifyThrownExceptionBy(() -> targetManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), "Target"); + + verifyThrownExceptionBy(() -> targetManagement.createMetadata(NOT_EXIST_ID, Map.of("123", "123")), "Target"); + verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(NOT_EXIST_ID, "xxx"), "Target"); + verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(target.getControllerId(), NOT_EXIST_ID), "Target"); + verifyThrownExceptionBy(() -> targetManagement.getMetadata(NOT_EXIST_ID).get("xxx"), "Target"); + verifyThrownExceptionBy(() -> targetManagement.createMetadata(NOT_EXIST_ID, "xxx", "xxx"), "Target"); + } + + /** + * Verify that a target with same controller ID than another device cannot be created. + */ + @Test + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) }) + void failToCreateTargetWithSameControllerId() { + final Create targetCreate = Create.builder().controllerId("123").build(); + targetManagement.create(targetCreate); + assertThatExceptionOfType(EntityAlreadyExistsException.class).isThrownBy(() -> targetManagement.create(targetCreate)); + } + + @Test + @ExpectEvents({ + @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class) }) + void failToCreateAndUpdateTargetWithInvalidFields() { + final Target target = testdataFactory.createTarget(); + + createTargetWithInvalidControllerId(); + createAndUpdateTargetWithInvalidDescription(target); + createAndUpdateTargetWithInvalidName(target); + createAndUpdateTargetWithInvalidSecurityToken(target); + createAndUpdateTargetWithInvalidAddress(target); + } + + @Test + @WithUser(allSpPermissions = true) + void failToAssignInvalidTargetTypeToTarget() { + // create a target + final Target target = testdataFactory.createTarget("target1", "testtarget"); + // initial opt lock revision must be one + final Optional targetFound = targetRepository.findById(target.getId()); + assertThat(targetFound).isPresent(); + assertThat(targetFound.get().getOptLockRevision()).isEqualTo(1); + assertThat(targetFound.get().getTargetType()).isNull(); + + // assign target type to target + final String controllerId = targetFound.get().getControllerId(); + assertThatExceptionOfType(ConstraintViolationException.class) + .as("target type with id=null cannot be assigned") + .isThrownBy(() -> targetManagement.assignType(controllerId, null)); + + assertThatExceptionOfType(EntityNotFoundException.class) + .as("target type with id that does not exists cannot be assigned") + .isThrownBy(() -> targetManagement.assignType(controllerId, 114L)); + + // opt lock revision is not changed + final Optional targetFound1 = targetRepository.findById(target.getId()); + assertThat(targetFound1).isPresent(); + assertThat(targetFound1.get().getOptLockRevision()).isEqualTo(1); + } + private void createAndUpdateTargetWithInvalidDescription(final Target target) { final Create targetCreateTooLong = Create.builder().controllerId("a").description(randomString(513)).build(); assertThatExceptionOfType(ConstraintViolationException.class) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTagManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTagManagementTest.java index fad9c4f70..7996d3833 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTagManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTagManagementTest.java @@ -14,11 +14,9 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Random; import jakarta.validation.ConstraintViolationException; @@ -26,12 +24,9 @@ import org.eclipse.hawkbit.repository.TargetTagManagement; import org.eclipse.hawkbit.repository.TargetTagManagement.Create; import org.eclipse.hawkbit.repository.TargetTagManagement.Update; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTagUpdatedEvent; -import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetTagUpdatedEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; -import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.Target; @@ -47,15 +42,44 @@ import org.springframework.data.domain.Pageable; * Feature: Component Tests - Repository
* Story: Target Tag Management */ -class TargetTagManagementTest extends AbstractJpaIntegrationTest { +class TargetTagManagementTest extends AbstractRepositoryManagementTest { - private static final Random RND = new Random(); - - /** - * Verifies that tagging of set containing missing DS throws meaningful and correct exception. - */ @Test - void failOnMissingDs() { + void assignAndUnassignTargetTags() { + final List groupA = testdataFactory.createTargets(20); + final List groupB = testdataFactory.createTargets(20, "groupb", "groupb"); + + final TargetTag tag = targetTagManagement.create(Create.builder().name("tag1").description("tagdesc1").build()); + + // toggle A only -> A is now assigned + List result = assignTag(groupA, tag); + assertThat(result) + .containsAll(targetManagement.getByControllerId(groupA.stream().map(Target::getControllerId).toList())) + .size().isEqualTo(20); + assertThat(targetManagement.findByTag(tag.getId(), Pageable.unpaged()).getContent().stream().map(Target::getControllerId).sorted() + .toList()) + .isEqualTo(groupA.stream().map(Target::getControllerId).sorted().toList()); + + // toggle A+B -> A is still assigned and B is assigned as well + final Collection groupAB = concat(groupA, groupB); + result = assignTag(groupAB, tag); + assertThat(result) + .containsAll(targetManagement.getByControllerId(groupAB.stream().map(Target::getControllerId).toList())) + .size().isEqualTo(40); + assertThat(targetManagement.findByTag(tag.getId(), Pageable.unpaged()).getContent().stream().map(Target::getControllerId).sorted() + .toList()) + .isEqualTo(groupAB.stream().map(Target::getControllerId).sorted().toList()); + + // toggle A+B -> both unassigned + result = unassignTag(groupAB, tag); + assertThat(result) + .containsAll(targetManagement.getByControllerId(groupAB.stream().map(Target::getControllerId).toList())) + .size().isEqualTo(40); + assertThat(targetManagement.findByTag(tag.getId(), Pageable.unpaged()).getContent()).isEmpty(); + } + + @Test + void failToAssignOnMissingTargetTag() { final Collection group = testdataFactory.createTargets(5).stream() .map(Target::getControllerId) .toList(); @@ -84,162 +108,29 @@ class TargetTagManagementTest extends AbstractJpaIntegrationTest { }); } - /** - * Verifies that management get access reacts as specfied on calls for non existing entities by means - * of Optional not present. - */ - @Test - @ExpectEvents({ @Expect(type = TargetCreatedEvent.class) }) - void nonExistingEntityAccessReturnsNotPresent() { - assertThat(targetTagManagement.find(NOT_EXIST_IDL)).isNotPresent(); - } - - /** - * Verifies that management queries react as specfied on calls for non existing entities - * by means of throwing EntityNotFoundException. - */ @Test @ExpectEvents({ @Expect(type = DistributionSetTagUpdatedEvent.class), @Expect(type = TargetTagUpdatedEvent.class) }) - void entityQueriesReferringToNotExistingEntitiesThrowsException() { + void failIfReferNotExistingEntity() { verifyThrownExceptionBy(() -> targetTagManagement.delete(NOT_EXIST_IDL), "TargetTag"); verifyThrownExceptionBy(() -> targetTagManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), "TargetTag"); verifyThrownExceptionBy(() -> getTargetTags(NOT_EXIST_ID), "Target"); } - /** - * Verify that a tag with with invalid properties cannot be created or updated - */ @Test - void createAndUpdateTagWithInvalidFields() { + void failToCreateAndUpdateTagWithInvalidFields() { final TargetTag tag = targetTagManagement.create(Create.builder().name("tag1").description("tagdesc1").build()); createAndUpdateTagWithInvalidDescription(tag); createAndUpdateTagWithInvalidColour(tag); createAndUpdateTagWithInvalidName(tag); } - /** - * Verifies assign/unassign. - */ - @Test - void assignAndUnassignTargetTags() { - final List groupA = testdataFactory.createTargets(20); - final List groupB = testdataFactory.createTargets(20, "groupb", "groupb"); - - final TargetTag tag = targetTagManagement.create(Create.builder().name("tag1").description("tagdesc1").build()); - - // toggle A only -> A is now assigned - List result = assignTag(groupA, tag); - assertThat(result) - .containsAll( - targetManagement.getByControllerId(groupA.stream().map(Target::getControllerId).toList())) - .size().isEqualTo(20); - assertThat(targetManagement.findByTag(tag.getId(), Pageable.unpaged()).getContent().stream().map(Target::getControllerId).sorted() - .toList()) - .isEqualTo(groupA.stream().map(Target::getControllerId).sorted().toList()); - - // toggle A+B -> A is still assigned and B is assigned as well - final Collection groupAB = concat(groupA, groupB); - result = assignTag(groupAB, tag); - assertThat(result) - .containsAll( - targetManagement.getByControllerId(groupAB.stream().map(Target::getControllerId).toList())) - .size().isEqualTo(40); - assertThat(targetManagement.findByTag(tag.getId(), Pageable.unpaged()).getContent().stream().map(Target::getControllerId).sorted() - .toList()) - .isEqualTo(groupAB.stream().map(Target::getControllerId).sorted().toList()); - - // toggle A+B -> both unassigned - result = unassignTag(groupAB, tag); - assertThat(result) - .containsAll(targetManagement.getByControllerId(groupAB.stream().map(Target::getControllerId).toList())) - .size().isEqualTo(40); - assertThat(targetManagement.findByTag(tag.getId(), Pageable.unpaged()).getContent()).isEmpty(); - } - - /** - * Ensures that all tags are retrieved through repository. - */ - @Test - void findAllTargetTags() { - final List tags = createTargetsWithTags(); - - assertThat(targetTagRepository.findAll()).isEqualTo(targetTagRepository.findAll()).isEqualTo(tags) - .as("Wrong tag size").hasSize(20); - } - - /** - * Ensures that a created tag is persisted in the repository as defined. - */ - @Test - void createTargetTag() { - final Tag tag = targetTagManagement.create(Create.builder().name("k1").description("k2").colour("colour").build()); - assertThat(targetTagManagement.find(tag.getId()).orElseThrow().getColour()).as("wrong tag found").isEqualTo("colour"); - } - - /** - * Ensures that a deleted tag is removed from the repository as defined. - */ - @Test - void deleteTargetTags() { - // create test data - final Iterable tags = createTargetsWithTags(); - final TargetTag toDelete = tags.iterator().next(); - - for (final Target target : targetRepository.findAll()) { - assertThat(getTargetTags(target.getControllerId())).contains(toDelete); - } - - // delete - targetTagManagement.delete(toDelete.getId()); - - // check - for (final Target target : targetRepository.findAll()) { - assertThat(getTargetTags(target.getControllerId())) - .doesNotContain(toDelete); - } - assertThat(targetTagRepository.findById(toDelete.getId())).as("No tag should be found").isNotPresent(); - assertThat(targetTagRepository.findAll()).as("Wrong target tag size").hasSize(19); - } - - /** - * Tests the name update of a target tag. - */ - @Test - void updateTargetTag() { - final List tags = createTargetsWithTags(); - - // change data - final TargetTag savedAssigned = tags.iterator().next(); - - // persist - targetTagManagement.update(Update.builder().id(savedAssigned.getId()).name("test123").build()); - - // check data - assertThat(targetTagRepository.findAll()).as("Wrong target tag size").hasSize(tags.size()); - assertThat(targetTagRepository.findById(savedAssigned.getId()).orElseThrow().getName()).as("wrong target tag is saved") - .isEqualTo("test123"); - assertThat(targetTagRepository.findById(savedAssigned.getId()).orElseThrow().getOptLockRevision()) - .as("wrong target tag is saved") - .isEqualTo(2); - } - - /** - * Ensures that a tag cannot be created if one exists already with that name (expects EntityAlreadyExistsException). - */ - @Test - void failedDuplicateTargetTagNameException() { - final Create tagCreate = Create.builder().name("A").build(); - targetTagManagement.create(tagCreate); - assertThatExceptionOfType(EntityAlreadyExistsException.class).isThrownBy(() -> targetTagManagement.create(tagCreate)); - } - /** * Ensures that a tag cannot be updated to a name that already exists on another tag (expects EntityAlreadyExistsException). */ @Test - void failedDuplicateTargetTagNameExceptionAfterUpdate() { + void failToDuplicateTargetTagNameOnUpdate() { targetTagManagement.create(Create.builder().name("A").build()); final TargetTag tag = targetTagManagement.create(Create.builder().name("B").build()); @@ -306,20 +197,4 @@ class TargetTagManagementTest extends AbstractJpaIntegrationTest { .as("tag with too short name should not be updated") .isThrownBy(() -> targetTagManagement.update(tagUpdateEmpty)); } - - @SafeVarargs - private Collection concat(final Collection... targets) { - final List result = new ArrayList<>(); - Arrays.asList(targets).forEach(result::addAll); - return result; - } - - private List createTargetsWithTags() { - final List targets = testdataFactory.createTargets(20); - final Iterable tags = testdataFactory.createTargetTags(20, ""); - - tags.forEach(tag -> assignTag(targets, tag)); - - return targetTagRepository.findAll(); - } -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTypeManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTypeManagementTest.java index 0970c7495..2d3d2657b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTypeManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTypeManagementTest.java @@ -20,10 +20,8 @@ import jakarta.validation.ConstraintViolationException; import org.eclipse.hawkbit.repository.TargetTypeManagement.Create; import org.eclipse.hawkbit.repository.TargetTypeManagement.Update; -import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeUpdatedEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.NamedEntity; @@ -37,77 +35,16 @@ import org.junit.jupiter.api.Test; * Feature: Component Tests - Repository
* Story: Target Type Management */ -class TargetTypeManagementTest extends AbstractJpaIntegrationTest { - - /** - * Verifies that management get access react as specified on calls for non existing entities by means - * of Optional not present. - */ - @Test - @ExpectEvents({ @Expect(type = TargetTypeCreatedEvent.class) }) - void nonExistingEntityAccessReturnsNotPresent() { - assertThat(targetTypeManagement.find(NOT_EXIST_IDL)).isNotPresent(); - assertThat(targetTypeManagement.getByName(NOT_EXIST_ID)).isNotPresent(); - } - - /** - * Verifies that management queries react as specified on calls for non existing entities - * by means of throwing EntityNotFoundException. - */ - @Test - @ExpectEvents({ @Expect(type = TargetTypeUpdatedEvent.class) }) - void entityQueriesReferringToNotExistingEntitiesThrowsException() { - verifyThrownExceptionBy(() -> targetTypeManagement.delete(NOT_EXIST_IDL), "TargetType"); - verifyThrownExceptionBy(() -> targetTypeManagement.update(Update.builder().id(NOT_EXIST_IDL).build()),"TargetType"); - } - - /** - * Verify that a target type with invalid properties cannot be created or updated - */ - @Test - void createAndUpdateTargetTypeWithInvalidFields() { - final TargetType targetType = targetTypeManagement - .create(Create.builder().name("targettype1").description("targettypedes1").key("targettype1.key").build()); - - createAndUpdateTargetTypeWithInvalidDescription(targetType); - createAndUpdateTargetTypeWithInvalidColour(targetType); - createTargetTypeWithInvalidKey(); - createAndUpdateTargetTypeWithInvalidName(targetType); - } - - void createAndUpdateTargetTypeWithInvalidDescription(final TargetType targetType) { - final Create targetTypeCreateTooLong = Create.builder() - .name("a").description(randomString(TargetType.DESCRIPTION_MAX_SIZE + 1)).build(); - assertThatExceptionOfType(ConstraintViolationException.class) - .as("targetType with too long description should not be created") - .isThrownBy(() -> targetTypeManagement.create(targetTypeCreateTooLong)); - - final Create targetTypeCreateInvalidHtml = Create.builder().name("a").description(INVALID_TEXT_HTML).build(); - assertThatExceptionOfType(ConstraintViolationException.class) - .as("targetType with invalid description should not be created") - .isThrownBy(() -> targetTypeManagement.create(targetTypeCreateInvalidHtml)); - - final Update targetTypeUpdateTooLong = Update.builder().id(targetType.getId()) - .description(randomString(TargetType.DESCRIPTION_MAX_SIZE + 1)).build(); - assertThatExceptionOfType(ConstraintViolationException.class) - .as("targetType with too long description should not be updated") - .isThrownBy(() -> targetTypeManagement.update(targetTypeUpdateTooLong)); - - final Update targetTypeUpdateInvalidHtml = Update.builder().id(targetType.getId()).description(INVALID_TEXT_HTML).build(); - assertThatExceptionOfType(ConstraintViolationException.class) - .as("targetType with invalid description should not be updated") - .isThrownBy(() -> targetTypeManagement.update(targetTypeUpdateInvalidHtml)); - } +class TargetTypeManagementTest extends AbstractRepositoryManagementTest { /** * Tests the successful assignment of compatible distribution set types to a target type */ @Test void assignCompatibleDistributionSetTypesToTargetType() { - final TargetType targetType = targetTypeManagement - .create(Create.builder() - .name("targettype1").description("targettypedes1").key("targettyp1.key").build()); - DistributionSetType distributionSetType = testdataFactory.findOrCreateDistributionSetType("testDst", "dst1"); + final TargetType targetType = targetTypeManagement.create( + Create.builder().name("targettype1").description("targettypedes1").key("targettyp1.key").build()); + final DistributionSetType distributionSetType = testdataFactory.findOrCreateDistributionSetType("testDst", "dst1"); targetTypeManagement.assignCompatibleDistributionSetTypes(targetType.getId(), Collections.singletonList(distributionSetType.getId())); Optional targetTypeWithDsTypes = targetTypeRepository.findById(targetType.getId()); @@ -133,86 +70,60 @@ class TargetTypeManagementTest extends AbstractJpaIntegrationTest { assertThat(targetTypeWithDsTypes1.get().getDistributionSetTypes()).isEmpty(); } - /** - * Ensures that all types are retrieved through repository. - */ @Test - void findAllTargetTypes() { - testdataFactory.createTargetTypes("targettype", 10); - assertThat(targetTypeRepository.findAll()).as("Target type size").hasSize(10); + @ExpectEvents({ @Expect(type = TargetTypeUpdatedEvent.class) }) + void failIfReferNotExistingEntity() { + verifyThrownExceptionBy(() -> targetTypeManagement.delete(NOT_EXIST_IDL), "TargetType"); + verifyThrownExceptionBy(() -> targetTypeManagement.update(Update.builder().id(NOT_EXIST_IDL).build()),"TargetType"); } /** - * Ensures that a created target type is persisted in the repository as defined. + * Verify that a target type with invalid properties cannot be created or updated */ @Test - void createTargetType() { - final String name = "targettype1"; - final String key = "targettype1.key"; - targetTypeManagement.create(Create.builder().name(name).description("targettypedes1").key(key).colour("colour1").build()); + void failToCreateAndUpdateTargetTypeWithInvalidFields() { + final TargetType targetType = targetTypeManagement + .create(Create.builder().name("targettype1").description("targettypedes1").key("targettype1.key").build()); - assertThat(findByName(name).map(JpaTargetType::getName).orElse(null)).as("type found (name)") - .isEqualTo(name); - assertThat(findByName(name).map(JpaTargetType::getDescription).orElse(null)) - .as("type found (des)").isEqualTo("targettypedes1"); - assertThat(findByName(name).map(JpaTargetType::getKey).orElse(null)).as("type found (key)") - .isEqualTo(key); - assertThat(findByName(name).map(JpaTargetType::getColour).orElse(null)) - .as("type found (colour)").isEqualTo("colour1"); - assertThat(findByKey(key).map(JpaTargetType::getName).orElse(null)).as("type found (name)") - .isEqualTo(name); - assertThat(findByKey(key).map(JpaTargetType::getDescription).orElse(null)) - .as("type found (des)").isEqualTo("targettypedes1"); - assertThat(findByKey(key).map(JpaTargetType::getKey).orElse(null)).as("type found (key)") - .isEqualTo(key); - assertThat(findByKey(key).map(JpaTargetType::getColour).orElse(null)).as("type found (colour)") - .isEqualTo("colour1"); - } - - /** - * Ensures that a deleted target type is removed from the repository as defined. - */ - @Test - void deleteTargetType() { - // create test data - final TargetType targetType = targetTypeManagement.create(Create.builder().name("targettype11").description("targettypedes11").build()); - assertThat(findByName("targettype11").get().getDescription()).as("type found").isEqualTo("targettypedes11"); - targetTypeManagement.delete(targetType.getId()); - assertThat(targetTypeRepository.findById(targetType.getId())).as("No target type should be found").isNotPresent(); - - } - - /** - * Tests the name update of a target type. - */ - @Test - void updateTargetType() { - final TargetType targetType = - targetTypeManagement.create(Create.builder().name("targettype111").description("targettypedes111").build()); - assertThat(findByName("targettype111").get().getDescription()).as("type found").isEqualTo("targettypedes111"); - targetTypeManagement.update(Update.builder().id(targetType.getId()).name("updatedtargettype111").build()); - assertThat(findByName("updatedtargettype111")).as("Updated target type should be found").isPresent(); - } - - /** - * Ensures that a target type cannot be created if one exists already with that name (expects EntityAlreadyExistsException). - */ - @Test - void failedDuplicateTargetTypeNameException() { - final Create targetTypeCreate = Create.builder().name("targettype123").build(); - targetTypeManagement.create(targetTypeCreate); - assertThrows(EntityAlreadyExistsException.class, () -> targetTypeManagement.create(targetTypeCreate)); + createAndUpdateTargetTypeWithInvalidDescription(targetType); + createAndUpdateTargetTypeWithInvalidColour(targetType); + createTargetTypeWithInvalidKey(); + createAndUpdateTargetTypeWithInvalidName(targetType); } /** * Ensures that a target type cannot be updated to a name that already exists (expects EntityAlreadyExistsException). */ @Test - void failedDuplicateTargetTypeNameExceptionAfterUpdate() { + void failToDuplicateTargetTypeNameExceptionOnUpdate() { targetTypeManagement.create(Create.builder().name("targettype1234").build()); - TargetType targetType = targetTypeManagement.create(Create.builder().name("targettype12345").build()); - assertThrows(EntityAlreadyExistsException.class, - () -> targetTypeManagement.update(Update.builder().id(targetType.getId()).name("targettype1234").build())); + final TargetType targetType = targetTypeManagement.create(Create.builder().name("targettype12345").build()); + final Update update = Update.builder().id(targetType.getId()).name("targettype1234").build(); + assertThrows(EntityAlreadyExistsException.class, () -> targetTypeManagement.update(update)); + } + + private void createAndUpdateTargetTypeWithInvalidDescription(final TargetType targetType) { + final Create targetTypeCreateTooLong = Create.builder() + .name("a").description(randomString(TargetType.DESCRIPTION_MAX_SIZE + 1)).build(); + assertThatExceptionOfType(ConstraintViolationException.class) + .as("targetType with too long description should not be created") + .isThrownBy(() -> targetTypeManagement.create(targetTypeCreateTooLong)); + + final Create targetTypeCreateInvalidHtml = Create.builder().name("a").description(INVALID_TEXT_HTML).build(); + assertThatExceptionOfType(ConstraintViolationException.class) + .as("targetType with invalid description should not be created") + .isThrownBy(() -> targetTypeManagement.create(targetTypeCreateInvalidHtml)); + + final Update targetTypeUpdateTooLong = Update.builder().id(targetType.getId()) + .description(randomString(TargetType.DESCRIPTION_MAX_SIZE + 1)).build(); + assertThatExceptionOfType(ConstraintViolationException.class) + .as("targetType with too long description should not be updated") + .isThrownBy(() -> targetTypeManagement.update(targetTypeUpdateTooLong)); + + final Update targetTypeUpdateInvalidHtml = Update.builder().id(targetType.getId()).description(INVALID_TEXT_HTML).build(); + assertThatExceptionOfType(ConstraintViolationException.class) + .as("targetType with invalid description should not be updated") + .isThrownBy(() -> targetTypeManagement.update(targetTypeUpdateInvalidHtml)); } private void createAndUpdateTargetTypeWithInvalidColour(final TargetType targetType) { @@ -279,12 +190,4 @@ class TargetTypeManagementTest extends AbstractJpaIntegrationTest { .isThrownBy(() -> targetTypeManagement.update(targetTypeUpdateEmpty)); } - - private Optional findByName(final String name) { - return targetTypeManagement.getByName(name).map(JpaTargetType.class::cast); - } - - private Optional findByKey(final String key) { - return targetTypeManagement.getByKey(key).map(JpaTargetType.class::cast); - } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/resources/jpa-test.properties b/hawkbit-repository/hawkbit-repository-jpa/src/test/resources/jpa-test.properties index 062f18d79..8b7f21fd1 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/resources/jpa-test.properties +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/resources/jpa-test.properties @@ -8,25 +8,35 @@ # SPDX-License-Identifier: EPL-2.0 # +NOISE_SUPPRESS_LEVEL=WARN + +### General logging configuration - START +## Remove noisy logs from libraries, could be set to INFO (or more detailed) when needed by switching the NOISE_SUPPRES_LEVEL +logging.level.org.springframework.integration=${NOISE_SUPPRESS_LEVEL} +logging.level.org.flywaydb=${NOISE_SUPPRESS_LEVEL} +logging.level.com.zaxxer.hikari=${NOISE_SUPPRESS_LEVEL} +logging.level.org.springframework.data.jpa.repository.query.QueryEnhancerFactory=${NOISE_SUPPRESS_LEVEL} +## Remove JpaRepositoryFactory$EclipseLinkProjectionQueryCreationListener log, could be useful for EclipseLink debugging +## see https://github.com/spring-projects/spring-data-jpa/blob/main/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +logging.level.org.springframework.data.jpa.repository.support.JpaRepositoryFactory=${NOISE_SUPPRESS_LEVEL} +logging.level.org.eclipse.hawkbit.repository.test.matcher.EventVerifier=${NOISE_SUPPRESS_LEVEL} +### General logging configuration - END + ### Debug & Monitor Eclipselink - START -logging.level.org.eclipse.persistence=ERROR +logging.level.org.eclipse.persistence=${NOISE_SUPPRESS_LEVEL} ## Uncomment to see the debug of persistence, e.g. to see the generated SQLs #logging.level.org.eclipse.persistence=DEBUG #spring.jpa.properties.eclipselink.logging.level=FINE #spring.jpa.properties.eclipselink.logging.level.sql=FINE #spring.jpa.properties.eclipselink.logging.parameters=true - ## Enable EclipseLink performance monitor (monitoring and profile) #spring.jpa.properties.eclipselink.profiler=PerformanceMonitor - ### Debug & Monitor Eclipselink - END ### Debug & Monitor Hibernate - START - ## Enable the generated SQLs logging #logging.level.org.hibernate.SQL=TRACE #logging.level.org.hibernate.stat=TRACE - ## Enable Hibernate statistics #spring.jpa.properties.hibernate.generate_statistics=true ## Disables info log messages from Hibernate statistics @@ -37,8 +47,6 @@ logging.level.org.eclipse.persistence=ERROR #logging.level.org.springframework.aop=TRACE #spring.aop.proxy-target-class=true -### Debug utility functions - END - ### Switch to MySQL or MariaDB - START #spring.jpa.database=MYSQL #spring.datasource.url=jdbc:mariadb://localhost:3306/hawkbit_test @@ -47,13 +55,13 @@ logging.level.org.eclipse.persistence=ERROR #spring.datasource.password= ### Switch to MySQL or MariaDB - END -# enable / disable case sensitiveness of the DB when playing around +## enable / disable case sensitiveness of the DB when playing around #hawkbit.rsql.caseInsensitiveDB=true hawkbit.repository.cluster.lock.ttl=1000 hawkbit.repository.cluster.lock.refreshOnRemainMS=200 hawkbit.repository.cluster.lock.refreshOnRemainPercent=10 -# reduce scheduler tic period to speed up tests +## reduce scheduler tic period to speed up tests hawkbit.repository.cluster.lock.ticPeriodMS=10 org.eclipse.hawkbit.events.remote-enabled=false \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java index 60fda2ea6..14f4fe0ee 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java @@ -25,6 +25,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -35,8 +36,14 @@ import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent; import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent; import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent; +import org.eclipse.hawkbit.repository.event.remote.RemoteIdEvent; +import org.eclipse.hawkbit.repository.event.remote.RemoteTenantAwareEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent; @@ -47,12 +54,6 @@ import org.eclipse.hawkbit.repository.event.remote.service.TargetAssignDistribut import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceEvent; -import org.eclipse.hawkbit.repository.event.remote.RemoteIdEvent; -import org.eclipse.hawkbit.repository.event.remote.RemoteTenantAwareEvent; -import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; -import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; -import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; -import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetUpdatedServiceEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationListener; @@ -70,15 +71,12 @@ public class EventVerifier extends AbstractTestExecutionListener { private EventCaptor eventCaptor; /** - * Publishes a reset counter marker event on the context to reset the - * current counted events. This allows test to prepare a setup such in - * {@code @Before} annotations which are actually counted to the executed - * test-method and maybe fire events which are not covered / recognized by - * the test-method itself and reset the counter again. + * Publishes a reset counter marker event on the context to reset the current counted events. This allows test to prepare a setup such in + * {@code @Before} annotations which are actually counted to the executed test-method and maybe fire events which are not + * covered / recognized by the test-method itself and reset the counter again. *

- * Note that this approach is only working when using a single-thread - * executor in the ApplicationEventMultiCaster, so the order of the events - * keep the same. + * Note that this approach is only working when using a single-thread executor in the ApplicationEventMultiCaster, so the order of the + * events keep the same. * * @param publisher the {@link ApplicationEventPublisher} to publish the marker event to */ @@ -88,8 +86,12 @@ public class EventVerifier extends AbstractTestExecutionListener { @Override public void beforeTestMethod(final TestContext testContext) { - final Optional expectedEvents = getExpectationsFrom(testContext.getTestMethod()); - expectedEvents.ifPresent(events -> beforeTest(testContext)); + Optional.ofNullable(testContext.getTestMethod().getAnnotation(ExpectEvents.class)) + .map(ExpectEvents::value) + .ifPresent(events -> { + eventCaptor = new EventCaptor(); + ((ConfigurableApplicationContext) testContext.getApplicationContext()).addApplicationListener(eventCaptor); + }); } @Override @@ -100,75 +102,16 @@ public class EventVerifier extends AbstractTestExecutionListener { return; } - final Optional expectedEvents = getExpectationsFrom(testContext.getTestMethod()); - try { - expectedEvents.ifPresent(this::afterTest); - } finally { - expectedEvents.ifPresent(listener -> removeEventListener(testContext)); - } - } - - private Optional getExpectationsFrom(final Method testMethod) { - final Optional expectedEvents = Optional.ofNullable(testMethod.getAnnotation(ExpectEvents.class)).map(ExpectEvents::value); - if (expectedEvents.isPresent()) { - List modifiedEvents = new ArrayList<>(Arrays.asList(expectedEvents.get())); - for (Expect event : expectedEvents.get()) { - final Class type = event.type(); - if (type.isAssignableFrom(TargetCreatedEvent.class)) { - modifiedEvents.add(toExpectServiceEvent(TargetCreatedServiceEvent.class, event.count())); - } else if (type.isAssignableFrom(TargetUpdatedEvent.class)) { - modifiedEvents.add(toExpectServiceEvent(TargetUpdatedServiceEvent.class, event.count())); - } else if (type.isAssignableFrom(TargetDeletedEvent.class)) { - modifiedEvents.add(toExpectServiceEvent(TargetDeletedServiceEvent.class, event.count())); - } else if (type.isAssignableFrom(TargetAssignDistributionSetEvent.class)) { - modifiedEvents.add(toExpectServiceEvent(TargetAssignDistributionSetServiceEvent.class, event.count())); - } else if (type.isAssignableFrom(MultiActionAssignEvent.class)) { - modifiedEvents.add(toExpectServiceEvent(MultiActionAssignServiceEvent.class, event.count())); - } else if (type.isAssignableFrom(MultiActionCancelEvent.class)) { - modifiedEvents.add(toExpectServiceEvent(MultiActionCancelServiceEvent.class, event.count())); - } else if (type.isAssignableFrom(TargetAttributesRequestedEvent.class)) { - modifiedEvents.add(toExpectServiceEvent(TargetAttributesRequestedServiceEvent.class, event.count())); - } else if (type.isAssignableFrom(CancelTargetAssignmentEvent.class)) { - modifiedEvents.add(toExpectServiceEvent(CancelTargetAssignmentServiceEvent.class, event.count())); - } else if (type.isAssignableFrom(ActionCreatedEvent.class)) { - modifiedEvents.add(toExpectServiceEvent(ActionCreatedServiceEvent.class, event.count())); - } else if (type.isAssignableFrom(ActionUpdatedEvent.class)) { - modifiedEvents.add(toExpectServiceEvent(ActionUpdatedServiceEvent.class, event.count())); - } + getExpectationsFrom(testContext.getTestMethod()).ifPresent(expectedEvents -> { + try { + verifyRightCountOfEvents(expectedEvents); + verifyAllEventsCounted(expectedEvents); + log.info("Expected events received:\n\t{}", Arrays.stream(expectedEvents).map(Object::toString).collect(Collectors.joining("\n\t"))); + } finally { + testContext.getApplicationContext().getBean(ApplicationEventMulticaster.class).removeApplicationListener(eventCaptor); + DYNAMIC_EXPECTATIONS.remove(); } - return Optional.of(modifiedEvents.toArray(new Expect[0])); - } - return Optional.empty(); - } - - private static Expect toExpectServiceEvent(final Class clazz, final int count) { - return new Expect() { - - @Override - public Class annotationType() { - return Expect.class; - } - - @Override - public Class type() { - return clazz; - } - - @Override - public int count() { - return count; - } - }; - } - - private void beforeTest(final TestContext testContext) { - eventCaptor = new EventCaptor(); - ((ConfigurableApplicationContext) testContext.getApplicationContext()).addApplicationListener(eventCaptor); - } - - private void afterTest(final Expect[] expectedEvents) { - verifyRightCountOfEvents(expectedEvents); - verifyAllEventsCounted(expectedEvents); + }); } private void verifyRightCountOfEvents(final Expect[] expectedEvents) { @@ -188,7 +131,7 @@ public class EventVerifier extends AbstractTestExecutionListener { private void verifyAllEventsCounted(final Expect[] expectedEvents) { final Set> diffSet = eventCaptor.diff(expectedEvents); if (!diffSet.isEmpty()) { - final StringBuilder failMessage = new StringBuilder("Missing event verification for "); + final StringBuilder failMessage = new StringBuilder("Missing event expectation for "); for (final Class element : diffSet) { final int count = eventCaptor.getCountFor(element); failMessage.append(element).append(" with count: ").append(count).append(" "); @@ -197,11 +140,67 @@ public class EventVerifier extends AbstractTestExecutionListener { } } - private void removeEventListener(final TestContext testContext) { - testContext.getApplicationContext().getBean(ApplicationEventMulticaster.class).removeApplicationListener(eventCaptor); + public static final ThreadLocal> DYNAMIC_EXPECTATIONS = new ThreadLocal<>(); + + private Optional getExpectationsFrom(final Method testMethod) { + return Optional.ofNullable(testMethod.getAnnotation(ExpectEvents.class)) + .map(ExpectEvents::value) + .map(expectedEvents -> { + final Supplier supplier = DYNAMIC_EXPECTATIONS.get(); + if (expectedEvents.length == 0) { + if (supplier != null) { + return supplier.get(); + } + } else { + if (supplier != null && supplier.get().length > 0) { + fail("Expectations defined - both static and dynamic - only one definition is allowed"); + } + } + return expectedEvents; + }) + .map(expectedEvents -> { + final List modifiedEvents = new ArrayList<>(Arrays.asList(expectedEvents)); + for (final Expect event : expectedEvents) { + addServiceEventIfNeeded(event, modifiedEvents); + } + return modifiedEvents.toArray(new Expect[0]); + }); } - private static class EventCaptor implements ApplicationListener { + private static void addServiceEventIfNeeded(final Expect event, final List modifiedEvents) { + final Class type = event.type(); + if (type.isAssignableFrom(TargetCreatedEvent.class)) { + modifiedEvents.add(new DynamicExpect(TargetCreatedServiceEvent.class, event.count())); + } else if (type.isAssignableFrom(TargetUpdatedEvent.class)) { + modifiedEvents.add(new DynamicExpect(TargetUpdatedServiceEvent.class, event.count())); + } else if (type.isAssignableFrom(TargetDeletedEvent.class)) { + modifiedEvents.add(new DynamicExpect(TargetDeletedServiceEvent.class, event.count())); + } else if (type.isAssignableFrom(TargetAssignDistributionSetEvent.class)) { + modifiedEvents.add(new DynamicExpect(TargetAssignDistributionSetServiceEvent.class, event.count())); + } else if (type.isAssignableFrom(MultiActionAssignEvent.class)) { + modifiedEvents.add(new DynamicExpect(MultiActionAssignServiceEvent.class, event.count())); + } else if (type.isAssignableFrom(MultiActionCancelEvent.class)) { + modifiedEvents.add(new DynamicExpect(MultiActionCancelServiceEvent.class, event.count())); + } else if (type.isAssignableFrom(TargetAttributesRequestedEvent.class)) { + modifiedEvents.add(new DynamicExpect(TargetAttributesRequestedServiceEvent.class, event.count())); + } else if (type.isAssignableFrom(CancelTargetAssignmentEvent.class)) { + modifiedEvents.add(new DynamicExpect(CancelTargetAssignmentServiceEvent.class, event.count())); + } else if (type.isAssignableFrom(ActionCreatedEvent.class)) { + modifiedEvents.add(new DynamicExpect(ActionCreatedServiceEvent.class, event.count())); + } else if (type.isAssignableFrom(ActionUpdatedEvent.class)) { + modifiedEvents.add(new DynamicExpect(ActionUpdatedServiceEvent.class, event.count())); + } + } + + public record DynamicExpect(Class type, int count) implements Expect { + + @Override + public Class annotationType() { + return Expect.class; + } + } + + private static final class EventCaptor implements ApplicationListener { private final ConcurrentHashMap, Integer> capturedEvents = new ConcurrentHashMap<>(); @@ -225,7 +224,6 @@ public class EventVerifier extends AbstractTestExecutionListener { if (event instanceof TargetAssignDistributionSetEvent targetAssignDistributionSetEvent) { assertThat(targetAssignDistributionSetEvent.getActions()).isNotEmpty(); - assertThat(targetAssignDistributionSetEvent.getDistributionSetId()).isNotNull(); } capturedEvents.compute(event.getClass(), (k, v) -> v == null ? 1 : v + 1); diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java index fec01b8d6..8d3765a54 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java @@ -20,6 +20,8 @@ import java.net.URI; import java.nio.file.Files; import java.time.Duration; import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -117,6 +119,8 @@ import org.springframework.test.context.TestPropertySource; public abstract class AbstractIntegrationTest { protected static final Pageable PAGE = PageRequest.of(0, 500, Sort.by(Direction.ASC, "id")); + protected static final Pageable UNPAGED = Pageable.unpaged(); + protected static final URI LOCALHOST = URI.create("http://127.0.0.1"); protected static final int DEFAULT_TEST_WEIGHT = 500; protected static final Random RND = new Random(); @@ -516,4 +520,11 @@ public abstract class AbstractIntegrationTest { protected List findByUpdateStatus(final TargetUpdateStatus status, final Pageable pageable) { return targetManagement.findAll(pageable).stream().filter(target -> status.equals(target.getUpdateStatus())).toList(); } + + @SafeVarargs + protected static Collection concat(final Collection... targets) { + final List result = new ArrayList<>(); + List.of(targets).forEach(result::addAll); + return result; + } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestLoggerExtension.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestLoggerExtension.java index 15ed56af2..7adf0feb9 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestLoggerExtension.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestLoggerExtension.java @@ -29,6 +29,6 @@ public class TestLoggerExtension implements BeforeTestExecutionCallback, TestWat @Override public void beforeTestExecution(final ExtensionContext context) { - log.info("Starting Test {}...", context.getTestMethod()); + log.debug("Starting Test {}...", context.getTestMethod()); } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java index 9485d5486..b7192693b 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java @@ -430,16 +430,16 @@ public class TestdataFactory { /** * Creates {@link DistributionSet}s in repository including three {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , - * {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative number and {@link DistributionSet#isRequiredMigrationStep()} + * {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative count and {@link DistributionSet#isRequiredMigrationStep()} * false. * * @param prefix for {@link SoftwareModule}s and {@link DistributionSet}s name, vendor and description. - * @param number of {@link DistributionSet}s to create + * @param count of {@link DistributionSet}s to create * @return {@link List} of {@link DistributionSet} entities */ - public List createDistributionSets(final String prefix, final int number) { + public List createDistributionSets(final String prefix, final int count) { final List sets = new ArrayList<>(); - for (int i = 0; i < number; i++) { + for (int i = 0; i < count; i++) { sets.add(createDistributionSet(prefix, DEFAULT_VERSION + "." + i, false)); } return sets;