login-credentials without RBAC (#173)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/173 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
@@ -49,6 +49,7 @@ public class ArchitectureTest {
|
||||
"..test.pac",
|
||||
"..test.dom",
|
||||
"..context",
|
||||
"..credentials",
|
||||
"..hash",
|
||||
"..lambda",
|
||||
"..generated..",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package net.hostsharing.hsadminng.credentials;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class HsCredentialsContextRbacEntityUnitTest {
|
||||
|
||||
@Test
|
||||
void toShortString() {
|
||||
final var entity = HsCredentialsContextRbacEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("SSH")
|
||||
.qualifier("prod")
|
||||
.build();
|
||||
assertEquals("loginContext(SSH:prod)", entity.toShortString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package net.hostsharing.hsadminng.credentials;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.postgresql.util.PSQLException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
|
||||
@DataJpaTest
|
||||
@ActiveProfiles("test")
|
||||
@Tag("generalIntegrationTest")
|
||||
@Import({ Context.class, JpaAttempt.class })
|
||||
@Transactional
|
||||
class HsCredentialsContextRbacRepositoryIntegrationTest extends ContextBasedTest {
|
||||
|
||||
// existing UUIDs from test data (Liquibase changeset 310-login-credentials-test-data.sql)
|
||||
private static final UUID TEST_CONTEXT_HSADMIN_PROD_UUID = UUID.fromString("11111111-1111-1111-1111-111111111111");
|
||||
private static final UUID TEST_CONTEXT_MATRIX_INTERNAL_UUID = UUID.fromString("33333333-3333-3333-3333-333333333333");
|
||||
|
||||
private static final String SUPERUSER_ALEX_SUBJECT_NAME = "superuser-alex@hostsharing.net";
|
||||
private static final String TEST_USER_SUBJECT_NAME = "selfregistered-test-user@hostsharing.org";
|
||||
|
||||
@MockitoBean
|
||||
HttpServletRequest request;
|
||||
|
||||
@Autowired
|
||||
private HsCredentialsContextRbacRepository loginContextRepository;
|
||||
|
||||
@Test
|
||||
void shouldFindAllByNormalUserUsingTestData() {
|
||||
context(TEST_USER_SUBJECT_NAME);
|
||||
|
||||
// when
|
||||
final var allContexts = loginContextRepository.findAll();
|
||||
|
||||
// then
|
||||
assertThat(allContexts)
|
||||
.isNotNull()
|
||||
.hasSizeGreaterThanOrEqualTo(1) // Expect at least the 1 public context from assumed test data
|
||||
.extracting(HsCredentialsContext::getUuid)
|
||||
.contains(TEST_CONTEXT_HSADMIN_PROD_UUID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindAllByAdminUserUsingTestData() {
|
||||
context(SUPERUSER_ALEX_SUBJECT_NAME);
|
||||
|
||||
// when
|
||||
final var allContexts = loginContextRepository.findAll();
|
||||
|
||||
// then
|
||||
assertThat(allContexts)
|
||||
.isNotNull()
|
||||
.hasSizeGreaterThanOrEqualTo(3); // Expect at least the 1 public context from assumed test data
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindByUuidUsingTestData() {
|
||||
context(TEST_USER_SUBJECT_NAME);
|
||||
|
||||
// when
|
||||
final var foundEntityOptional = loginContextRepository.findByUuid(TEST_CONTEXT_HSADMIN_PROD_UUID);
|
||||
|
||||
// then
|
||||
assertThat(foundEntityOptional).isPresent();
|
||||
assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(HSADMIN:prod)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindByTypeAndQualifierUsingTestData() {
|
||||
context(SUPERUSER_ALEX_SUBJECT_NAME);
|
||||
|
||||
// when
|
||||
final var foundEntityOptional = loginContextRepository.findByTypeAndQualifier("SSH", "internal");
|
||||
|
||||
// then
|
||||
assertThat(foundEntityOptional).isPresent();
|
||||
assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(SSH:internal)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyOptionalWhenFindByTypeAndQualifierNotFound() {
|
||||
context(SUPERUSER_ALEX_SUBJECT_NAME);
|
||||
|
||||
// given
|
||||
final var nonExistentQualifier = "non-existent-qualifier";
|
||||
|
||||
// when
|
||||
final var foundEntityOptional = loginContextRepository.findByTypeAndQualifier(
|
||||
"HSADMIN", nonExistentQualifier);
|
||||
|
||||
// then
|
||||
assertThat(foundEntityOptional).isNotPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveNewLoginContext() {
|
||||
context(SUPERUSER_ALEX_SUBJECT_NAME);
|
||||
|
||||
// given
|
||||
final var newQualifier = "test@example.social";
|
||||
final var newType = "MASTODON";
|
||||
final var newContext = HsCredentialsContextRbacEntity.builder()
|
||||
.type(newType)
|
||||
.qualifier(newQualifier)
|
||||
.build();
|
||||
|
||||
// when
|
||||
final var savedEntity = loginContextRepository.save(newContext);
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
// then
|
||||
assertThat(savedEntity).isNotNull();
|
||||
final var generatedUuid = savedEntity.getUuid();
|
||||
assertThat(generatedUuid).isNotNull(); // Verify UUID was generated
|
||||
|
||||
// Fetch again using the generated UUID to confirm persistence
|
||||
context(SUPERUSER_ALEX_SUBJECT_NAME); // Re-set context if needed after clear
|
||||
final var foundEntityOptional = loginContextRepository.findByUuid(generatedUuid);
|
||||
assertThat(foundEntityOptional).isPresent();
|
||||
final var foundEntity = foundEntityOptional.get();
|
||||
assertThat(foundEntity.getUuid()).isEqualTo(generatedUuid);
|
||||
assertThat(foundEntity.getType()).isEqualTo(newType);
|
||||
assertThat(foundEntity.getQualifier()).isEqualTo(newQualifier);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPreventUpdateOfExistingLoginContext() {
|
||||
context(SUPERUSER_ALEX_SUBJECT_NAME);
|
||||
|
||||
// given an existing entity from test data
|
||||
final var entityToUpdateOptional = loginContextRepository.findByUuid(TEST_CONTEXT_MATRIX_INTERNAL_UUID);
|
||||
assertThat(entityToUpdateOptional)
|
||||
.withFailMessage("Could not find existing LoginContext with UUID %s. Ensure test data exists.",
|
||||
TEST_CONTEXT_MATRIX_INTERNAL_UUID)
|
||||
.isPresent();
|
||||
final var entityToUpdate = entityToUpdateOptional.get();
|
||||
|
||||
// when
|
||||
entityToUpdate.setQualifier("updated");
|
||||
final var exception = catchThrowable( () -> {
|
||||
loginContextRepository.save(entityToUpdate);
|
||||
em.flush();
|
||||
});
|
||||
|
||||
// then
|
||||
assertThat(exception)
|
||||
.isInstanceOf(PersistenceException.class)
|
||||
.hasCauseInstanceOf(PSQLException.class)
|
||||
.hasMessageContaining("ERROR: Updates to hs_credentials.context are not allowed.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package net.hostsharing.hsadminng.credentials;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class HsCredentialsContextRealEntityUnitTest {
|
||||
|
||||
@Test
|
||||
void toShortString() {
|
||||
final var entity = HsCredentialsContextRealEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("testType")
|
||||
.qualifier("testQualifier")
|
||||
.build();
|
||||
assertEquals("loginContext(testType:testQualifier)", entity.toShortString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package net.hostsharing.hsadminng.credentials;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.postgresql.util.PSQLException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
|
||||
@DataJpaTest
|
||||
@ActiveProfiles("test")
|
||||
@Tag("generalIntegrationTest")
|
||||
@Import({ Context.class, JpaAttempt.class })
|
||||
class HsCredentialsContextRealRepositoryIntegrationTest extends ContextBasedTest {
|
||||
|
||||
// existing UUIDs from test data (Liquibase changeset 310-login-credentials-test-data.sql)
|
||||
private static final UUID TEST_CONTEXT_HSADMIN_PROD_UUID = UUID.fromString("11111111-1111-1111-1111-111111111111");
|
||||
private static final UUID TEST_CONTEXT_SSH_INTERNAL_UUID = UUID.fromString("22222222-2222-2222-2222-222222222222");
|
||||
private static final UUID TEST_CONTEXT_MATRIX_INTERNAL_UUID = UUID.fromString("33333333-3333-3333-3333-333333333333");
|
||||
|
||||
private static final String SUPERUSER_ALEX_SUBJECT_NAME = "superuser-alex@hostsharing.net";
|
||||
private static final String TEST_USER_SUBJECT_NAME = "selfregistered-test-user@hostsharing.org";
|
||||
|
||||
@MockitoBean
|
||||
HttpServletRequest request;
|
||||
|
||||
@Autowired
|
||||
private HsCredentialsContextRealRepository loginContextRepository;
|
||||
|
||||
@Test
|
||||
public void historizationIsAvailable() {
|
||||
// given
|
||||
final String nativeQuerySql = "select * from hs_credentials.context_hv";
|
||||
|
||||
// when
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().minusDays(1).toInstant()));
|
||||
final var query = em.createNativeQuery(nativeQuerySql);
|
||||
final var rowsBefore = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(rowsBefore).as("hs_credentials.context_hv only contain no rows for a timestamp before test data creation").hasSize(0);
|
||||
|
||||
// and when
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().toInstant()));
|
||||
em.createNativeQuery(nativeQuerySql, Integer.class);
|
||||
final var rowsAfter = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(rowsAfter).as("hs_credentials.context_hv should now contain the test-data rows for the current timestamp").hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindAllUsingTestData() {
|
||||
context(TEST_USER_SUBJECT_NAME);
|
||||
|
||||
// when
|
||||
final var allContexts = loginContextRepository.findAll();
|
||||
|
||||
// then
|
||||
assertThat(allContexts)
|
||||
.isNotNull()
|
||||
.hasSizeGreaterThanOrEqualTo(3) // Expect at least the 3 from assumed test data
|
||||
.extracting(HsCredentialsContext::getUuid)
|
||||
.contains(TEST_CONTEXT_HSADMIN_PROD_UUID, TEST_CONTEXT_SSH_INTERNAL_UUID, TEST_CONTEXT_MATRIX_INTERNAL_UUID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindByUuidUsingTestData() {
|
||||
context(TEST_USER_SUBJECT_NAME);
|
||||
|
||||
// when
|
||||
final var foundEntityOptional = loginContextRepository.findByUuid(TEST_CONTEXT_HSADMIN_PROD_UUID);
|
||||
|
||||
// then
|
||||
assertThat(foundEntityOptional).isPresent();
|
||||
assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(HSADMIN:prod)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindByTypeAndQualifierUsingTestData() {
|
||||
context(TEST_USER_SUBJECT_NAME);
|
||||
|
||||
// when
|
||||
final var foundEntityOptional = loginContextRepository.findByTypeAndQualifier("SSH", "internal");
|
||||
|
||||
// then
|
||||
assertThat(foundEntityOptional).isPresent();
|
||||
assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(SSH:internal)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyOptionalWhenFindByTypeAndQualifierNotFound() {
|
||||
context(TEST_USER_SUBJECT_NAME);
|
||||
|
||||
// given
|
||||
final var nonExistentQualifier = "non-existent-qualifier";
|
||||
|
||||
// when
|
||||
final var foundEntityOptional = loginContextRepository.findByTypeAndQualifier(
|
||||
"HSADMIN", nonExistentQualifier);
|
||||
|
||||
// then
|
||||
assertThat(foundEntityOptional).isNotPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveNewLoginContext() {
|
||||
context(SUPERUSER_ALEX_SUBJECT_NAME);
|
||||
|
||||
// given
|
||||
final var newQualifier = "test@example.social";
|
||||
final var newType = "MASTODON";
|
||||
final var newContext = HsCredentialsContextRealEntity.builder()
|
||||
.type(newType)
|
||||
.qualifier(newQualifier)
|
||||
.build();
|
||||
|
||||
// when
|
||||
final var savedEntity = loginContextRepository.save(newContext);
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
// then
|
||||
assertThat(savedEntity).isNotNull();
|
||||
final var generatedUuid = savedEntity.getUuid();
|
||||
assertThat(generatedUuid).isNotNull(); // Verify UUID was generated
|
||||
|
||||
// Fetch again using the generated UUID to confirm persistence
|
||||
context(TEST_USER_SUBJECT_NAME); // Re-set context if needed after clear
|
||||
final var foundEntityOptional = loginContextRepository.findByUuid(generatedUuid);
|
||||
assertThat(foundEntityOptional).isPresent();
|
||||
final var foundEntity = foundEntityOptional.get();
|
||||
assertThat(foundEntity.getUuid()).isEqualTo(generatedUuid);
|
||||
assertThat(foundEntity.getType()).isEqualTo(newType);
|
||||
assertThat(foundEntity.getQualifier()).isEqualTo(newQualifier);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPreventUpdateOfExistingLoginContext() {
|
||||
context(TEST_USER_SUBJECT_NAME);
|
||||
|
||||
// given an existing entity from test data
|
||||
final var entityToUpdateOptional = loginContextRepository.findByUuid(TEST_CONTEXT_MATRIX_INTERNAL_UUID);
|
||||
assertThat(entityToUpdateOptional)
|
||||
.withFailMessage("Could not find existing LoginContext with UUID %s. Ensure test data exists.",
|
||||
TEST_CONTEXT_MATRIX_INTERNAL_UUID)
|
||||
.isPresent();
|
||||
final var entityToUpdate = entityToUpdateOptional.get();
|
||||
|
||||
// when
|
||||
entityToUpdate.setQualifier("updated");
|
||||
final var exception = catchThrowable( () -> {
|
||||
loginContextRepository.save(entityToUpdate);
|
||||
em.flush();
|
||||
});
|
||||
|
||||
// then
|
||||
assertThat(exception)
|
||||
.isInstanceOf(PersistenceException.class)
|
||||
.hasCauseInstanceOf(PSQLException.class)
|
||||
.hasMessageContaining("ERROR: Updates to hs_credentials.context are not allowed.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package net.hostsharing.hsadminng.credentials;
|
||||
|
||||
import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import net.hostsharing.hsadminng.config.DisableSecurityConfig;
|
||||
import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration;
|
||||
import net.hostsharing.hsadminng.config.MessageTranslator;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.SynchronizationType;
|
||||
|
||||
@WebMvcTest(HsCredentialsContextsController.class)
|
||||
@Import({ StrictMapper.class, JsonObjectMapperConfiguration.class, DisableSecurityConfig.class, MessageTranslator.class})
|
||||
@ActiveProfiles("test")
|
||||
class HsCredentialsContextsControllerRestTest {
|
||||
|
||||
@Autowired
|
||||
MockMvc mockMvc;
|
||||
|
||||
@MockitoBean
|
||||
Context contextMock;
|
||||
|
||||
@Autowired
|
||||
@SuppressWarnings("unused") // not used in test, but in controller class
|
||||
StrictMapper mapper;
|
||||
|
||||
@MockitoBean
|
||||
EntityManagerWrapper em;
|
||||
|
||||
@MockitoBean
|
||||
EntityManagerFactory emf;
|
||||
|
||||
@MockitoBean
|
||||
HsCredentialsContextRbacRepository loginContextRbacRepo;
|
||||
|
||||
|
||||
@TestConfiguration
|
||||
public static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public EntityManager entityManager() {
|
||||
return mock(EntityManager.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
when(emf.createEntityManager()).thenReturn(em);
|
||||
when(emf.createEntityManager(any(Map.class))).thenReturn(em);
|
||||
when(emf.createEntityManager(any(SynchronizationType.class))).thenReturn(em);
|
||||
when(emf.createEntityManager(any(SynchronizationType.class), any(Map.class))).thenReturn(em);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getListOfLoginContextsReturnsOkWithEmptyList() throws Exception {
|
||||
|
||||
// given
|
||||
when(loginContextRbacRepo.findAll()).thenReturn(List.of(
|
||||
HsCredentialsContextRbacEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("HSADMIN")
|
||||
.qualifier("prod")
|
||||
.build(),
|
||||
HsCredentialsContextRbacEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("SSH")
|
||||
.qualifier("prod")
|
||||
.build()
|
||||
));
|
||||
|
||||
// when
|
||||
mockMvc.perform(MockMvcRequestBuilders
|
||||
.get("/api/login/contexts")
|
||||
.header("Authorization", "Bearer superuser-alex@hostsharing.net")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(print())
|
||||
|
||||
// then
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath(
|
||||
"$", lenientlyEquals("""
|
||||
[
|
||||
{
|
||||
"type": "HSADMIN",
|
||||
"qualifier": "prod"
|
||||
},
|
||||
{
|
||||
"type": "SSH",
|
||||
"qualifier": "prod"
|
||||
}
|
||||
]
|
||||
"""
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package net.hostsharing.hsadminng.credentials;
|
||||
|
||||
import net.hostsharing.hsadminng.credentials.generated.api.v1.model.LoginContextResource;
|
||||
import net.hostsharing.hsadminng.credentials.generated.api.v1.model.LoginContextTypeResource;
|
||||
import net.hostsharing.hsadminng.credentials.generated.api.v1.model.LoginCredentialsPatchResource;
|
||||
import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
|
||||
@TestInstance(PER_CLASS)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HsCredentialsEntityPatcherUnitTest extends PatchUnitTestBase<
|
||||
LoginCredentialsPatchResource,
|
||||
HsCredentialsEntity
|
||||
> {
|
||||
|
||||
private static final UUID INITIAL_CREDENTIALS_UUID = UUID.randomUUID();
|
||||
|
||||
private static final Boolean INITIAL_ACTIVE = true;
|
||||
private static final String INITIAL_EMAIL_ADDRESS = "initial@example.com";
|
||||
private static final String INITIAL_TWO_FACTOR_AUTH = "initial_2fa";
|
||||
private static final String INITIAL_SMS_NUMBER = "initial_sms";
|
||||
private static final String INITIAL_PHONE_PASSWORD = "initial_phone_pw";
|
||||
|
||||
private static final Boolean PATCHED_ACTIVE = false;
|
||||
private static final String PATCHED_EMAIL_ADDRESS = "patched@example.com";
|
||||
private static final String PATCHED_TWO_FACTOR_AUTH = "patched_2fa";
|
||||
private static final String PATCHED_SMS_NUMBER = "patched_sms";
|
||||
private static final String PATCHED_PHONE_PASSWORD = "patched_phone_pw";
|
||||
|
||||
// Contexts
|
||||
private static final UUID CONTEXT_UUID_1 = UUID.randomUUID();
|
||||
private static final UUID CONTEXT_UUID_2 = UUID.randomUUID();
|
||||
private static final UUID CONTEXT_UUID_3 = UUID.randomUUID();
|
||||
|
||||
private final HsCredentialsContextRealEntity initialContextEntity1 = HsCredentialsContextRealEntity.builder()
|
||||
.uuid(CONTEXT_UUID_1)
|
||||
.type("HSADMIN")
|
||||
.qualifier("prod")
|
||||
.build();
|
||||
private final HsCredentialsContextRealEntity initialContextEntity2 = HsCredentialsContextRealEntity.builder()
|
||||
.uuid(CONTEXT_UUID_2)
|
||||
.type("SSH")
|
||||
.qualifier("dev")
|
||||
.build();
|
||||
|
||||
private LoginContextResource patchContextResource2;
|
||||
private LoginContextResource patchContextResource3;
|
||||
|
||||
// This is what em.find should return for CONTEXT_UUID_3
|
||||
private final HsCredentialsContextRealEntity newContextEntity3 = HsCredentialsContextRealEntity.builder()
|
||||
.uuid(CONTEXT_UUID_3)
|
||||
.type("HSADMIN")
|
||||
.qualifier("test")
|
||||
.build();
|
||||
|
||||
private final Set<HsCredentialsContextRealEntity> initialContextEntities = Set.of(initialContextEntity1, initialContextEntity2);
|
||||
private List<LoginContextResource> patchedContextResources;
|
||||
private final Set<HsCredentialsContextRealEntity> expectedPatchedContextEntities = Set.of(initialContextEntity2, newContextEntity3);
|
||||
|
||||
@Mock
|
||||
private EntityManager em;
|
||||
|
||||
@BeforeEach
|
||||
void initMocks() {
|
||||
// Mock em.find for contexts that are part of the patch and need to be fetched
|
||||
lenient().when(em.find(eq(HsCredentialsContextRealEntity.class), eq(CONTEXT_UUID_1))).thenReturn(initialContextEntity1);
|
||||
lenient().when(em.find(eq(HsCredentialsContextRealEntity.class), eq(CONTEXT_UUID_2))).thenReturn(initialContextEntity2);
|
||||
lenient().when(em.find(eq(HsCredentialsContextRealEntity.class), eq(CONTEXT_UUID_3))).thenReturn(newContextEntity3);
|
||||
|
||||
patchContextResource2 = new LoginContextResource();
|
||||
patchContextResource2.setUuid(CONTEXT_UUID_2);
|
||||
patchContextResource2.setType(LoginContextTypeResource.SSH);
|
||||
patchContextResource2.setQualifier("dev");
|
||||
|
||||
patchContextResource3 = new LoginContextResource();
|
||||
patchContextResource3.setUuid(CONTEXT_UUID_3);
|
||||
patchContextResource3.setType(LoginContextTypeResource.HSADMIN);
|
||||
patchContextResource3.setQualifier("test");
|
||||
|
||||
patchedContextResources = List.of(patchContextResource2, patchContextResource3);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HsCredentialsEntity newInitialEntity() {
|
||||
final var entity = new HsCredentialsEntity();
|
||||
entity.setUuid(INITIAL_CREDENTIALS_UUID);
|
||||
entity.setActive(INITIAL_ACTIVE);
|
||||
entity.setEmailAddress(INITIAL_EMAIL_ADDRESS);
|
||||
entity.setTwoFactorAuth(INITIAL_TWO_FACTOR_AUTH);
|
||||
entity.setSmsNumber(INITIAL_SMS_NUMBER);
|
||||
entity.setPhonePassword(INITIAL_PHONE_PASSWORD);
|
||||
// Ensure loginContexts is a mutable set for the patcher
|
||||
entity.setLoginContexts(new HashSet<>(initialContextEntities));
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LoginCredentialsPatchResource newPatchResource() {
|
||||
return new LoginCredentialsPatchResource();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HsCredentialsEntityPatcher createPatcher(final HsCredentialsEntity entity) {
|
||||
return new HsCredentialsEntityPatcher(em, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Stream<Property> propertyTestDescriptors() {
|
||||
return Stream.of(
|
||||
new SimpleProperty<>(
|
||||
"active",
|
||||
LoginCredentialsPatchResource::setActive,
|
||||
PATCHED_ACTIVE,
|
||||
HsCredentialsEntity::setActive,
|
||||
PATCHED_ACTIVE)
|
||||
.notNullable(),
|
||||
new JsonNullableProperty<>(
|
||||
"emailAddress",
|
||||
LoginCredentialsPatchResource::setEmailAddress,
|
||||
PATCHED_EMAIL_ADDRESS,
|
||||
HsCredentialsEntity::setEmailAddress,
|
||||
PATCHED_EMAIL_ADDRESS),
|
||||
new JsonNullableProperty<>(
|
||||
"twoFactorAuth",
|
||||
LoginCredentialsPatchResource::setTwoFactorAuth,
|
||||
PATCHED_TWO_FACTOR_AUTH,
|
||||
HsCredentialsEntity::setTwoFactorAuth,
|
||||
PATCHED_TWO_FACTOR_AUTH),
|
||||
new JsonNullableProperty<>(
|
||||
"smsNumber",
|
||||
LoginCredentialsPatchResource::setSmsNumber,
|
||||
PATCHED_SMS_NUMBER,
|
||||
HsCredentialsEntity::setSmsNumber,
|
||||
PATCHED_SMS_NUMBER),
|
||||
new JsonNullableProperty<>(
|
||||
"phonePassword",
|
||||
LoginCredentialsPatchResource::setPhonePassword,
|
||||
PATCHED_PHONE_PASSWORD,
|
||||
HsCredentialsEntity::setPhonePassword,
|
||||
PATCHED_PHONE_PASSWORD),
|
||||
new SimpleProperty<>(
|
||||
"contexts",
|
||||
LoginCredentialsPatchResource::setContexts,
|
||||
patchedContextResources,
|
||||
HsCredentialsEntity::setLoginContexts,
|
||||
expectedPatchedContextEntities)
|
||||
.notNullable()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package net.hostsharing.hsadminng.credentials;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
||||
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.hibernate.TransientObjectException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import jakarta.persistence.NoResultException;
|
||||
import jakarta.persistence.Query;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
|
||||
@DataJpaTest
|
||||
@Tag("generalIntegrationTest")
|
||||
@Import({ Context.class, JpaAttempt.class })
|
||||
class HsCredentialsRepositoryIntegrationTest extends ContextBasedTest {
|
||||
|
||||
private static final String SUPERUSER_ALEX_SUBJECT_NAME = "superuser-alex@hostsharing.net";
|
||||
private static final String USER_DREW_SUBJECT_NAME = "selfregistered-user-drew@hostsharing.org";
|
||||
private static final String TEST_USER_SUBJECT_NAME = "selfregistered-test-user@hostsharing.org";
|
||||
|
||||
// HOWTO fix UnsatisfiedDependencyException with cause "No qualifying bean of type 'jakarta.servlet.http.HttpServletRequest'"
|
||||
// This dependency comes from class net.hostsharing.hsadminng.context.Context,
|
||||
// which is not automatically wired in a @DataJpaTest, but just in @SpringBootTest.
|
||||
// If, e.g. for validators, the current user or assumed roles are needed, the values need to be mocked.
|
||||
@MockitoBean
|
||||
HttpServletRequest request;
|
||||
|
||||
@Autowired
|
||||
private HsCredentialsRepository loginCredentialsRepository;
|
||||
|
||||
@Autowired
|
||||
private HsCredentialsContextRealRepository loginContextRealRepo;
|
||||
|
||||
// fetched UUIDs from test-data
|
||||
private RbacSubjectEntity alexSubject;
|
||||
private RbacSubjectEntity drewSubject;
|
||||
private RbacSubjectEntity testUserSubject;
|
||||
private HsOfficePersonRealEntity drewPerson;
|
||||
private HsOfficePersonRealEntity testUserPerson;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
alexSubject = fetchSubjectByName(SUPERUSER_ALEX_SUBJECT_NAME);
|
||||
drewSubject = fetchSubjectByName(USER_DREW_SUBJECT_NAME);
|
||||
testUserSubject = fetchSubjectByName(TEST_USER_SUBJECT_NAME);
|
||||
drewPerson = fetchPersonByGivenName("Drew");
|
||||
testUserPerson = fetchPersonByGivenName("Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void historizationIsAvailable() {
|
||||
// given
|
||||
final String nativeQuerySql = "select * from hs_credentials.credentials_hv";
|
||||
|
||||
// when
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().minusDays(1).toInstant()));
|
||||
final var query = em.createNativeQuery(nativeQuerySql);
|
||||
final var rowsBefore = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(rowsBefore).as("hs_credentials.credentials_hv only contain no rows for a timestamp before test data creation").hasSize(0);
|
||||
|
||||
// and when
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().toInstant()));
|
||||
em.createNativeQuery(nativeQuerySql);
|
||||
final var rowsAfter = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(rowsAfter).as("hs_credentials.credentials_hv should now contain the test-data rows for the current timestamp").hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindByUuidUsingTestData() {
|
||||
// when
|
||||
final var foundEntityOptional = loginCredentialsRepository.findByUuid(alexSubject.getUuid());
|
||||
|
||||
// then
|
||||
assertThat(foundEntityOptional).isPresent()
|
||||
.map(HsCredentialsEntity::getEmailAddress).contains("alex@example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveLoginCredentialsWithExistingContext() {
|
||||
// given
|
||||
final var existingContext = loginContextRealRepo.findByTypeAndQualifier("HSADMIN", "prod")
|
||||
.orElseThrow();
|
||||
final var newCredentials = HsCredentialsEntity.builder()
|
||||
.subject(drewSubject)
|
||||
.person(drewPerson)
|
||||
.active(true)
|
||||
.emailAddress("drew.new@example.com")
|
||||
.globalUid(2001)
|
||||
.globalGid(2001)
|
||||
.loginContexts(mutableSetOf(existingContext))
|
||||
.build();
|
||||
|
||||
// when
|
||||
loginCredentialsRepository.save(newCredentials);
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
// then
|
||||
final var foundEntityOptional = loginCredentialsRepository.findByUuid(drewSubject.getUuid());
|
||||
assertThat(foundEntityOptional).isPresent();
|
||||
final var foundEntity = foundEntityOptional.get();
|
||||
assertThat(foundEntity.getEmailAddress()).isEqualTo("drew.new@example.com");
|
||||
assertThat(foundEntity.isActive()).isTrue();
|
||||
assertThat(foundEntity.getVersion()).isEqualTo(0); // Initial version
|
||||
assertThat(foundEntity.getGlobalUid()).isEqualTo(2001);
|
||||
|
||||
assertThat(foundEntity.getLoginContexts()).hasSize(1)
|
||||
.map(HsCredentialsContextRealEntity::toString).contains("loginContext(HSADMIN:prod)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSaveLoginCredentialsWithNewContext() {
|
||||
// given
|
||||
final var newContext = HsCredentialsContextRealEntity.builder()
|
||||
.type("MATRIX")
|
||||
.qualifier("forbidden")
|
||||
.build();
|
||||
final var newCredentials = HsCredentialsEntity.builder()
|
||||
.subject(drewSubject)
|
||||
.active(true)
|
||||
.emailAddress("drew.new@example.com")
|
||||
.globalUid(2001)
|
||||
.globalGid(2001)
|
||||
.loginContexts(mutableSetOf(newContext))
|
||||
.build();
|
||||
|
||||
// when
|
||||
final var exception = catchThrowable(() -> {
|
||||
loginCredentialsRepository.save(newCredentials);
|
||||
em.flush();
|
||||
});
|
||||
|
||||
// then
|
||||
assertThat(exception).isNotNull().hasCauseInstanceOf(TransientObjectException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveNewLoginCredentialsWithoutContext() {
|
||||
// given
|
||||
final var newCredentials = HsCredentialsEntity.builder()
|
||||
.subject(testUserSubject)
|
||||
.person(testUserPerson)
|
||||
.active(true)
|
||||
.emailAddress("test.user.new@example.com")
|
||||
.globalUid(20002)
|
||||
.globalGid(2002)
|
||||
.build();
|
||||
|
||||
// when
|
||||
loginCredentialsRepository.save(newCredentials);
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
// then
|
||||
final var foundEntityOptional = loginCredentialsRepository.findByUuid(testUserSubject.getUuid());
|
||||
assertThat(foundEntityOptional).isPresent();
|
||||
final var foundEntity = foundEntityOptional.get();
|
||||
assertThat(foundEntity.getEmailAddress()).isEqualTo("test.user.new@example.com");
|
||||
assertThat(foundEntity.isActive()).isTrue();
|
||||
assertThat(foundEntity.getGlobalUid()).isEqualTo(20002);
|
||||
assertThat(foundEntity.getGlobalGid()).isEqualTo(2002);
|
||||
assertThat(foundEntity.getLoginContexts()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateExistingLoginCredentials() {
|
||||
// given
|
||||
final var entityToUpdate = loginCredentialsRepository.findByUuid(alexSubject.getUuid()).orElseThrow();
|
||||
final var initialVersion = entityToUpdate.getVersion();
|
||||
|
||||
// when
|
||||
entityToUpdate.setActive(false);
|
||||
entityToUpdate.setEmailAddress("updated.user1@example.com");
|
||||
final var savedEntity = loginCredentialsRepository.save(entityToUpdate);
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
// then
|
||||
assertThat(savedEntity.getVersion()).isGreaterThan(initialVersion);
|
||||
final var updatedEntityOptional = loginCredentialsRepository.findByUuid(alexSubject.getUuid());
|
||||
assertThat(updatedEntityOptional).isPresent();
|
||||
final var updatedEntity = updatedEntityOptional.get();
|
||||
assertThat(updatedEntity.isActive()).isFalse();
|
||||
assertThat(updatedEntity.getEmailAddress()).isEqualTo("updated.user1@example.com");
|
||||
}
|
||||
|
||||
|
||||
private RbacSubjectEntity fetchSubjectByName(final String name) {
|
||||
final String jpql = "SELECT s FROM RbacSubjectEntity s WHERE s.name = :name";
|
||||
final Query query = em.createQuery(jpql, RbacSubjectEntity.class);
|
||||
query.setParameter("name", name);
|
||||
try {
|
||||
context(SUPERUSER_ALEX_SUBJECT_NAME);
|
||||
return notNull((RbacSubjectEntity) query.getSingleResult());
|
||||
} catch (final NoResultException e) {
|
||||
throw new AssertionError(
|
||||
"Failed to find subject with name '" + name + "'. Ensure test data is present.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private HsOfficePersonRealEntity fetchPersonByGivenName(final String givenName) {
|
||||
final String jpql = "SELECT p FROM HsOfficePersonRealEntity p WHERE p.givenName = :givenName";
|
||||
final Query query = em.createQuery(jpql, HsOfficePersonRealEntity.class);
|
||||
query.setParameter("givenName", givenName);
|
||||
try {
|
||||
context(SUPERUSER_ALEX_SUBJECT_NAME);
|
||||
return notNull((HsOfficePersonRealEntity) query.getSingleResult());
|
||||
} catch (final NoResultException e) {
|
||||
throw new AssertionError(
|
||||
"Failed to find person with name '" + givenName + "'. Ensure test data is present.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T notNull(final T result) {
|
||||
assertThat(result).isNotNull();
|
||||
return result;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private <T> Set<T> mutableSetOf(final T... elements) {
|
||||
return new HashSet<T>(Set.of(elements));
|
||||
}
|
||||
}
|
||||
@@ -92,26 +92,23 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
|
||||
@Test
|
||||
public void historizationIsAvailable() {
|
||||
// given
|
||||
final String nativeQuerySql = """
|
||||
select count(*)
|
||||
from hs_booking.item_hv ha;
|
||||
""";
|
||||
final String nativeQuerySql = "select * from hs_booking.item_hv";
|
||||
|
||||
// when
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().minusDays(1).toInstant()));
|
||||
final var query = em.createNativeQuery(nativeQuerySql, Integer.class);
|
||||
@SuppressWarnings("unchecked") final var countBefore = (Integer) query.getSingleResult();
|
||||
final var query = em.createNativeQuery(nativeQuerySql);
|
||||
final var rowsBefore = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(countBefore).as("hs_booking.item should not contain rows for a timestamp in the past").isEqualTo(0);
|
||||
assertThat(rowsBefore).as("hs_booking.item should not contain rows for a timestamp in the past").hasSize(0);
|
||||
|
||||
// and when
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().plusHours(1).toInstant()));
|
||||
em.createNativeQuery(nativeQuerySql, Integer.class);
|
||||
@SuppressWarnings("unchecked") final var countAfter = (Integer) query.getSingleResult();
|
||||
em.createNativeQuery(nativeQuerySql);
|
||||
final var rowsAfter = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(countAfter).as("hs_booking.item should contain rows for a timestamp in the future").isGreaterThan(1);
|
||||
assertThat(rowsAfter).as("hs_booking.item should contain rows for a timestamp in the future").hasSize(21);
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
@@ -83,26 +83,23 @@ class HsBookingProjectRepositoryIntegrationTest extends ContextBasedTestWithClea
|
||||
@Test
|
||||
public void historizationIsAvailable() {
|
||||
// given
|
||||
final String nativeQuerySql = """
|
||||
select count(*)
|
||||
from hs_booking.project_hv ha;
|
||||
""";
|
||||
final String nativeQuerySql = "select * from hs_booking.project_hv ha";
|
||||
|
||||
// when
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().minusDays(1).toInstant()));
|
||||
final var query = em.createNativeQuery(nativeQuerySql, Integer.class);
|
||||
@SuppressWarnings("unchecked") final var countBefore = (Integer) query.getSingleResult();
|
||||
final var query = em.createNativeQuery(nativeQuerySql);
|
||||
final var rowsBefore = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(countBefore).as("hs_booking.project_hv should not contain rows for a timestamp in the past").isEqualTo(0);
|
||||
assertThat(rowsBefore).as("hs_booking.project_hv should not contain rows for a timestamp in the past").hasSize(0);
|
||||
|
||||
// and when
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().plusHours(1).toInstant()));
|
||||
em.createNativeQuery(nativeQuerySql, Integer.class);
|
||||
@SuppressWarnings("unchecked") final var countAfter = (Integer) query.getSingleResult();
|
||||
final var rowsAfter = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(countAfter).as("hs_booking.project_hv should contain rows for a timestamp in the future").isGreaterThan(1);
|
||||
assertThat(rowsAfter).as("hs_booking.project_hv should contain test-data rows for current timestamp").hasSize(3);
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
@@ -113,26 +113,23 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
|
||||
@Test
|
||||
public void historizationIsAvailable() {
|
||||
// given
|
||||
final String nativeQuerySql = """
|
||||
select count(*)
|
||||
from hs_hosting.asset_hv ha;
|
||||
""";
|
||||
final String nativeQuerySql = "select * from hs_hosting.asset_hv";
|
||||
|
||||
// when
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().minusDays(1).toInstant()));
|
||||
final var query = em.createNativeQuery(nativeQuerySql, Integer.class);
|
||||
@SuppressWarnings("unchecked") final var countBefore = (Integer) query.getSingleResult();
|
||||
final var query = em.createNativeQuery(nativeQuerySql);
|
||||
@SuppressWarnings("unchecked") final var rowsBefore = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(countBefore).as("hs_hosting.asset_hv should not contain rows for a timestamp in the past").isEqualTo(0);
|
||||
assertThat(rowsBefore).as("hs_hosting.asset_hv should not contain rows for a timestamp in the past").hasSize(0);
|
||||
|
||||
// and when
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().plusHours(1).toInstant()));
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().toInstant()));
|
||||
em.createNativeQuery(nativeQuerySql, Integer.class);
|
||||
@SuppressWarnings("unchecked") final var countAfter = (Integer) query.getSingleResult();
|
||||
@SuppressWarnings("unchecked") final var rowsAfter = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(countAfter).as("hs_hosting.asset_hv should contain rows for a timestamp in the future").isGreaterThan(1);
|
||||
assertThat(rowsAfter).as("hs_hosting.asset_hv should contain test-data rows for current timestamp").hasSize(54);
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
@@ -68,7 +68,7 @@ class HsOfficePersonControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(200)
|
||||
.contentType("application/json")
|
||||
.body("", hasSize(13));
|
||||
.body("", hasSize(17));
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user