Refactor SofrwareModule Management (#2594)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -40,6 +40,7 @@ import com.jayway.jsonpath.JsonPath;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleRequestBodyPost;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||
@@ -339,7 +340,7 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
|
||||
void unlockSoftwareModule() throws Exception {
|
||||
final SoftwareModule sm = softwareModuleManagement.create(
|
||||
SoftwareModuleManagement.Create.builder().type(osType).name("name1").version("version1").build());
|
||||
softwareModuleManagement.lock(sm.getId());
|
||||
softwareModuleManagement.lock(sm);
|
||||
assertThat(softwareModuleManagement.get(sm.getId())
|
||||
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, sm.getId())).isLocked())
|
||||
.as("Software module is locked")
|
||||
@@ -1309,26 +1310,34 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
|
||||
.andExpect(jsonPath("[1].createdAt", not(equalTo(0))))
|
||||
.andReturn();
|
||||
|
||||
final SoftwareModule osCreated = softwareModuleManagement.findByNameAndVersionAndType("name1", "version1", osType.getId()).get();
|
||||
final SoftwareModule appCreated = softwareModuleManagement.findByNameAndVersionAndType("name3", "version3", appType.getId()).get();
|
||||
final List<MgmtSoftwareModule> softwareModules = OBJECT_MAPPER.readerForListOf(MgmtSoftwareModule.class)
|
||||
.readValue(mvcResult.getResponse().getContentAsString());
|
||||
final long osCreatedId = softwareModules.stream()
|
||||
.filter(softwareModule -> osType.getKey().equals(softwareModule.getType()))
|
||||
.findFirst()
|
||||
.map(MgmtSoftwareModule::getId)
|
||||
.orElseThrow();
|
||||
final long appCreatedId = softwareModules.stream()
|
||||
.filter(softwareModule -> appType.getKey().equals(softwareModule.getType()))
|
||||
.findFirst()
|
||||
.map(MgmtSoftwareModule::getId)
|
||||
.orElseThrow();
|
||||
|
||||
assertThat(JsonPath.compile("[0]._links.self.href").read(mvcResult.getResponse().getContentAsString()).toString())
|
||||
.as("Response contains invalid self href")
|
||||
.isEqualTo("http://localhost/rest/v1/softwaremodules/" + osCreated.getId());
|
||||
.isEqualTo("http://localhost/rest/v1/softwaremodules/" + osCreatedId);
|
||||
|
||||
assertThat(JsonPath.compile("[1]._links.self.href").read(mvcResult.getResponse().getContentAsString()).toString())
|
||||
.as("Response contains links self href")
|
||||
.isEqualTo("http://localhost/rest/v1/softwaremodules/" + appCreated.getId());
|
||||
.isEqualTo("http://localhost/rest/v1/softwaremodules/" + appCreatedId);
|
||||
|
||||
assertThat(softwareModuleManagement.findAll(PAGE)).as("Wrong softwaremodule size").hasSize(2);
|
||||
assertThat(softwareModuleManagement.findByType(osType.getId(), PAGE).getContent().get(0).getName())
|
||||
.as("Softwaremoudle name is wrong").isEqualTo(os.getName());
|
||||
assertThat(softwareModuleManagement.findByType(osType.getId(), PAGE).getContent().get(0).getCreatedBy())
|
||||
.as("Softwaremoudle created by is wrong").isEqualTo("uploadTester");
|
||||
assertThat(softwareModuleManagement.findByType(osType.getId(), PAGE).getContent().get(0).getCreatedAt())
|
||||
.as("Softwaremoudle created at is wrong").isGreaterThanOrEqualTo(current);
|
||||
assertThat(softwareModuleManagement.findByType(appType.getId(), PAGE).getContent().get(0).getName())
|
||||
.as("Softwaremoudle name is wrong").isEqualTo(ah.getName());
|
||||
final SoftwareModule osCreated = softwareModuleManagement.get(osCreatedId).orElseThrow();
|
||||
final SoftwareModule appCreated = softwareModuleManagement.get(appCreatedId).orElseThrow();
|
||||
assertThat(osCreated.getName()).as("Softwaremoudle name is wrong").isEqualTo(os.getName());
|
||||
assertThat(osCreated.getCreatedBy()).as("Softwaremoudle created by is wrong").isEqualTo("uploadTester");
|
||||
assertThat(osCreated.getCreatedAt()).as("Softwaremoudle created at is wrong").isGreaterThanOrEqualTo(current);
|
||||
assertThat(appCreated.getName()).as("Softwaremoudle name is wrong").isEqualTo(ah.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,22 +55,22 @@ public interface SoftwareModuleManagement<T extends SoftwareModule>
|
||||
/**
|
||||
* Locks a software module.
|
||||
*
|
||||
* @param id the software module id
|
||||
* @param softwareModule the software module
|
||||
* @throws EntityNotFoundException if software module with given ID does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
|
||||
void lock(long id);
|
||||
T lock(SoftwareModule softwareModule);
|
||||
|
||||
/**
|
||||
* Unlocks a software module.<br/>
|
||||
* Use it with extreme care! In general once software module is locked
|
||||
* it shall not be unlocked. Note that it could have been assigned / deployed to targets.
|
||||
*
|
||||
* @param id the software module id
|
||||
* @param softwareModule the software module
|
||||
* @throws EntityNotFoundException if software module with given ID does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
|
||||
void unlock(long id);
|
||||
T unlock(SoftwareModule softwareModule);
|
||||
|
||||
/**
|
||||
* Returns all modules assigned to given {@link DistributionSet}.
|
||||
@@ -83,52 +83,6 @@ public interface SoftwareModuleManagement<T extends SoftwareModule>
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Page<T> findByAssignedTo(long distributionSetId, @NotNull Pageable pageable);
|
||||
|
||||
/**
|
||||
* Filter {@link SoftwareModule}s with given {@link SoftwareModule#getName()} or {@link SoftwareModule#getVersion()}
|
||||
* and {@link SoftwareModule#getType()} that are not marked as deleted.
|
||||
*
|
||||
* @param searchText to be filtered as "like" on {@link SoftwareModule#getName()}
|
||||
* @param typeId to be filtered as "like" on {@link SoftwareModule#getType()}
|
||||
* @param pageable page parameter
|
||||
* @return the page of found {@link SoftwareModule}
|
||||
* @throws EntityNotFoundException if given software module type does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Slice<T> findByTextAndType(String searchText, Long typeId, @NotNull Pageable pageable);
|
||||
|
||||
/**
|
||||
* Retrieves {@link SoftwareModule} by their name AND version AND type.
|
||||
*
|
||||
* @param name of the {@link SoftwareModule}
|
||||
* @param version of the {@link SoftwareModule}
|
||||
* @param typeId of the {@link SoftwareModule}
|
||||
* @return the found {@link SoftwareModule}
|
||||
* @throws EntityNotFoundException if software module type with given ID does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Optional<T> findByNameAndVersionAndType(@NotEmpty String name, @NotEmpty String version, long typeId);
|
||||
|
||||
/**
|
||||
* Retrieves the {@link SoftwareModule}s by their {@link SoftwareModuleType}
|
||||
*
|
||||
* @param typeId to be filtered on
|
||||
* @param pageable page parameters
|
||||
* @return the found {@link SoftwareModule}s
|
||||
* @throws EntityNotFoundException if software module type with given ID does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Slice<T> findByType(long typeId, @NotNull Pageable pageable);
|
||||
|
||||
/**
|
||||
* Returns count of all modules assigned to given {@link DistributionSet}.
|
||||
*
|
||||
* @param distributionSetId to search for
|
||||
* @return count of {@link SoftwareModule}s that are assigned to given {@link DistributionSet}.
|
||||
* @throws EntityNotFoundException if distribution set with given ID does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
long countByAssignedTo(long distributionSetId);
|
||||
|
||||
@SuperBuilder
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
|
||||
@@ -36,9 +36,6 @@ public interface TargetTagManagement<T extends TargetTag>
|
||||
return "TARGET";
|
||||
}
|
||||
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Optional<TargetTag> getByName(@NotEmpty String name);
|
||||
|
||||
@SuperBuilder
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
|
||||
@@ -70,23 +70,6 @@ public interface TargetTypeManagement<T extends TargetType>
|
||||
@PreAuthorize(HAS_AUTH_READ_TARGET_TYPE)
|
||||
Optional<TargetType> getByName(@NotEmpty String name);
|
||||
|
||||
/**
|
||||
* @param name as {@link TargetType#getName()}
|
||||
* @return total count by name
|
||||
*/
|
||||
@PreAuthorize(HAS_AUTH_READ_TARGET_TYPE)
|
||||
long countByName(String name);
|
||||
|
||||
/**
|
||||
* Retrieves {@link TargetType}s by filtering on the given parameters.
|
||||
*
|
||||
* @param name has text of filters to be applied.
|
||||
* @param pageable page parameter
|
||||
* @return the page of found {@link TargetType}
|
||||
*/
|
||||
@PreAuthorize(HAS_AUTH_READ_TARGET_TYPE)
|
||||
Slice<TargetType> findByName(String name, @NotNull Pageable pageable);
|
||||
|
||||
/**
|
||||
* @param id Target type ID
|
||||
* @param distributionSetTypeIds Distribution set ID
|
||||
|
||||
@@ -12,13 +12,11 @@ package org.eclipse.hawkbit.repository.jpa.management;
|
||||
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_DELAY;
|
||||
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -34,24 +32,19 @@ import org.eclipse.hawkbit.repository.exception.LockedException;
|
||||
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule.JpaMetadataValue;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleSpecification;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValue;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.retry.annotation.Backoff;
|
||||
import org.springframework.retry.annotation.Retryable;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -65,7 +58,6 @@ public class JpaSoftwareModuleManagement
|
||||
implements SoftwareModuleManagement<JpaSoftwareModule> {
|
||||
|
||||
private final DistributionSetRepository distributionSetRepository;
|
||||
private final SoftwareModuleTypeRepository softwareModuleTypeRepository;
|
||||
private final ArtifactManagement artifactManagement;
|
||||
private final QuotaManagement quotaManagement;
|
||||
|
||||
@@ -73,11 +65,9 @@ public class JpaSoftwareModuleManagement
|
||||
final SoftwareModuleRepository softwareModuleRepository,
|
||||
final EntityManager entityManager,
|
||||
final DistributionSetRepository distributionSetRepository,
|
||||
final SoftwareModuleTypeRepository softwareModuleTypeRepository,
|
||||
final ArtifactManagement artifactManagement, final QuotaManagement quotaManagement) {
|
||||
super(softwareModuleRepository, entityManager);
|
||||
this.distributionSetRepository = distributionSetRepository;
|
||||
this.softwareModuleTypeRepository = softwareModuleTypeRepository;
|
||||
this.artifactManagement = artifactManagement;
|
||||
this.quotaManagement = quotaManagement;
|
||||
}
|
||||
@@ -175,26 +165,26 @@ public class JpaSoftwareModuleManagement
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
|
||||
public void lock(final long id) {
|
||||
final JpaSoftwareModule softwareModule = jpaRepository
|
||||
.findById(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id));
|
||||
if (!softwareModule.isLocked()) {
|
||||
softwareModule.lock();
|
||||
jpaRepository.save(softwareModule);
|
||||
public JpaSoftwareModule lock(final SoftwareModule softwareModule) {
|
||||
final JpaSoftwareModule jpaSoftwareModule = toJpaSoftwareModule(softwareModule);
|
||||
if (jpaSoftwareModule.isLocked()) {
|
||||
return jpaSoftwareModule;
|
||||
} else {
|
||||
jpaSoftwareModule.lock();
|
||||
return jpaRepository.save(jpaSoftwareModule);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
|
||||
public void unlock(final long id) {
|
||||
final JpaSoftwareModule softwareModule = jpaRepository
|
||||
.findById(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id));
|
||||
public JpaSoftwareModule unlock(final SoftwareModule softwareModule) {
|
||||
final JpaSoftwareModule jpaSoftwareModule = toJpaSoftwareModule(softwareModule);
|
||||
if (softwareModule.isLocked()) {
|
||||
softwareModule.unlock();
|
||||
jpaRepository.save(softwareModule);
|
||||
jpaSoftwareModule.unlock();
|
||||
return jpaRepository.save(jpaSoftwareModule);
|
||||
} else {
|
||||
return jpaSoftwareModule;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,59 +198,6 @@ public class JpaSoftwareModuleManagement
|
||||
pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<JpaSoftwareModule> findByTextAndType(final String searchText, final Long typeId, final Pageable pageable) {
|
||||
final List<Specification<JpaSoftwareModule>> specList = new ArrayList<>(3);
|
||||
specList.add(SoftwareModuleSpecification.isNotDeleted());
|
||||
|
||||
if (!ObjectUtils.isEmpty(searchText)) {
|
||||
specList.add(buildSmSearchQuerySpec(searchText));
|
||||
}
|
||||
|
||||
if (null != typeId) {
|
||||
assertSoftwareModuleTypeExists(typeId);
|
||||
specList.add(SoftwareModuleSpecification.equalType(typeId));
|
||||
}
|
||||
|
||||
specList.add(SoftwareModuleSpecification.fetchType());
|
||||
|
||||
return JpaManagementHelper.findAllWithoutCountBySpec(jpaRepository, specList, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<JpaSoftwareModule> findByNameAndVersionAndType(final String name, final String version, final long typeId) {
|
||||
assertSoftwareModuleTypeExists(typeId);
|
||||
|
||||
// TODO AC - Access is restricted. This could have problem with UI when access control is enabled.
|
||||
// Vaadin UI use this for validation. May need to be called via elevated access
|
||||
return JpaManagementHelper
|
||||
.findOneBySpec(
|
||||
jpaRepository,
|
||||
List.of(
|
||||
SoftwareModuleSpecification.likeNameAndVersion(name, version),
|
||||
SoftwareModuleSpecification.equalType(typeId),
|
||||
SoftwareModuleSpecification.fetchType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<JpaSoftwareModule> findByType(final long typeId, final Pageable pageable) {
|
||||
assertSoftwareModuleTypeExists(typeId);
|
||||
|
||||
return JpaManagementHelper.findAllWithoutCountBySpec(
|
||||
jpaRepository,
|
||||
List.of(
|
||||
SoftwareModuleSpecification.equalType(typeId),
|
||||
SoftwareModuleSpecification.isNotDeleted()), pageable
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByAssignedTo(final long distributionSetId) {
|
||||
assertDistributionSetExists(distributionSetId);
|
||||
|
||||
return jpaRepository.count(SoftwareModuleSpecification.byAssignedToDs(distributionSetId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the meta-data quota for the software module with the given ID.
|
||||
*
|
||||
@@ -272,6 +209,20 @@ public class JpaSoftwareModuleManagement
|
||||
QuotaHelper.assertAssignmentQuota(requested, maxMetaData, SoftwareModule.MetadataValueCreate.class, SoftwareModule.class);
|
||||
}
|
||||
|
||||
private JpaSoftwareModule toJpaSoftwareModule(final SoftwareModule softwareModule) {
|
||||
if (softwareModule instanceof JpaSoftwareModule jpaSoftwareModule) {
|
||||
return entityManager.merge(jpaSoftwareModule); // if from
|
||||
} else {
|
||||
return getById(softwareModule.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private JpaSoftwareModule getById(final long id) {
|
||||
return jpaRepository
|
||||
.findById(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id));
|
||||
}
|
||||
|
||||
private void deleteGridFsArtifacts(final JpaSoftwareModule swModule) {
|
||||
jpaRepository.getAccessController().ifPresent(accessController ->
|
||||
accessController.assertOperationAllowed(AccessController.Operation.DELETE, swModule));
|
||||
@@ -280,19 +231,6 @@ public class JpaSoftwareModuleManagement
|
||||
sha1Hashes.forEach(((JpaArtifactManagement) artifactManagement)::clearArtifactBinary));
|
||||
}
|
||||
|
||||
private Specification<JpaSoftwareModule> buildSmSearchQuerySpec(final String searchText) {
|
||||
final String[] smFilterNameAndVersionEntries = JpaManagementHelper
|
||||
.getFilterNameAndVersionEntries(searchText.trim());
|
||||
return SoftwareModuleSpecification.likeNameAndVersion(smFilterNameAndVersionEntries[0],
|
||||
smFilterNameAndVersionEntries[1]);
|
||||
}
|
||||
|
||||
private void assertSoftwareModuleTypeExists(final Long typeId) {
|
||||
if (!softwareModuleTypeRepository.existsById(typeId)) {
|
||||
throw new EntityNotFoundException(SoftwareModuleType.class, typeId);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertDistributionSetExists(final long distributionSetId) {
|
||||
if (!distributionSetRepository.existsById(distributionSetId)) {
|
||||
throw new EntityNotFoundException(DistributionSet.class, distributionSetId);
|
||||
|
||||
@@ -39,10 +39,4 @@ public class JpaTargetTagManagement
|
||||
final EntityManager entityManager) {
|
||||
super(targetTagRepository, entityManager);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Optional<TargetTag> getByName(final String name) {
|
||||
return jpaRepository.findByNameEquals(name);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@
|
||||
package org.eclipse.hawkbit.repository.jpa.management;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.ToLongFunction;
|
||||
|
||||
@@ -22,7 +21,6 @@ import org.eclipse.hawkbit.repository.TargetTypeManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.TargetTypeInUseException;
|
||||
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
|
||||
@@ -35,8 +33,6 @@ import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.TargetType;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.retry.annotation.Backoff;
|
||||
import org.springframework.retry.annotation.Retryable;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -89,16 +85,6 @@ public class JpaTargetTypeManagement
|
||||
return jpaRepository.findOne(TargetTypeSpecification.hasName(name)).map(TargetType.class::cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<TargetType> findByName(final String name, final Pageable pageable) {
|
||||
return JpaManagementHelper.findAllWithoutCountBySpec(jpaRepository, List.of(TargetTypeSpecification.likeName(name)), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByName(final String name) {
|
||||
return jpaRepository.count(TargetTypeSpecification.hasName(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
|
||||
|
||||
@@ -65,4 +65,4 @@ public interface SoftwareModuleRepository extends BaseEntityRepository<JpaSoftwa
|
||||
@Transactional
|
||||
@Query("DELETE FROM JpaSoftwareModule t WHERE t.tenant = :tenant")
|
||||
void deleteByTenant(@Param("tenant") String tenant);
|
||||
}
|
||||
}
|
||||
@@ -27,14 +27,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Transactional(readOnly = true)
|
||||
public interface TargetTagRepository extends BaseEntityRepository<JpaTargetTag> {
|
||||
|
||||
/**
|
||||
* find {@link TargetTag} by its name.
|
||||
*
|
||||
* @param tagName to filter on
|
||||
* @return the {@link TargetTag} if found, otherwise null
|
||||
*/
|
||||
Optional<TargetTag> findByNameEquals(String tagName);
|
||||
|
||||
/**
|
||||
* Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety
|
||||
* reasons (this is a "delete everything" query after all) we add the tenant
|
||||
|
||||
@@ -56,23 +56,9 @@ class TargetTypeAccessControllerTest extends AbstractJpaIntegrationTest {
|
||||
assertThat(targetTypeManagement.findByRsql("name==*", Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
||||
.containsOnly(permittedTargetType.getId());
|
||||
|
||||
// verify targetTypeManagement#findByName
|
||||
assertThat(targetTypeManagement.findByName(permittedTargetType.getName(), Pageable.unpaged()).getContent())
|
||||
.hasSize(1).satisfies(results ->
|
||||
assertThat(results.get(0).getId()).isEqualTo(permittedTargetType.getId()));
|
||||
assertThat(targetTypeManagement.findByName(hiddenTargetType.getName(), Pageable.unpaged())).isEmpty();
|
||||
|
||||
// verify targetTypeManagement#count
|
||||
assertThat(targetTypeManagement.count()).isEqualTo(1);
|
||||
|
||||
// verify targetTypeManagement#countByName
|
||||
assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1);
|
||||
assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero();
|
||||
|
||||
// verify targetTypeManagement#countByName
|
||||
assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1);
|
||||
assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero();
|
||||
|
||||
// verify targetTypeManagement#get by id
|
||||
assertThat(targetTypeManagement.get(permittedTargetType.getId())).isPresent();
|
||||
final Long hiddenTargetTypeId = hiddenTargetType.getId();
|
||||
|
||||
@@ -81,28 +81,6 @@ class SoftwareManagementSecurityTest
|
||||
assertPermissions(() -> softwareModuleManagement.findByAssignedTo(1L, PAGE), List.of(SpPermission.READ_REPOSITORY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
|
||||
*/
|
||||
@Test
|
||||
void countByAssignedToPermissionsCheck() {
|
||||
assertPermissions(() -> softwareModuleManagement.countByAssignedTo(1L), List.of(SpPermission.READ_REPOSITORY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
|
||||
*/
|
||||
@Test
|
||||
void findByTextAndTypePermissionsCheck() {
|
||||
assertPermissions(() -> softwareModuleManagement.findByTextAndType("text", 1L, PAGE), List.of(SpPermission.READ_REPOSITORY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getByNameAndVersionAndTypePermissionsCheck() {
|
||||
assertPermissions(() -> softwareModuleManagement.findByNameAndVersionAndType("name", "version", 1L),
|
||||
List.of(SpPermission.READ_REPOSITORY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
|
||||
*/
|
||||
@@ -119,21 +97,14 @@ class SoftwareManagementSecurityTest
|
||||
assertPermissions(() -> softwareModuleManagement.getMetadata(1L), List.of(SpPermission.READ_REPOSITORY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
|
||||
*/
|
||||
@Test
|
||||
void findByTypePermissionsCheck() {
|
||||
assertPermissions(() -> softwareModuleManagement.findByType(1L, PAGE), List.of(SpPermission.READ_REPOSITORY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
|
||||
*/
|
||||
@Test
|
||||
void lockPermissionsCheck() {
|
||||
final SoftwareModule softwareModule = testdataFactory.createSoftwareModuleOs();
|
||||
assertPermissions(() -> {
|
||||
softwareModuleManagement.lock(1L);
|
||||
softwareModuleManagement.lock(softwareModule);
|
||||
return null;
|
||||
}, List.of(SpPermission.UPDATE_REPOSITORY));
|
||||
}
|
||||
@@ -143,8 +114,9 @@ class SoftwareManagementSecurityTest
|
||||
*/
|
||||
@Test
|
||||
void unlockPermissionsCheck() {
|
||||
final SoftwareModule softwareModule = testdataFactory.createSoftwareModuleOs();
|
||||
assertPermissions(() -> {
|
||||
softwareModuleManagement.unlock(1L);
|
||||
softwareModuleManagement.unlock(softwareModule);
|
||||
return null;
|
||||
}, List.of(SpPermission.UPDATE_REPOSITORY));
|
||||
}
|
||||
|
||||
@@ -68,7 +68,6 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
void nonExistingEntityAccessReturnsNotPresent() {
|
||||
final SoftwareModule module = testdataFactory.createSoftwareModuleApp();
|
||||
assertThat(softwareModuleManagement.get(1234L)).isNotPresent();
|
||||
assertThat(softwareModuleManagement.findByNameAndVersionAndType(NOT_EXIST_ID, NOT_EXIST_ID, osType.getId())).isNotPresent();
|
||||
final Long moduleId = module.getId();
|
||||
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> softwareModuleManagement.getMetadata(moduleId, NOT_EXIST_ID));
|
||||
}
|
||||
@@ -93,27 +92,18 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
NOT_EXIST_IDL, "xxx", new MetadataValueCreate("xxx")), "SoftwareModule");
|
||||
verifyThrownExceptionBy(
|
||||
() -> softwareModuleManagement.createMetadata(
|
||||
NOT_EXIST_IDL,
|
||||
Map.of("xxx", new MetadataValueCreate("xxx"))), "SoftwareModule");
|
||||
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");
|
||||
() -> softwareModuleManagement.createMetadata(NOT_EXIST_IDL, "xxx", new MetadataValueCreate("xxx")), "SoftwareModule");
|
||||
|
||||
verifyThrownExceptionBy(() -> softwareModuleManagement.findByAssignedTo(NOT_EXIST_IDL, PAGE), "DistributionSet");
|
||||
|
||||
verifyThrownExceptionBy(() -> softwareModuleManagement.findByNameAndVersionAndType("xxx", "xxx", NOT_EXIST_IDL),
|
||||
"SoftwareModuleType");
|
||||
|
||||
verifyThrownExceptionBy(() -> softwareModuleManagement.getMetadata(NOT_EXIST_IDL, NOT_EXIST_ID), "SoftwareModule");
|
||||
|
||||
verifyThrownExceptionBy(() -> softwareModuleManagement.getMetadata(NOT_EXIST_IDL), "SoftwareModule");
|
||||
verifyThrownExceptionBy(() -> softwareModuleManagement.findByType(NOT_EXIST_IDL, PAGE), "SoftwareModule");
|
||||
|
||||
verifyThrownExceptionBy(() -> softwareModuleManagement.update(SoftwareModuleManagement.Update.builder().id(NOT_EXIST_IDL).build()),
|
||||
"SoftwareModule");
|
||||
}
|
||||
@@ -166,7 +156,7 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
*/
|
||||
@Test
|
||||
void findSoftwareModuleByFilters() {
|
||||
final SoftwareModule ah = softwareModuleManagement
|
||||
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());
|
||||
@@ -187,23 +177,8 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
implicitLock(os);
|
||||
implicitLock(jvm);
|
||||
|
||||
// standard searches
|
||||
assertThat(softwareModuleManagement.findByTextAndType("poky", osType.getId(), PAGE).getContent()).hasSize(1);
|
||||
assertThat(softwareModuleManagement.findByTextAndType("poky", osType.getId(), PAGE).getContent().get(0))
|
||||
.isEqualTo(os);
|
||||
assertThat(softwareModuleManagement.findByTextAndType("oracle", runtimeType.getId(), PAGE).getContent())
|
||||
.hasSize(1);
|
||||
assertThat(
|
||||
softwareModuleManagement.findByTextAndType("oracle", runtimeType.getId(), PAGE).getContent().get(0)).isEqualTo(jvm);
|
||||
assertThat(softwareModuleManagement.findByTextAndType(":1.0.1", appType.getId(), PAGE).getContent()).hasSize(1)
|
||||
.first().isEqualTo(ah);
|
||||
assertThat(softwareModuleManagement.findByTextAndType(":1.0", appType.getId(), PAGE).getContent()).hasSize(2);
|
||||
|
||||
distributionSetManagement.unlock(ds); // otherwise delete will be rejected as a part of a locked DS
|
||||
softwareModuleManagement.delete(ah2.getId());
|
||||
|
||||
assertThat(softwareModuleManagement.findByTextAndType(":1.0", appType.getId(), PAGE).getContent()).hasSize(1);
|
||||
assertThat(softwareModuleManagement.findByTextAndType(":1.0", appType.getId(), PAGE).getContent().get(0)).isEqualTo(ah);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,23 +190,6 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
assertThat(softwareModuleManagement.get(modules)).hasSize(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for software modules by type.
|
||||
*/
|
||||
@Test
|
||||
void findSoftwareModulesByType() {
|
||||
// found in test
|
||||
final SoftwareModule one = testdataFactory.createSoftwareModuleOs("one");
|
||||
final SoftwareModule two = testdataFactory.createSoftwareModuleOs("two");
|
||||
// ignored
|
||||
softwareModuleManagement.delete(testdataFactory.createSoftwareModuleOs("deleted").getId());
|
||||
testdataFactory.createSoftwareModuleApp();
|
||||
|
||||
assertThat((List) softwareModuleManagement.findByType(osType.getId(), PAGE).getContent())
|
||||
.as("Expected to find the following number of modules:").hasSize(2)
|
||||
.as("with the following elements").contains(two, one);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts all software modules in the repsitory that are not marked as deleted.
|
||||
*/
|
||||
@@ -244,8 +202,7 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
// ignored
|
||||
softwareModuleManagement.delete(deleted.getId());
|
||||
|
||||
assertThat(softwareModuleManagement.count()).as("Expected to find the following number of modules:")
|
||||
.isEqualTo(2);
|
||||
assertThat(softwareModuleManagement.count()).as("Expected to find the following number of modules:").isEqualTo(2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -681,13 +638,9 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
@Test
|
||||
void lockSoftwareModule() {
|
||||
final SoftwareModule softwareModule = testdataFactory.createSoftwareModule("sm-1");
|
||||
assertThat(
|
||||
softwareModuleManagement.get(softwareModule.getId()).map(SoftwareModule::isLocked).orElse(true))
|
||||
.isFalse();
|
||||
softwareModuleManagement.lock(softwareModule.getId());
|
||||
assertThat(
|
||||
softwareModuleManagement.get(softwareModule.getId()).map(SoftwareModule::isLocked).orElse(false))
|
||||
.isTrue();
|
||||
assertThat(softwareModuleManagement.get(softwareModule.getId()).map(SoftwareModule::isLocked).orElse(true)).isFalse();
|
||||
softwareModuleManagement.lock(softwareModule);
|
||||
assertThat(softwareModuleManagement.get(softwareModule.getId()).map(SoftwareModule::isLocked).orElse(false)).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -695,15 +648,11 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
*/
|
||||
@Test
|
||||
void unlockSoftwareModule() {
|
||||
final SoftwareModule softwareModule = testdataFactory.createSoftwareModule("sm-1");
|
||||
softwareModuleManagement.lock(softwareModule.getId());
|
||||
assertThat(
|
||||
softwareModuleManagement.get(softwareModule.getId()).map(SoftwareModule::isLocked).orElse(false))
|
||||
.isTrue();
|
||||
softwareModuleManagement.unlock(softwareModule.getId());
|
||||
assertThat(
|
||||
softwareModuleManagement.get(softwareModule.getId()).map(SoftwareModule::isLocked).orElse(true))
|
||||
.isFalse();
|
||||
SoftwareModule softwareModule = testdataFactory.createSoftwareModule("sm-1");
|
||||
softwareModule = softwareModuleManagement.lock(softwareModule);
|
||||
assertThat(softwareModuleManagement.get(softwareModule.getId()).map(SoftwareModule::isLocked).orElse(false)).isTrue();
|
||||
softwareModule = softwareModuleManagement.unlock(softwareModule);
|
||||
assertThat(softwareModuleManagement.get(softwareModule.getId()).map(SoftwareModule::isLocked).orElse(true)).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -711,14 +660,14 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
*/
|
||||
@Test
|
||||
void lockSoftwareModuleApplied() {
|
||||
final Long softwareModuleId = testdataFactory.createSoftwareModule("sm-1").getId();
|
||||
artifactManagement.create(
|
||||
new ArtifactUpload(new ByteArrayInputStream(new byte[] { 1 }), softwareModuleId, "artifact1", false, 1));
|
||||
final int artifactCount = softwareModuleManagement.get(softwareModuleId).get().getArtifacts().size();
|
||||
SoftwareModule softwareModule = testdataFactory.createSoftwareModule("sm-1");
|
||||
final Long softwareModuleId = softwareModule.getId();
|
||||
artifactManagement.create(new ArtifactUpload(new ByteArrayInputStream(new byte[] { 1 }), softwareModuleId, "artifact1", false, 1));
|
||||
// update software module reference since it is modified, old reference is stale
|
||||
final int artifactCount = (softwareModule = softwareModuleManagement.get(softwareModuleId).orElseThrow()).getArtifacts().size();
|
||||
assertThat(artifactCount).isNotZero();
|
||||
softwareModuleManagement.lock(softwareModuleId);
|
||||
assertThat(softwareModuleManagement.get(softwareModuleId).map(SoftwareModule::isLocked).orElse(false))
|
||||
.isTrue();
|
||||
softwareModuleManagement.lock(softwareModule);
|
||||
assertThat(softwareModuleManagement.get(softwareModuleId).map(SoftwareModule::isLocked).orElse(false)).isTrue();
|
||||
|
||||
// try add
|
||||
final ArtifactUpload artifactUpload = new ArtifactUpload(new ByteArrayInputStream(new byte[] { 2 }), softwareModuleId, "artifact2",
|
||||
@@ -807,7 +756,7 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
assertThat(artifacts).hasSize(numberArtifacts);
|
||||
if (numberArtifacts != 0) {
|
||||
assertArtifactNotNull(artifacts.toArray(new Artifact[artifacts.size()]));
|
||||
assertArtifactNotNull(artifacts.toArray(new Artifact[0]));
|
||||
}
|
||||
|
||||
artifacts.forEach(artifact -> assertThat(artifactRepository.findById(artifact.getId())).isNotNull());
|
||||
|
||||
@@ -248,15 +248,14 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
assertThat(assignedTargets).as("Assigned targets are wrong").hasSize(4);
|
||||
assignedTargets.forEach(target -> assertThat(getTargetTags(target.getControllerId())).hasSize(1));
|
||||
|
||||
final TargetTag findTargetTag = targetTagManagement.getByName("Tag1").orElseThrow(IllegalStateException::new);
|
||||
final TargetTag findTargetTag = targetTagManagement.get(targetTag.getId()).orElseThrow(IllegalStateException::new);
|
||||
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);
|
||||
assertThat(unAssignTarget.getControllerId()).as("Controller id is wrong").isEqualTo("targetId123");
|
||||
assertThat(getTargetTags(unAssignTarget.getControllerId())).as("Tag size is wrong")
|
||||
.isEmpty();
|
||||
targetTagManagement.getByName("Tag1").orElseThrow(NoSuchElementException::new);
|
||||
assertThat(getTargetTags(unAssignTarget.getControllerId())).as("Tag size is wrong").isEmpty();
|
||||
targetTagManagement.get(targetTag.getId()).orElseThrow(NoSuchElementException::new);
|
||||
assertThat(targetManagement.findByTag(targetTag.getId(), PAGE)).as("Assigned targets are wrong").hasSize(3);
|
||||
assertThat(targetManagement.findByRsqlAndTag("controllerId==targetId123", targetTag.getId(), PAGE))
|
||||
.as("Assigned targets are wrong").isEmpty();
|
||||
|
||||
@@ -79,14 +79,6 @@ class TargetTagManagementSecurityTest extends AbstractJpaIntegrationTest {
|
||||
assertPermissions(() -> targetTagManagement.findByRsql("name==tag", PAGE), List.of(SpPermission.READ_TARGET));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
|
||||
*/
|
||||
@Test
|
||||
void getByNamePermissionsCheck() {
|
||||
assertPermissions(() -> targetTagManagement.getByName("tag"), List.of(SpPermission.READ_TARGET));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
|
||||
*/
|
||||
|
||||
@@ -91,7 +91,6 @@ class TargetTagManagementTest extends AbstractJpaIntegrationTest {
|
||||
@Test
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class) })
|
||||
void nonExistingEntityAccessReturnsNotPresent() {
|
||||
assertThat(targetTagManagement.getByName(NOT_EXIST_ID)).isNotPresent();
|
||||
assertThat(targetTagManagement.get(NOT_EXIST_IDL)).isNotPresent();
|
||||
}
|
||||
|
||||
@@ -176,8 +175,6 @@ class TargetTagManagementTest extends AbstractJpaIntegrationTest {
|
||||
@Test
|
||||
void createTargetTag() {
|
||||
final Tag tag = targetTagManagement.create(Create.builder().name("k1").description("k2").colour("colour").build());
|
||||
assertThat(targetTagRepository.findByNameEquals("k1").orElseThrow().getDescription()).as("wrong tag ed").isEqualTo("k2");
|
||||
assertThat(targetTagManagement.getByName("k1").orElseThrow().getColour()).as("wrong tag found").isEqualTo("colour");
|
||||
assertThat(targetTagManagement.get(tag.getId()).orElseThrow().getColour()).as("wrong tag found").isEqualTo("colour");
|
||||
}
|
||||
|
||||
|
||||
@@ -51,14 +51,6 @@ class TargetTypeManagementSecurityTest extends AbstractJpaIntegrationTest {
|
||||
assertPermissions(() -> targetTypeManagement.count(), List.of(SpPermission.READ_TARGET));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
|
||||
*/
|
||||
@Test
|
||||
void countByNamePermissionsCheck() {
|
||||
assertPermissions(() -> targetTypeManagement.countByName("name"), List.of(SpPermission.READ_TARGET));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
|
||||
*/
|
||||
@@ -104,14 +96,6 @@ class TargetTypeManagementSecurityTest extends AbstractJpaIntegrationTest {
|
||||
assertPermissions(() -> targetTypeManagement.findByRsql("name==tag", PAGE), List.of(SpPermission.READ_TARGET));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
|
||||
*/
|
||||
@Test
|
||||
void findByNamePermissionsCheck() {
|
||||
assertPermissions(() -> targetTypeManagement.findByName("name", PAGE), List.of(SpPermission.READ_TARGET));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user