Small hawkbit-repository-test refactoring (#2608)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-08-13 16:41:25 +03:00
committed by GitHub
parent 8abf7275c4
commit fa4dea75a3
18 changed files with 115 additions and 233 deletions

View File

@@ -24,7 +24,7 @@ import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
import org.eclipse.hawkbit.repository.test.TestConfiguration;
import org.eclipse.hawkbit.repository.test.matcher.EventVerifier;
import org.eclipse.hawkbit.repository.test.util.CleanupTestExecutionListener;
import org.eclipse.hawkbit.repository.test.util.JUnitTestLoggerExtension;
import org.eclipse.hawkbit.repository.test.util.TestLoggerExtension;
import org.eclipse.hawkbit.repository.test.util.SharedSqlTestDatabaseExtension;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.RestConfiguration;
@@ -49,37 +49,35 @@ import org.springframework.web.context.WebApplicationContext;
/**
* Test for {@link MgmtBasicAuthResource}.
* <p/>
* Feature: Component Tests - Management API<br/>
* Story: Basic auth Userinfo Resource
*/
@ActiveProfiles({ "test" })
@ExtendWith({ JUnitTestLoggerExtension.class, SharedSqlTestDatabaseExtension.class })
@ExtendWith({ TestLoggerExtension.class, SharedSqlTestDatabaseExtension.class })
@SpringBootTest
// destroy the context after each test class because otherwise we get problem
// when context is
// refreshed we e.g. get two instances of CacheManager which leads to very
// strange test failures.
// destroy the context after each test class because otherwise we get problem when context is
// refreshed we e.g. get two instances of CacheManager which leads to very strange test failures.
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
// Cleaning repository will fire "delete" events. We won't count them to the
// test execution. So, the order execution between EventVerifier and Cleanup is
// important!
@TestExecutionListeners(listeners = { EventVerifier.class, CleanupTestExecutionListener.class },
// Cleaning repository will fire "delete" events. We won't count them to the test execution. So, the order execution between EventVerifier and
// Cleanup is important!
@TestExecutionListeners(
listeners = { EventVerifier.class, CleanupTestExecutionListener.class },
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
@TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true")
@WebAppConfiguration
@AutoConfigureMockMvc
@ContextConfiguration(classes = { MgmtApiConfiguration.class, RestConfiguration.class,
JpaRepositoryConfiguration.class, TestConfiguration.class })
/**
* Feature: Component Tests - Management API<br/>
* Story: Basic auth Userinfo Resource
*/
@ContextConfiguration(
classes = { MgmtApiConfiguration.class, RestConfiguration.class, JpaRepositoryConfiguration.class, TestConfiguration.class })
class MgmtBasicAuthResourceTest {
private static final String DEFAULT_TENANT = "DEFAULT";
private static final String TEST_USER = "testUser";
@Autowired
protected WebApplicationContext webApplicationContext;
@Autowired
MockMvc defaultMock;
private static final String DEFAULT_TENANT = "DEFAULT";
private static final String TEST_USER = "testUser";
/**
* Test of userinfo api with basic auth validation

View File

@@ -106,7 +106,7 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
enableMultiAssignments();
approvalStrategy.setApprovalNeeded(true);
try {
approvalStrategy.setApproveDecidedBy("exampleUsername");
approvalStrategy.setApprovalDecidedBy("exampleUsername");
testdataFactory.createTargets(4, "rollout", "description");
final DistributionSet dsA = testdataFactory.createDistributionSet("");
@@ -198,7 +198,7 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
@Test
void validateIfApprovalFieldsArePresentAfterApproval() throws Exception {
approvalStrategy.setApprovalNeeded(true);
approvalStrategy.setApproveDecidedBy("testUser");
approvalStrategy.setApprovalDecidedBy("testUser");
final int amountTargets = 2;
final String remark = "Some remark";
testdataFactory.createTargets(amountTargets, "rollout");

View File

@@ -31,7 +31,7 @@ import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy;
import org.eclipse.hawkbit.repository.test.util.SystemManagementHolder;
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SecurityContextSerializer;
@@ -127,13 +127,10 @@ public class TestConfiguration implements AsyncConfigurer {
return new ArtifactFilesystemRepository(artifactFilesystemProperties);
}
/**
* @return the {@link org.eclipse.hawkbit.repository.test.util.SystemManagementHolder} singleton bean which holds the current
* {@link SystemManagement} service and make it accessible in beans which cannot access the service directly, e.g. JPA entities.
*/
/** @return the {@link org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch} to be injected. */
@Bean
SystemManagementHolder systemManagementHolder() {
return SystemManagementHolder.getInstance();
SecurityContextSwitch securityContextSwitch() {
return SecurityContextSwitch.getInstance();
}
@Bean

View File

@@ -1,41 +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.test.matcher;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
/**
* Matcher for {@link BaseEntity}.
*/
public final class BaseEntityMatcher {
private BaseEntityMatcher() {
}
public static Matcher<BaseEntity> hasId(final Long id) {
return new HasIdMatcher(Matchers.equalTo(id));
}
private static class HasIdMatcher extends FeatureMatcher<BaseEntity, Long> {
public HasIdMatcher(final Matcher<Long> subMatcher) {
super(subMatcher, "getId()", "id");
}
@Override
protected Long featureValueOf(final BaseEntity baseEntity) {
return baseEntity.getId();
}
}
}

View File

@@ -99,7 +99,7 @@ import org.springframework.test.context.TestPropertySource;
@Slf4j
@ActiveProfiles({ "test" })
@ExtendWith({ JUnitTestLoggerExtension.class, SharedSqlTestDatabaseExtension.class })
@ExtendWith({ TestLoggerExtension.class, SharedSqlTestDatabaseExtension.class })
@WithUser(principal = "bumlux", allSpPermissions = true, authorities = { CONTROLLER_ROLE, SYSTEM_ROLE })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ContextConfiguration(classes = { TestConfiguration.class })

View File

@@ -9,28 +9,58 @@
*/
package org.eclipse.hawkbit.repository.test.util;
import java.util.List;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.AbstractTestExecutionListener;
/**
* A spring {@link TestExecutionListener} which cleansup the repository after
* each test-method.
* A spring {@link TestExecutionListener} which cleans up the repository after each test-method.
*/
@Slf4j
public class CleanupTestExecutionListener extends AbstractTestExecutionListener {
private static final Pageable PAGE = PageRequest.of(0, 400, Sort.by(Sort.Direction.ASC, "id"));
@Override
public void afterTestMethod(final TestContext testContext) throws Exception {
public void afterTestMethod(@NotNull final TestContext testContext) throws Exception {
SecurityContextSwitch.callAsPrivileged(() -> {
final ApplicationContext applicationContext = testContext.getApplicationContext();
new JpaTestRepositoryManagement(applicationContext.getBean(TenantAwareCacheManager.class),
clearTestRepository(
applicationContext.getBean(TenantAwareCacheManager.class),
applicationContext.getBean(SystemSecurityContext.class),
applicationContext.getBean(SystemManagement.class)).clearTestRepository();
applicationContext.getBean(SystemManagement.class));
return null;
});
}
private void clearTestRepository(
final TenantAwareCacheManager cacheManager,
final SystemSecurityContext systemSecurityContext,
final SystemManagement systemManagement) {
final List<String> tenants = systemSecurityContext.runAsSystem(() -> systemManagement.findTenants(PAGE).getContent());
tenants.forEach(tenant -> {
try {
systemSecurityContext.runAsSystem(() -> {
systemManagement.deleteTenant(tenant);
return null;
});
} catch (final Exception e) {
log.error("Error while delete tenant", e);
}
});
cacheManager.getDirectCacheNames().forEach(name -> cacheManager.getDirectCache(name).clear());
}
}

View File

@@ -17,13 +17,13 @@ import lombok.extern.slf4j.Slf4j;
*/
@Getter
@Slf4j
public class DatasourceContext {
class DatasourceContext {
public static final String SPRING_DATASOURCE_URL_KEY = "spring.datasource.url";
public static final String SPRING_DATABASE_KEY = "spring.jpa.database";
public static final String SPRING_DATABASE_USERNAME_KEY = "spring.datasource.username";
public static final String SPRING_DATABASE_PASSWORD_KEY = "spring.datasource.password";
public static final String DATABASE_PREFIX_KEY = "spring.database.random.prefix";
static final String SPRING_DATASOURCE_URL_KEY = "spring.datasource.url";
static final String SPRING_DATABASE_KEY = "spring.jpa.database";
static final String SPRING_DATABASE_USERNAME_KEY = "spring.datasource.username";
static final String SPRING_DATABASE_PASSWORD_KEY = "spring.datasource.password";
static final String DATABASE_PREFIX_KEY = "spring.database.random.prefix";
private static final String RANDOM_DB_PREFIX = System.getProperty(DATABASE_PREFIX_KEY, "HAWKBIT_TEST_");
@@ -33,17 +33,14 @@ public class DatasourceContext {
private final String password;
private final String randomSchemaName = RANDOM_DB_PREFIX + TestdataFactory.randomString(10);
public DatasourceContext(final String database, final String datasourceUrl, final String username, final String password) {
DatasourceContext(final String database, final String datasourceUrl, final String username, final String password) {
this.database = database;
this.datasourceUrl = datasourceUrl;
this.username = username;
this.password = password;
}
/**
* Constructor
*/
public DatasourceContext() {
DatasourceContext() {
database = System.getProperty(SPRING_DATABASE_KEY, System.getProperty(upperCaseVariant(SPRING_DATABASE_KEY)));
datasourceUrl = System.getProperty(SPRING_DATASOURCE_URL_KEY, System.getProperty(upperCaseVariant(SPRING_DATASOURCE_URL_KEY)));
username = System.getProperty(SPRING_DATABASE_USERNAME_KEY, System.getProperty(upperCaseVariant(SPRING_DATABASE_USERNAME_KEY)));

View File

@@ -12,8 +12,7 @@ package org.eclipse.hawkbit.repository.test.util;
import org.junit.jupiter.api.extension.Extension;
/**
* An {@link Extension} for creating and dropping H2 schemas if
* tests are set up with H2.
* An {@link Extension} for creating and dropping H2 schemas if tests are set up with H2.
*/
public class H2TestDatabase extends AbstractSqlTestDatabase {
@@ -36,4 +35,4 @@ public class H2TestDatabase extends AbstractSqlTestDatabase {
protected String getRandomSchemaUri() {
return "jdbc:h2:mem:" + context.getRandomSchemaName() + ";MODE=MySQL;";
}
}
}

View File

@@ -1,67 +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.test.util;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
@Slf4j
public class JpaTestRepositoryManagement {
private static final Pageable PAGE = PageRequest.of(0, 400, Sort.by(Direction.ASC, "id"));
private final TenantAwareCacheManager cacheManager;
private final SystemSecurityContext systemSecurityContext;
private final SystemManagement systemManagement;
/**
* Constructor.
*
* @param cacheManager the cachemanager
* @param systemSecurityContext the systemSecurityContext
* @param systemManagement the systemManagement
*/
public JpaTestRepositoryManagement(final TenantAwareCacheManager cacheManager,
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement) {
this.cacheManager = cacheManager;
this.systemSecurityContext = systemSecurityContext;
this.systemManagement = systemManagement;
}
public void clearTestRepository() {
deleteAllRepos();
cacheManager.getDirectCacheNames().forEach(name -> cacheManager.getDirectCache(name).clear());
}
public void deleteAllRepos() {
final List<String> tenants = systemSecurityContext
.runAsSystem(() -> systemManagement.findTenants(PAGE).getContent());
tenants.forEach(tenant -> {
try {
systemSecurityContext.runAsSystem(() -> {
systemManagement.deleteTenant(tenant);
return null;
});
} catch (final Exception e) {
log.error("Error while delete tenant", e);
}
});
}
}

View File

@@ -61,4 +61,4 @@ public class MySqlTestDatabase extends AbstractSqlTestDatabase {
private static String getSchemaName(final String datasourceUrl) {
return MATCHER.extractUriTemplateVariables(MYSQL_URI_PATTERN, datasourceUrl).get("db");
}
}
}

View File

@@ -15,8 +15,7 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.extension.Extension;
/**
* An {@link Extension} for creating and dropping MySql schemas if
* tests are set up with MySql.
* An {@link Extension} for creating and dropping PostgreSQL schemas if tests are set up with PostgreSQL.
*/
@Slf4j
public class PostgreSqlTestDatabase extends AbstractSqlTestDatabase {
@@ -62,4 +61,4 @@ public class PostgreSqlTestDatabase extends AbstractSqlTestDatabase {
.get("db")
.split("\\?")[0];
}
}
}

View File

@@ -9,17 +9,21 @@
*/
package org.eclipse.hawkbit.repository.test.util;
import lombok.EqualsAndHashCode;
import lombok.Setter;
import lombok.ToString;
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
import org.eclipse.hawkbit.repository.model.Rollout;
/**
* Provides an approval strategy that can be manipulated by setting the {link
* {@link #approvalNeeded}} flag used for testing.
* Provides an approval strategy that can be manipulated by setting the {@link #approvalNeeded}} flag used for testing.
*/
@Setter
@EqualsAndHashCode
@ToString
public class RolloutTestApprovalStrategy implements RolloutApprovalStrategy {
private boolean approvalNeeded = false;
private String approvalDecidedBy;
@Override
@@ -27,21 +31,13 @@ public class RolloutTestApprovalStrategy implements RolloutApprovalStrategy {
return approvalNeeded;
}
@Override
public void onApprovalRequired(Rollout rollout) {
// do nothing, as no action is needed when testing
}
@Override
public String getApprovalUser(Rollout rollout) {
return approvalDecidedBy;
}
public void setApprovalNeeded(boolean approvalNeeded) {
this.approvalNeeded = approvalNeeded;
@Override
public void onApprovalRequired(Rollout rollout) {
// do nothing, as no action is needed when testing
}
public void setApproveDecidedBy(final String user) {
this.approvalDecidedBy = user;
}
}
}

View File

@@ -22,24 +22,43 @@ import java.util.function.Supplier;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.tenancy.TenantAwareUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("java:S6548") // java:S6548 - singleton holder ensures static access to spring resources in some places
public class SecurityContextSwitch {
public static final String DEFAULT_TENANT = "DEFAULT";
private static final SecurityContextSwitch INSTANCE = new SecurityContextSwitch();
private static final WithUser PRIVILEDGED_USER =
createWithUser("bumlux", DEFAULT_TENANT, false, true, false, "ROLE_CONTROLLER", "ROLE_SYSTEM_CODE");
public static final String DEFAULT_TENANT = "DEFAULT";
private static final WithUser PRIVILEGED_USER = createWithUser(
"bumlux", DEFAULT_TENANT, false, true, false, "ROLE_CONTROLLER", "ROLE_SYSTEM_CODE");
private static SystemManagement systemManagement;
/**
* @return the singleton {@link SecurityContextSwitch} instance to be injected
*/
public static SecurityContextSwitch getInstance() {
return INSTANCE;
}
@SuppressWarnings("java:S2696") // intentionally, we want, after registering as bean the instance to have injected system management
@Autowired // spring setter injection
public void setSystemManagement(final SystemManagement systemManagement) {
SecurityContextSwitch.systemManagement = systemManagement;
}
public static <T> T callAsPrivileged(final Callable<T> callable) throws Exception {
createTenant(DEFAULT_TENANT);
return callAs(PRIVILEDGED_USER, callable);
return callAs(PRIVILEGED_USER, callable);
}
public static <T> T callAs(final WithUser withUser, final Callable<T> callable) throws Exception {
@@ -96,9 +115,9 @@ public class SecurityContextSwitch {
private static void createTenant(final String tenantId) {
final SecurityContext oldContext = SecurityContextHolder.getContext();
setSecurityContext(PRIVILEDGED_USER);
setSecurityContext(PRIVILEGED_USER);
try {
SystemManagementHolder.getInstance().getSystemManagement().createTenantMetadata(tenantId);
systemManagement.createTenantMetadata(tenantId);
} finally {
SecurityContextHolder.setContext(oldContext);
}
@@ -241,4 +260,4 @@ public class SecurityContextSwitch {
return controller;
}
}
}
}

View File

@@ -23,7 +23,7 @@ import org.junit.jupiter.api.extension.ExtensionContext;
@Slf4j
public class SharedSqlTestDatabaseExtension implements BeforeAllCallback {
protected static final AtomicReference<DatasourceContext> CONTEXT = new AtomicReference<>();
static final AtomicReference<DatasourceContext> CONTEXT = new AtomicReference<>();
@Override
public void beforeAll(final ExtensionContext extensionContext) {

View File

@@ -1,42 +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.test.util;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds {@link SystemManagement} service and makes it
* accessible to beans which are not managed by spring, e.g. JPA entities.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("java:S6548") // java:S6548 - singleton holder ensures static access to spring resources in some places
public final class SystemManagementHolder {
private static final SystemManagementHolder INSTANCE = new SystemManagementHolder();
@Getter
private SystemManagement systemManagement;
/**
* @return the singleton {@link SystemManagementHolder} instance
*/
public static SystemManagementHolder getInstance() {
return INSTANCE;
}
@Autowired // spring setter injection
public void setSystemManagement(final SystemManagement systemManagement) {
this.systemManagement = systemManagement;
}
}

View File

@@ -12,8 +12,10 @@ package org.eclipse.hawkbit.repository.test.util;
import java.security.SecureRandom;
import java.util.Random;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.model.Target;
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public class TargetTestData {
public static final String ATTRIBUTE_KEY_TOO_LONG;
@@ -29,10 +31,6 @@ public class TargetTestData {
ATTRIBUTE_VALUE_VALID = generateRandomStringWithLength(Target.CONTROLLER_ATTRIBUTE_MAX_VALUE_SIZE, rand);
}
private TargetTestData() {
// nothing to do here
}
private static String generateRandomStringWithLength(final int length, final Random rand) {
final StringBuilder randomStringBuilder = new StringBuilder(length);
final int lowercaseACode = 97;
@@ -44,4 +42,4 @@ public class TargetTestData {
}
return randomStringBuilder.toString();
}
}
}

View File

@@ -15,7 +15,7 @@ import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestWatcher;
@Slf4j
public class JUnitTestLoggerExtension implements BeforeTestExecutionCallback, TestWatcher {
public class TestLoggerExtension implements BeforeTestExecutionCallback, TestWatcher {
@Override
public void testSuccessful(final ExtensionContext context) {
@@ -31,4 +31,4 @@ public class JUnitTestLoggerExtension implements BeforeTestExecutionCallback, Te
public void beforeTestExecution(final ExtensionContext context) {
log.info("Starting Test {}...", context.getTestMethod());
}
}
}

View File

@@ -20,8 +20,7 @@ import org.springframework.security.test.context.support.WithSecurityContext;
import org.springframework.security.test.context.support.WithSecurityContextFactory;
/**
* Annotation to run test classes or test methods with a specific user with
* specific permissions.
* Annotation to run test classes or test methods with a specific user with specific permissions.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })