Abstract RepositoryManagement test (#2631)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -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<String, String> 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<Target> 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
|
||||
|
||||
@@ -242,16 +242,6 @@ public interface DeploymentManagement extends PermissionSupport {
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Page<ActionStatus> 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}.<p/>
|
||||
* No entity based access control applied.
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<RegexChar> 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<String> getL18nReferenceDescription() {
|
||||
return Optional.ofNullable(l18nReferenceDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* 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
|
||||
* <p/>
|
||||
* 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}.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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);
|
||||
|
||||
@@ -95,15 +95,6 @@ public interface RolloutGroupManagement extends PermissionSupport {
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Page<RolloutGroup> 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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
* <p/>
|
||||
* 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
|
||||
* <p/>
|
||||
* 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
|
||||
* <p/>
|
||||
* 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
|
||||
* <p/>
|
||||
* 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
|
||||
* <p/>
|
||||
* 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)
|
||||
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* 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}.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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}.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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}.
|
||||
* <p/>
|
||||
* 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<GroupCreate> 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<Rollout> 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<Rollout> 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
|
||||
*/
|
||||
|
||||
@@ -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<T extends SoftwareModule>
|
||||
@ToString(callSuper = true)
|
||||
final class Create extends UpdateCreate {
|
||||
|
||||
private SoftwareModuleType type;
|
||||
private boolean encrypted;
|
||||
}
|
||||
|
||||
@@ -123,6 +118,5 @@ public interface SoftwareModuleManagement<T extends SoftwareModule>
|
||||
@ValidString
|
||||
@Size(max = SoftwareModule.VENDOR_MAX_SIZE)
|
||||
private String vendor;
|
||||
private SoftwareModuleType type;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -56,13 +56,12 @@ public interface TargetFilterQueryManagement<T extends TargetFilterQuery>
|
||||
* Verifies the provided filter syntax.
|
||||
*
|
||||
* @param query to verify
|
||||
* @return <code>true</code> 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
|
||||
|
||||
@@ -60,7 +60,6 @@ public interface TargetManagement<T extends Target>
|
||||
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<T extends Target>
|
||||
@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);
|
||||
|
||||
@@ -59,8 +59,7 @@ public interface TenantConfigurationManagement extends PermissionSupport {
|
||||
<T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration(Map<String, T> 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)
|
||||
<T extends Serializable> TenantConfigurationValue<T> 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 <T> 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)
|
||||
<T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(String configurationKeyName,
|
||||
Class<T> 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 <T> 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> T getGlobalConfigurationValue(String configurationKeyName, Class<T> propertyType);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<UpdateMode> valueOfIgnoreCase(final String name) {
|
||||
return Arrays.stream(values()).filter(mode -> mode.name().equalsIgnoreCase(name)).findAny();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public class ValidStringValidator implements ConstraintValidator<ValidString, St
|
||||
try {
|
||||
return cleaner.isValid(stringToDocument(value));
|
||||
} catch (final Exception ex) {
|
||||
log.error(String.format("There was an exception during bean field value (%s) validation", value), ex);
|
||||
log.error("There was an exception during bean field value ({}) validation", value, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +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.model;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Holds distribution set filter parameters.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public final class DistributionSetFilter {
|
||||
|
||||
private final Boolean isDeleted;
|
||||
private final Boolean isComplete;
|
||||
private final Boolean isValid;
|
||||
private final Long typeId;
|
||||
private final String searchText;
|
||||
private final Boolean selectDSWithNoTag;
|
||||
private final Collection<String> tagNames;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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<br/>
|
||||
* 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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<JpaArtifact> artifactAccessController,
|
||||
@Autowired(required = false) final AccessController<JpaSoftwareModuleType> softwareModuleTypeAccessController,
|
||||
@Autowired(required = false) final AccessController<JpaSoftwareModule> softwareModuleAccessController,
|
||||
|
||||
@@ -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<Object[]> resultList = entityManager.createQuery(query).setFirstResult(0).setMaxResults(1)
|
||||
.getResultList();
|
||||
final List<Object[]> 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) {
|
||||
|
||||
@@ -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<String> findMessagesByActionStatusId(final long actionStatusId, final Pageable pageable) {
|
||||
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
|
||||
@@ -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<Target> findTargetsOfRolloutGroup(final long rolloutGroupId, final Pageable page) {
|
||||
final JpaRolloutGroup rolloutGroup = rolloutGroupRepository.getById(rolloutGroupId);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -464,7 +464,7 @@ public class JpaTargetManagement
|
||||
// get the modifiable metadata map
|
||||
final Map<String, String> metadata = target.getMetadata();
|
||||
if (!metadata.containsKey(key)) {
|
||||
throw new EntityNotFoundException("Target metadata", controllerId + ":" + key);
|
||||
assertMetadataQuota(target.getId(), metadata.size() + 1);
|
||||
}
|
||||
metadata.put(key, value);
|
||||
|
||||
|
||||
@@ -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<String, String> metadata;
|
||||
private Map<String, String> metadata = new HashMap<>();
|
||||
|
||||
@Column(name = "complete")
|
||||
private boolean complete;
|
||||
|
||||
@@ -119,22 +119,9 @@ public class JpaSoftwareModule
|
||||
@ManyToMany(mappedBy = "modules", targetEntity = JpaDistributionSet.class, fetch = FetchType.LAZY)
|
||||
private List<DistributionSet> 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
|
||||
|
||||
@@ -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<? extends DistributionSet> assignTag(final Collection<? extends DistributionSet> sets,
|
||||
final DistributionSetTag tag) {
|
||||
protected List<? extends DistributionSet> assignTag(final Collection<? extends DistributionSet> sets, final DistributionSetTag tag) {
|
||||
return distributionSetManagement.assignTag(sets.stream().map(DistributionSet::getId).toList(), tag.getId());
|
||||
}
|
||||
|
||||
protected List<? extends DistributionSet> assignTags(final Collection<? extends DistributionSet> sets, final DistributionSetTag... tags) {
|
||||
List<? extends DistributionSet> result = null;
|
||||
for (DistributionSetTag tag : tags) {
|
||||
result = assignTag(sets, tag);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected List<? extends DistributionSet> unassignTag(
|
||||
final Collection<DistributionSet> 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<JpaDistributionSet> findDsByDistributionSetFilter(final DistributionSetFilter distributionSetFilter, final Pageable pageable) {
|
||||
final List<Specification<JpaDistributionSet>> specList = buildDistributionSetSpecifications(distributionSetFilter);
|
||||
return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetRepository, specList, pageable);
|
||||
}
|
||||
|
||||
private static List<Specification<JpaDistributionSet>> buildDistributionSetSpecifications(
|
||||
final DistributionSetFilter distributionSetFilter) {
|
||||
final List<Specification<JpaDistributionSet>> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<T extends BaseEntity, C, U extends Identifiable<Long>>
|
||||
extends AbstractJpaIntegrationTest {
|
||||
|
||||
protected RepositoryManagement<T, C, U> repositoryManagement;
|
||||
|
||||
@Getter(AccessLevel.PROTECTED)
|
||||
private Class<T> entityType; // T
|
||||
private Class<C> createType; // C
|
||||
private Class<U> updateType; // U
|
||||
private Function<Class<?>, ?> entityFactory;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
synchronized (AbstractRepositoryManagementTest.class) {
|
||||
if (repositoryManagement == null) {
|
||||
final RepoMan<T, C, U> 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<Long>)
|
||||
@ExpectEvents
|
||||
@Test
|
||||
void creates() {
|
||||
final List<T> instances = instances();
|
||||
final List<Long> 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<T> 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<Long> 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<T> instances() {
|
||||
return instances(1 + RND.nextInt(5));
|
||||
}
|
||||
|
||||
protected List<T> instances(final int count) {
|
||||
final List<C> creates = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
creates.add(forType(createType));
|
||||
}
|
||||
incrementEvents(entityType, EventType.CREATED, count);
|
||||
final List<T> 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<String> 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<Class<?>, 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> O forType(final Class<O> 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> O forBuildableTypeRe(final Class<O> 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> O forBuildableType(final Class<O> type, final Long id)
|
||||
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
|
||||
return forBuildableType(type, id, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <O> O forBuildableType(final Class<O> type, final Long id, final BiFunction<String, Class<?>, 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<T, C, U> resolveRepositoryManagement() {
|
||||
final Type[] abstractTestTypeArgs = genericTypes(getClass(), AbstractRepositoryManagementTest.class);
|
||||
final List<Field> 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<Class<?>, 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<T, C, U>(
|
||||
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<T>) abstractTestTypeArgs[0],
|
||||
(Class<C>) abstractTestTypeArgs[1],
|
||||
(Class<U>) 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<Field> fields(final Class<?> clazz, final List<Field> fields) {
|
||||
Collections.addAll(fields, clazz.getDeclaredFields());
|
||||
final Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass != null) {
|
||||
fields(superClass, fields);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static Optional<ResolvableType> findGenericSuperType(final ResolvableType resolvableType, final Class<?> targetSuperClass) {
|
||||
if (resolvableType.getRawClass() == targetSuperClass) {
|
||||
return Optional.of(resolvableType);
|
||||
}
|
||||
final Optional<ResolvableType> 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<String> 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<EventTypeKey, Class<?>> 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<T extends BaseEntity, C, U extends Identifiable<Long>>(
|
||||
RepositoryManagement<T, C, U> repo,
|
||||
Class<T> entityType,
|
||||
Class<C> createType,
|
||||
Class<U> updateType,
|
||||
Function<Class<?>, ?> entityFactory) {}
|
||||
}
|
||||
@@ -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<T extends BaseEntity, C, U extends Identifiable<Long>, MV, MVI extends MV>
|
||||
extends AbstractRepositoryManagementTest<T, C, U> {
|
||||
|
||||
protected MetadataSupport<MV> metadataSupport; // repository management, just casted to MetadataSupport
|
||||
private Class<MVI> 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<MVI>) genericTypes[4];
|
||||
maxMetaDataEntries = (Integer)QuotaManagement.class.getMethod("getMaxMetaDataEntriesPer" + getEntityType().getSimpleName()).invoke(quotaManagement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void setMetadataSupport() {
|
||||
metadataSupport = (MetadataSupport<MV>) 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<String, MV> 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<String, MV> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<br/>
|
||||
* Story: DistributionSet Management
|
||||
*/
|
||||
class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
|
||||
class DistributionSetManagementTest extends AbstractRepositoryManagementWithMetadataTest<DistributionSet, Create, Update, String, String> {
|
||||
|
||||
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<DistributionSetManagement.Create> creates = new ArrayList<>(10);
|
||||
final List<Create> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<? extends DistributionSet> dsGroup1 = testdataFactory.createDistributionSets("", 5);
|
||||
final String dsGroup2Prefix = "test";
|
||||
List<? extends DistributionSet> 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<? extends DistributionSet> allDistributionSets = Stream
|
||||
.of(dsGroup1, dsGroup2, Arrays.asList(dsDeleted, dsInComplete, dsNewType)).flatMap(Collection::stream)
|
||||
.toList();
|
||||
final List<? extends DistributionSet> 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<? extends DistributionSet> 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<? extends DistributionSet> allDistributionSets, final String dsNamePrefix) {
|
||||
final List<? extends DistributionSet> withTestNamePrefix = allDistributionSets.stream()
|
||||
.filter(ds -> ds.getName().startsWith(dsNamePrefix)).toList();
|
||||
assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().searchText(dsNamePrefix),
|
||||
withTestNamePrefix);
|
||||
|
||||
final List<? extends DistributionSet> withTestNameExact = withTestNamePrefix.stream()
|
||||
.filter(ds -> ds.getName().equals(dsNamePrefix)).toList();
|
||||
assertThatFilterContainsOnlyGivenDistributionSets(
|
||||
DistributionSetFilter.builder().searchText(dsNamePrefix + ":"), withTestNameExact);
|
||||
|
||||
final List<? extends DistributionSet> withTestNameExactAndVersionPrefix = withTestNameExact.stream()
|
||||
.filter(ds -> ds.getVersion().startsWith("1")).toList();
|
||||
assertThatFilterContainsOnlyGivenDistributionSets(
|
||||
DistributionSetFilter.builder().searchText(dsNamePrefix + ":1"),
|
||||
withTestNameExactAndVersionPrefix);
|
||||
|
||||
final List<? extends DistributionSet> 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<? extends DistributionSet> withVersionPrefix = allDistributionSets.stream()
|
||||
.filter(ds -> ds.getVersion().startsWith("1.0.")).toList();
|
||||
assertThatFilterContainsOnlyGivenDistributionSets(DistributionSetFilter.builder().searchText(":1.0."),
|
||||
withVersionPrefix);
|
||||
|
||||
final List<? extends DistributionSet> 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<? extends DistributionSet> dsWithTagA,
|
||||
final List<? extends DistributionSet> 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<? extends DistributionSet> completedStandardType,
|
||||
final DistributionSet dsNewType, final DistributionSet dsDeleted) {
|
||||
|
||||
final List<DistributionSet> 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<? extends DistributionSet> 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<? extends DistributionSet> 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<? extends DistributionSet> completedAndNotDeletedStandardTypeAndFilterString,
|
||||
final DistributionSet dsDeleted, final DistributionSet dsInComplete, final DistributionSet dsNewType,
|
||||
final DistributionSetType newType, final String filterString) {
|
||||
|
||||
final List<DistributionSet> 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<? extends DistributionSet> 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<? extends DistributionSet> 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> T getOrThrow(final Optional<T> opt) {
|
||||
return opt.orElseThrow(NoSuchElementException::new);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<br/>
|
||||
* Story: DistributionSet Tag Management
|
||||
*/
|
||||
class DistributionSetTagManagementTest extends AbstractJpaIntegrationTest {
|
||||
class DistributionSetTagManagementTest extends AbstractRepositoryManagementTest<DistributionSetTag, Create, Update> {
|
||||
|
||||
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<DistributionSet> dsAs = testdataFactory.createDistributionSets("DS-A", 1);
|
||||
final Collection<DistributionSet> dsBs = testdataFactory.createDistributionSets("DS-B", 2);
|
||||
final Collection<DistributionSet> dsCs = testdataFactory.createDistributionSets("DS-C", 4);
|
||||
final Collection<DistributionSet> dsABs = testdataFactory.createDistributionSets("DS-AB", 8);
|
||||
final Collection<DistributionSet> dsACs = testdataFactory.createDistributionSets("DS-AC", 16);
|
||||
final Collection<DistributionSet> dsBCs = testdataFactory.createDistributionSets("DS-BC", 32);
|
||||
final Collection<DistributionSet> 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<DistributionSet> dsAs = testdataFactory.createDistributionSets("DS-A", 20);
|
||||
final Collection<DistributionSet> dsBs = testdataFactory.createDistributionSets("DS-B", 10);
|
||||
final Collection<DistributionSet> dsCs = testdataFactory.createDistributionSets("DS-C", 25);
|
||||
final Collection<DistributionSet> dsABs = testdataFactory.createDistributionSets("DS-AB", 5);
|
||||
final Collection<DistributionSet> dsACs = testdataFactory.createDistributionSets("DS-AC", 11);
|
||||
final Collection<DistributionSet> dsBCs = testdataFactory.createDistributionSets("DS-BC", 13);
|
||||
final Collection<DistributionSet> 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<DistributionSet> groupA = testdataFactory.createDistributionSets(20);
|
||||
final Collection<DistributionSet> 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<DistributionSet> groupA = testdataFactory.createDistributionSets("A_", 5);
|
||||
final Collection<DistributionSet> groupB = testdataFactory.createDistributionSets("B_", 5);
|
||||
final Collection<DistributionSet> groupAB = concat(groupA, groupB);
|
||||
|
||||
// toggle A only -> A is now assigned
|
||||
// set A only -> A is now assigned
|
||||
List<? extends DistributionSet> result = assignTag(groupA, tag);
|
||||
assertThat(result)
|
||||
.hasSize(20)
|
||||
.containsAll((Collection) distributionSetManagement.get(groupA.stream().map(DistributionSet::getId).toList()));
|
||||
Assertions.<DistributionSet> 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<DistributionSet> 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.<DistributionSet> 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.<DistributionSet> 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<Long> 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<Long> 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<DistributionSetTag> 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<DistributionSetTag> 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<DistributionSetTag> 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<DistributionSetTag> tags = createDsSetsWithTags();
|
||||
assertThat(distributionSetTagRepository.findAll()).as("Wrong size of tags created").hasSize(tags.size());
|
||||
}
|
||||
|
||||
private void verifyExpectedFilteredDistributionSets(final DistributionSetFilter.DistributionSetFilterBuilder distributionSetFilterBuilder,
|
||||
final Stream<Collection<DistributionSet>> expectedFilteredDistributionSets) {
|
||||
final Collection<Long> retrievedFilteredDsIds = findDsByDistributionSetFilter(distributionSetFilterBuilder.build(), PAGE).stream()
|
||||
.map(DistributionSet::getId).toList();
|
||||
final Collection<Long> expectedFilteredDsIds = expectedFilteredDistributionSets.flatMap(Collection::stream)
|
||||
.map(DistributionSet::getId).toList();
|
||||
@SafeVarargs
|
||||
private void assertTaggedWithATagAreAsExpected(final DistributionSetTag tag, final Collection<DistributionSet>... expectedDistributionSets) {
|
||||
final Collection<Long> retrievedFilteredDsIds = distributionSetManagement.findByTag(tag.getId(), UNPAGED).getContent().stream()
|
||||
.map(DistributionSet::getId)
|
||||
.toList();
|
||||
final Collection<Long> expectedFilteredDsIds = Stream.of(expectedDistributionSets).flatMap(Collection::stream)
|
||||
.map(DistributionSet::getId)
|
||||
.toList();
|
||||
assertThat(retrievedFilteredDsIds).hasSameElementsAs(expectedFilteredDsIds);
|
||||
}
|
||||
|
||||
private List<DistributionSetTag> createDsSetsWithTags() {
|
||||
final Collection<DistributionSet> sets = testdataFactory.createDistributionSets(20);
|
||||
final Iterable<DistributionSetTag> 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 <T> Collection<T> concat(final Collection<T>... targets) {
|
||||
final List<T> result = new ArrayList<>();
|
||||
Arrays.asList(targets).forEach(result::addAll);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -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<br/>
|
||||
* Story: DistributionSet Management
|
||||
*/
|
||||
class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTest {
|
||||
class DistributionSetTypeManagementTest extends AbstractRepositoryManagementTest<DistributionSetType, Create, Update> {
|
||||
|
||||
/**
|
||||
* 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<Long> softwareModuleTypes = Collections.singletonList(osType.getId());
|
||||
|
||||
verifyThrownExceptionBy(() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(NOT_EXIST_IDL,
|
||||
softwareModuleTypes), "DistributionSetType");
|
||||
final List<Long> 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<Long> 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<Long> 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<Long> 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<? extends DistributionSetType> softDeleted = distributionSetTypeManagement.findByKey("softdeleted");
|
||||
final Optional<? extends DistributionSetType> 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<SoftwareModule> 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<Long> 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<Long> 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<Long> softwareModuleTypes = Collections.singletonList(osType.getId());
|
||||
|
||||
verifyThrownExceptionBy(
|
||||
() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(NOT_EXIST_IDL, softwareModuleTypes),
|
||||
"DistributionSetType");
|
||||
final List<Long> 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<Long> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<br/>
|
||||
* Story: Software Module Management
|
||||
*/
|
||||
class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
class SoftwareModuleManagementTest
|
||||
extends AbstractRepositoryManagementWithMetadataTest<SoftwareModule, Create, Update, MetadataValue, MetadataValueCreate> {
|
||||
|
||||
/**
|
||||
* 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> O forType(final Class<O> 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<SoftwareModuleManagement.Create> 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<Long> 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<Artifact> 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<String, MetadataValueCreate> 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<String, MetadataValueCreate> 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<String, MetadataValue> metadataSw1 = softwareModuleManagement.getMetadata(sw1.getId());
|
||||
final Map<String, MetadataValue> metadataSw2 = softwareModuleManagement.getMetadata(sw2.getId());
|
||||
assertThat(metadataSw1).hasSize(metadataCountSw1);
|
||||
assertThat(metadataSw2).hasSize(metadataCountSw2);
|
||||
|
||||
final Map<String, String> metadataSw1V = softwareModuleManagement
|
||||
.findMetaDataBySoftwareModuleIdsAndTargetVisible(List.of(sw1.getId())).get(sw1.getId());
|
||||
final Map<String, String> 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<DistributionSet> 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<Create> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<br/>
|
||||
* Story: Software Module Management
|
||||
*/
|
||||
class SoftwareModuleTypeManagementTest extends AbstractJpaIntegrationTest {
|
||||
class SoftwareModuleTypeManagementTest extends AbstractRepositoryManagementTest<SoftwareModuleType, Create, Update> {
|
||||
|
||||
/**
|
||||
* 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<Create> 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.<SoftwareModuleType>assertThat(softwareModuleTypeManagement.findAll(PAGE)).hasSize(3).contains(osType, runtimeType, appType);
|
||||
Assertions.<SoftwareModuleType> 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.<SoftwareModuleType>assertThat(softwareModuleTypeManagement.findAll(PAGE)).hasSize(4).contains(osType, runtimeType, appType, type);
|
||||
Assertions.<SoftwareModuleType> assertThat(softwareModuleTypeManagement.findAll(PAGE))
|
||||
.hasSize(4)
|
||||
.contains(osType, runtimeType, appType, type);
|
||||
|
||||
// delete unassigned
|
||||
softwareModuleTypeManagement.delete(type.getId());
|
||||
Assertions.<SoftwareModuleType>assertThat(softwareModuleTypeManagement.findAll(PAGE)).hasSize(3).contains(osType, runtimeType, appType);
|
||||
Assertions.<SoftwareModuleType>assertThat(softwareModuleTypeRepository.findAll()).hasSize(3).contains(osType, runtimeType, appType);
|
||||
Assertions.<SoftwareModuleType> assertThat(softwareModuleTypeManagement.findAll(PAGE))
|
||||
.hasSize(3)
|
||||
.contains(osType, runtimeType, appType);
|
||||
Assertions.<SoftwareModuleType> 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.<SoftwareModuleType>assertThat(softwareModuleTypeManagement.findAll(PAGE)).hasSize(4).contains(osType, runtimeType, appType, type);
|
||||
Assertions.<SoftwareModuleType> 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.<SoftwareModuleType>assertThat(softwareModuleTypeManagement.findAll(PAGE)).hasSize(3).contains(osType, runtimeType, appType);
|
||||
Assertions.<SoftwareModuleType>assertThat(softwareModuleTypeManagement.findByRsql("name==*", PAGE)).hasSize(3).contains(osType, runtimeType, appType);
|
||||
Assertions.<SoftwareModuleType> assertThat(softwareModuleTypeManagement.findAll(PAGE))
|
||||
.hasSize(3)
|
||||
.contains(osType, runtimeType, appType);
|
||||
Assertions.<SoftwareModuleType> 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.<SoftwareModuleType>assertThat(((SoftwareModuleTypeManagement<SoftwareModuleType>) softwareModuleTypeManagement).findByName("thename"))
|
||||
Assertions.assertThat(((SoftwareModuleTypeManagement<SoftwareModuleType>) 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<Create> 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<? extends SoftwareModuleType> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<br/>
|
||||
* Story: Target Filter Query Management
|
||||
*/
|
||||
class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest {
|
||||
class TargetFilterQueryManagementTest extends AbstractRepositoryManagementTest<TargetFilterQuery, Create, Update> {
|
||||
|
||||
/**
|
||||
* 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> O forType(final Class<O> 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<? extends TargetFilterQuery> 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<Long> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<br/>
|
||||
* Story: Target Management
|
||||
*/
|
||||
class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
class TargetManagementTest extends AbstractRepositoryManagementWithMetadataTest<Target, Create, Update, String, String> {
|
||||
|
||||
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<String, ? extends String> metadata) {
|
||||
targetManagement.createMetadata(toControllerId(id), (Map<String, String>) metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMetadata(final Long id, final String key) {
|
||||
return targetManagement.getMetadata(targetManagement.get(id).getControllerId(), key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> 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<Long> 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<? extends Target> firstList = testdataFactory.createTargets(100, myCtrlID, "first description");
|
||||
|
||||
final Target extra = testdataFactory.createTarget("myCtrlID-00081XX");
|
||||
|
||||
final Iterable<JpaTarget> 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<? extends Target> targetsToDelete = firstList.subList(0, numberToDelete);
|
||||
final Target[] deletedTargets = toArray(targetsToDelete, Target.class);
|
||||
final List<Long> targetsIdsToDelete = targetsToDelete.stream().map(Target::getId).toList();
|
||||
|
||||
targetManagement.delete(targetsIdsToDelete);
|
||||
|
||||
final List<? extends Target> targetsLeft = targetManagement.findAll(PageRequest.of(0, 200)).getContent();
|
||||
assertThat(firstList.spliterator().getExactSizeIfKnown() - numberToDelete).as("Size of split list")
|
||||
.isEqualTo(targetsLeft.spliterator().getExactSizeIfKnown());
|
||||
|
||||
Assertions.<Target> 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<Long> 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<? extends Target> foundDs = targetManagement.get(searchIds);
|
||||
assertThat(foundDs).hasSize(3);
|
||||
|
||||
final List<Long> 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<String, String> 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<String, String> 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<JpaTarget> 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<JpaTarget> 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<JpaTarget> 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<JpaTarget> 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)
|
||||
|
||||
@@ -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<br/>
|
||||
* Story: Target Tag Management
|
||||
*/
|
||||
class TargetTagManagementTest extends AbstractJpaIntegrationTest {
|
||||
class TargetTagManagementTest extends AbstractRepositoryManagementTest<TargetTag, Create, Update> {
|
||||
|
||||
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<Target> groupA = testdataFactory.createTargets(20);
|
||||
final List<Target> 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<Target> 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<Target> 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<String> 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<Target> groupA = testdataFactory.createTargets(20);
|
||||
final List<Target> 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<Target> 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<Target> 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<JpaTargetTag> 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<JpaTargetTag> 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<JpaTargetTag> 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 <T> Collection<T> concat(final Collection<T>... targets) {
|
||||
final List<T> result = new ArrayList<>();
|
||||
Arrays.asList(targets).forEach(result::addAll);
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<JpaTargetTag> createTargetsWithTags() {
|
||||
final List<Target> targets = testdataFactory.createTargets(20);
|
||||
final Iterable<? extends TargetTag> tags = testdataFactory.createTargetTags(20, "");
|
||||
|
||||
tags.forEach(tag -> assignTag(targets, tag));
|
||||
|
||||
return targetTagRepository.findAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<br/>
|
||||
* 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<TargetType, Create, Update> {
|
||||
|
||||
/**
|
||||
* 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<JpaTargetType> 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<JpaTargetType> findByName(final String name) {
|
||||
return targetTypeManagement.getByName(name).map(JpaTargetType.class::cast);
|
||||
}
|
||||
|
||||
private Optional<JpaTargetType> findByKey(final String key) {
|
||||
return targetTypeManagement.getByKey(key).map(JpaTargetType.class::cast);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* 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<Expect[]> 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<Expect[]> expectedEvents = getExpectationsFrom(testContext.getTestMethod());
|
||||
try {
|
||||
expectedEvents.ifPresent(this::afterTest);
|
||||
} finally {
|
||||
expectedEvents.ifPresent(listener -> removeEventListener(testContext));
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Expect[]> getExpectationsFrom(final Method testMethod) {
|
||||
final Optional<Expect[]> expectedEvents = Optional.ofNullable(testMethod.getAnnotation(ExpectEvents.class)).map(ExpectEvents::value);
|
||||
if (expectedEvents.isPresent()) {
|
||||
List<Expect> 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<? extends Annotation> 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<Class<?>> 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<Supplier<Expect[]>> DYNAMIC_EXPECTATIONS = new ThreadLocal<>();
|
||||
|
||||
private Optional<Expect[]> getExpectationsFrom(final Method testMethod) {
|
||||
return Optional.ofNullable(testMethod.getAnnotation(ExpectEvents.class))
|
||||
.map(ExpectEvents::value)
|
||||
.map(expectedEvents -> {
|
||||
final Supplier<Expect[]> 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<Expect> 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<AbstractRemoteEvent> {
|
||||
private static void addServiceEventIfNeeded(final Expect event, final List<Expect> 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<? extends Annotation> annotationType() {
|
||||
return Expect.class;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EventCaptor implements ApplicationListener<AbstractRemoteEvent> {
|
||||
|
||||
private final ConcurrentHashMap<Class<?>, 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);
|
||||
|
||||
@@ -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<? extends Target> findByUpdateStatus(final TargetUpdateStatus status, final Pageable pageable) {
|
||||
return targetManagement.findAll(pageable).stream().filter(target -> status.equals(target.getUpdateStatus())).toList();
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
protected static <T> Collection<T> concat(final Collection<T>... targets) {
|
||||
final List<T> result = new ArrayList<>();
|
||||
List.of(targets).forEach(result::addAll);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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()}
|
||||
* <code>false</code>.
|
||||
*
|
||||
* @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<DistributionSet> createDistributionSets(final String prefix, final int number) {
|
||||
public List<DistributionSet> createDistributionSets(final String prefix, final int count) {
|
||||
final List<DistributionSet> 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;
|
||||
|
||||
Reference in New Issue
Block a user