1
0

feature/credentials-schema-updates (#180)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/180
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig
2025-06-04 11:36:20 +02:00
parent 9bf5f011b5
commit d4e78f8a50
33 changed files with 240 additions and 62 deletions

View File

@@ -0,0 +1,113 @@
package net.hostsharing.hsadminng.credentials;
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.hs.office.person.HsOfficePersonRbacRepository;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.hamcrest.CustomMatcher;
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.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.EntityManagerFactory;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.UUID;
import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals;
import static org.mockito.ArgumentMatchers.any;
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;
@WebMvcTest(HsCredentialsController.class)
@Import({ StrictMapper.class, JsonObjectMapperConfiguration.class, DisableSecurityConfig.class, MessageTranslator.class })
@ActiveProfiles("test")
class HsCredentialsControllerRestTest {
@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
HsOfficePersonRbacRepository personRbacRepo;
@MockitoBean
HsCredentialsContextRbacRepository loginContextRbacRepo;
@MockitoBean
HsCredentialsRepository credentialsRepo;
@Test
void patchCredentialsUsed() throws Exception {
// given
final var givenCredentialsUuid = UUID.randomUUID();
when(credentialsRepo.findByUuid(givenCredentialsUuid)).thenReturn(Optional.of(
HsCredentialsEntity.builder()
.uuid(givenCredentialsUuid)
.lastUsed(null)
.onboardingToken("fake-onboarding-token")
.build()
));
when(credentialsRepo.save(any())).thenAnswer(invocation ->
invocation.getArgument(0)
);
// when
mockMvc.perform(MockMvcRequestBuilders
.post("/api/hs/credentials/credentials/%{credentialsUuid}/used"
.replace("%{credentialsUuid}", givenCredentialsUuid.toString()))
.header("Authorization", "Bearer superuser-alex@hostsharing.net")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
// then
.andExpect(status().isOk())
.andExpect(jsonPath(
"$", lenientlyEquals("""
{
"uuid": "%{credentialsUuid}",
"onboardingToken": null
}
""".replace("%{credentialsUuid}", givenCredentialsUuid.toString())
)))
.andExpect(jsonPath("$.lastUsed").value(new CustomMatcher<String>("lastUsed should have recent timestamp") {
@Override
public boolean matches(final Object o) {
if (o == null) {
return false;
}
final var lastUsed = ZonedDateTime.parse(o.toString(), DateTimeFormatter.ISO_DATE_TIME)
.toLocalDateTime();
return lastUsed.isAfter(LocalDateTime.now().minusMinutes(1)) &&
lastUsed.isBefore(LocalDateTime.now());
}
}));
}
}

View File

@@ -33,13 +33,13 @@ class HsCredentialsEntityPatcherUnitTest extends PatchUnitTestBase<
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_TOTP_SECRET = "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_TOTP_SECRET = "patched_2fa";
private static final String PATCHED_SMS_NUMBER = "patched_sms";
private static final String PATCHED_PHONE_PASSWORD = "patched_phone_pw";
@@ -102,7 +102,7 @@ class HsCredentialsEntityPatcherUnitTest extends PatchUnitTestBase<
entity.setUuid(INITIAL_CREDENTIALS_UUID);
entity.setActive(INITIAL_ACTIVE);
entity.setEmailAddress(INITIAL_EMAIL_ADDRESS);
entity.setTwoFactorAuth(INITIAL_TWO_FACTOR_AUTH);
entity.setTotpSecret(INITIAL_TOTP_SECRET);
entity.setSmsNumber(INITIAL_SMS_NUMBER);
entity.setPhonePassword(INITIAL_PHONE_PASSWORD);
// Ensure loginContexts is a mutable set for the patcher
@@ -137,11 +137,11 @@ class HsCredentialsEntityPatcherUnitTest extends PatchUnitTestBase<
HsCredentialsEntity::setEmailAddress,
PATCHED_EMAIL_ADDRESS),
new JsonNullableProperty<>(
"twoFactorAuth",
CredentialsPatchResource::setTwoFactorAuth,
PATCHED_TWO_FACTOR_AUTH,
HsCredentialsEntity::setTwoFactorAuth,
PATCHED_TWO_FACTOR_AUTH),
"totpSecret",
CredentialsPatchResource::setTotpSecret,
PATCHED_TOTP_SECRET,
HsCredentialsEntity::setTotpSecret,
PATCHED_TOTP_SECRET),
new JsonNullableProperty<>(
"smsNumber",
CredentialsPatchResource::setSmsNumber,

View File

@@ -138,7 +138,7 @@ public class ImportHostingAssets extends CsvDataImport {
@Autowired
LiquibaseMigration liquibase;
@Value("${HSADMINNG_OFFICE_DATA_SQL_FILE:/db/released-only-office-schema-with-import-test-data.sql}")
@Value("${HSADMINNG_OFFICE_DATA_SQL_FILE:/db/released-only-prod-schema-with-import-test-data.sql}")
String officeSchemaAndDataSqlFile;
@Test

View File

@@ -24,9 +24,9 @@ import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TE
* <p>The test works as follows:</p>
*
* <ol>
* <li>the database is initialized by `db/released-only-office-schema-with-test-data.sql` from the test-resources</li>
* <li>the current Liquibase-migrations (only-office but with-test-data) are performed</li>
* <li>a new dump is written to `db/released-only-office-schema-with-test-data.sql` in the build-directory</li>
* <li>the database is initialized by `db/released-only-prod-schema-with-test-data.sql` from the test-resources</li>
* <li>the current Liquibase-migrations (only-prod-schema but with-test-data) are performed</li>
* <li>a new dump is written to `db/released-only-prod-schema-with-test-data.sql` in the build-directory</li>
* <li>an extra Liquibase-changeset (liquibase-migration-test) is applied</li>
* <li>it's asserted that the extra changeset got applied</li>
* </ol>
@@ -43,7 +43,7 @@ import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TE
@DirtiesContext
@ActiveProfiles("liquibase-migration-test")
@Import(LiquibaseConfig.class)
@Sql(value = "/db/released-only-office-schema-with-test-data.sql", executionPhase = BEFORE_TEST_CLASS) // release-schema
@Sql(value = "/db/released-only-prod-schema-with-test-data.sql", executionPhase = BEFORE_TEST_CLASS) // release-schema
public class LiquibaseCompatibilityIntegrationTest {
private static final String EXPECTED_CHANGESET_ONLY_AFTER_NEW_MIGRATION = "hs-global-liquibase-migration-test";
@@ -62,8 +62,8 @@ public class LiquibaseCompatibilityIntegrationTest {
EXPECTED_LIQUIBASE_CHANGELOGS_IN_PROD_SCHEMA_DUMP, EXPECTED_CHANGESET_ONLY_AFTER_NEW_MIGRATION);
// run the current migrations and dump the result to the build-directory
liquibase.runWithContexts("only-office", "with-test-data");
PostgresTestcontainer.dump(jdbcUrl, new File("build/db/released-only-office-schema-with-test-data.sql"));
liquibase.runWithContexts("only-prod-schema", "with-test-data");
PostgresTestcontainer.dump(jdbcUrl, new File("build/db/released-only-prod-schema-with-test-data.sql"));
// then add another migration and assert if it was applied
liquibase.runWithContexts("liquibase-migration-test");