From 3336181cdb85b27577d5bf81fbb6f8d4428245ab Mon Sep 17 00:00:00 2001 From: lobo Date: Wed, 30 Sep 2026 13:41:57 +0200 Subject: [PATCH] test: one test container and one context per test kind --- readme.md | 4 +- .../AuthServiceApplicationTests.java | 13 ---- .../security/ExpiredAccessTokenTest.java | 66 ------------------- .../security/PublicEndpointsTest.java | 2 - .../security/RoleCheckFlowTest.java | 2 - .../security/SessionFlowTest.java | 10 ++- .../security/SteamLinkFlowTest.java | 2 - .../account/api/DeletedAccountFlowTest.java | 2 - .../account/repo/AccountRepositoryTest.java | 4 +- .../modules/admin/api/AdminFlowTest.java | 2 - .../admin/repo/AdminUserRepositoryTest.java | 2 - .../modules/hint/api/HintFlowTest.java | 2 - .../modules/hint/repo/HintRepositoryTest.java | 2 - .../MinimalUserRepositoryTest.java | 2 - .../api/PublicProfileControllerTest.java | 2 - .../RegisteredUserRepositoryTest.java | 6 +- .../user/persistence/UserRepositoryTest.java | 12 ++-- .../shared/test/AbstractIntegrationTest.java | 27 ++++++-- .../shared/test/AbstractPersistenceTest.java | 14 ++-- .../shared/test/TestPostgres.java | 19 ++++++ 20 files changed, 65 insertions(+), 130 deletions(-) delete mode 100644 src/test/java/auth_service/AuthServiceApplicationTests.java delete mode 100644 src/test/java/auth_service/configuration/security/ExpiredAccessTokenTest.java create mode 100644 src/test/java/auth_service/shared/test/TestPostgres.java diff --git a/readme.md b/readme.md index 994d2c6..e5c8e11 100644 --- a/readme.md +++ b/readme.md @@ -153,7 +153,9 @@ use each other. ArchUnit enforces these rules in `src/test/java/auth_service/architecture/ArchitectureTest.java`, so a layer violation fails the build. It also keeps the modules independent of each other and free of cycles. Test base classes (`AbstractUnitTest`, `AbstractWebMvcTest`, `AbstractPersistenceTest`, `AbstractIntegrationTest`) live in -`auth_service.shared.test`; all integration tests share one PostgreSQL container. +`auth_service.shared.test`. Integration and persistence tests share one PostgreSQL container (`TestPostgres`), and +the base classes carry every annotation, so each kind starts one Spring context. A test with its own annotations +starts another one. ### Login flow diff --git a/src/test/java/auth_service/AuthServiceApplicationTests.java b/src/test/java/auth_service/AuthServiceApplicationTests.java deleted file mode 100644 index 195afb7..0000000 --- a/src/test/java/auth_service/AuthServiceApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package auth_service; - -import auth_service.shared.test.AbstractIntegrationTest; -import org.junit.jupiter.api.Test; - -class AuthServiceApplicationTests extends AbstractIntegrationTest { - - @Test - void contextLoads() { - // If the context fails to load (e.g. Flyway migration fails or beans are missing), - // this test will fail automatically. No explicit assertions needed. - } -} diff --git a/src/test/java/auth_service/configuration/security/ExpiredAccessTokenTest.java b/src/test/java/auth_service/configuration/security/ExpiredAccessTokenTest.java deleted file mode 100644 index f624198..0000000 --- a/src/test/java/auth_service/configuration/security/ExpiredAccessTokenTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package auth_service.configuration.security; - -import auth_service.shared.api.AuthCookies; -import auth_service.shared.test.AbstractIntegrationTest; -import jakarta.servlet.http.Cookie; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; -import org.springframework.http.MediaType; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Every access token from this context is already expired when it is issued. - */ -@AutoConfigureMockMvc -@TestPropertySource(properties = "app.auth.access-token-ttl=-1s") -class ExpiredAccessTokenTest extends AbstractIntegrationTest { - - private static final String EMAIL = "review-findings@example.com"; - private static final String PASSWORD = "correct horse battery"; - - @Autowired - private MockMvc mockMvc; - - @Autowired - private JdbcTemplate jdbc; - - @Autowired - private PasswordEncoder passwordEncoder; - - @BeforeEach - void seedUser() { - jdbc.update("DELETE FROM users WHERE email = ?", EMAIL); - jdbc.update("INSERT INTO users (email, password, enabled, steam_id) VALUES (?, ?, true, NULL)", - EMAIL, passwordEncoder.encode(PASSWORD)); - addSettingsRows(); - } - - private String expiredAccessCookie() throws Exception { - Cookie cookie = mockMvc.perform(post("/api/auth/login") - .header(ClientHeaderFilter.CLIENT_HEADER, "web") - .contentType(MediaType.APPLICATION_JSON) - .content("{\"email\":\"" + EMAIL + "\",\"password\":\"" + PASSWORD + "\"}")) - .andExpect(status().isOk()) - .andReturn().getResponse().getCookie(AuthCookies.ACCESS_COOKIE); - assertNotNull(cookie); - return cookie.getValue(); - } - - @Test - void expiredAccessCookie_Is401SoTheFrontendRefreshes() throws Exception { - String expired = expiredAccessCookie(); - - mockMvc.perform(get("/api/users/me").cookie(new Cookie(AuthCookies.ACCESS_COOKIE, expired))) - .andExpect(status().isUnauthorized()); - } -} diff --git a/src/test/java/auth_service/configuration/security/PublicEndpointsTest.java b/src/test/java/auth_service/configuration/security/PublicEndpointsTest.java index 4c8d758..6f99b0a 100644 --- a/src/test/java/auth_service/configuration/security/PublicEndpointsTest.java +++ b/src/test/java/auth_service/configuration/security/PublicEndpointsTest.java @@ -3,7 +3,6 @@ package auth_service.configuration.security; import auth_service.shared.test.AbstractIntegrationTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.test.web.servlet.MockMvc; import java.util.Set; @@ -14,7 +13,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@AutoConfigureMockMvc class PublicEndpointsTest extends AbstractIntegrationTest { @Autowired diff --git a/src/test/java/auth_service/configuration/security/RoleCheckFlowTest.java b/src/test/java/auth_service/configuration/security/RoleCheckFlowTest.java index d6dd909..c105247 100644 --- a/src/test/java/auth_service/configuration/security/RoleCheckFlowTest.java +++ b/src/test/java/auth_service/configuration/security/RoleCheckFlowTest.java @@ -10,7 +10,6 @@ import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @@ -27,7 +26,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. /** * The role markers through the real filter chain, on a probe that only this test sees. */ -@AutoConfigureMockMvc @Import({RoleCheckFlowTest.ProbeService.class, RoleCheckFlowTest.ProbeController.class}) class RoleCheckFlowTest extends AbstractIntegrationTest { diff --git a/src/test/java/auth_service/configuration/security/SessionFlowTest.java b/src/test/java/auth_service/configuration/security/SessionFlowTest.java index 9436971..6001601 100644 --- a/src/test/java/auth_service/configuration/security/SessionFlowTest.java +++ b/src/test/java/auth_service/configuration/security/SessionFlowTest.java @@ -8,7 +8,6 @@ import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mock.web.MockHttpServletResponse; @@ -38,7 +37,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * The whole cookie session against a real database: login, refresh rotation, reuse detection, logout, the CSRF * header and CORS. */ -@AutoConfigureMockMvc class SessionFlowTest extends AbstractIntegrationTest { private static final String EMAIL = "session-flow@example.com"; @@ -98,6 +96,14 @@ class SessionFlowTest extends AbstractIntegrationTest { .andExpect(jsonPath("$.email").value(EMAIL)); } + @Test + void expiredAccessCookie_Is401SoTheFrontendRefreshes() throws Exception { + String expired = expiredCopy(cookie(login(), AuthCookies.ACCESS_COOKIE)); + + mockMvc.perform(get("/api/users/me").cookie(new Cookie(AuthCookies.ACCESS_COOKIE, expired))) + .andExpect(status().isUnauthorized()); + } + @Test void noLogin_Is401() throws Exception { mockMvc.perform(get("/api/users/me")) diff --git a/src/test/java/auth_service/configuration/security/SteamLinkFlowTest.java b/src/test/java/auth_service/configuration/security/SteamLinkFlowTest.java index 4e648cc..5181801 100644 --- a/src/test/java/auth_service/configuration/security/SteamLinkFlowTest.java +++ b/src/test/java/auth_service/configuration/security/SteamLinkFlowTest.java @@ -7,7 +7,6 @@ import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mock.web.MockHttpServletResponse; @@ -35,7 +34,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. /** * "Sign in through Steam" through the whole filter chain and a real database; only Steam itself is mocked. */ -@AutoConfigureMockMvc class SteamLinkFlowTest extends AbstractIntegrationTest { private static final String EMAIL = "steam-link@example.com"; diff --git a/src/test/java/auth_service/modules/account/api/DeletedAccountFlowTest.java b/src/test/java/auth_service/modules/account/api/DeletedAccountFlowTest.java index 727ddbc..69afbdd 100644 --- a/src/test/java/auth_service/modules/account/api/DeletedAccountFlowTest.java +++ b/src/test/java/auth_service/modules/account/api/DeletedAccountFlowTest.java @@ -10,7 +10,6 @@ import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mock.web.MockHttpServletResponse; @@ -30,7 +29,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * Delete my account against a real database. Core is not reachable in tests, so this also proves that a failed * mark in Core does not fail the delete. */ -@AutoConfigureMockMvc class DeletedAccountFlowTest extends AbstractIntegrationTest { private static final String EMAIL = "delete-flow@example.com"; diff --git a/src/test/java/auth_service/modules/account/repo/AccountRepositoryTest.java b/src/test/java/auth_service/modules/account/repo/AccountRepositoryTest.java index 4c7716b..b43967f 100644 --- a/src/test/java/auth_service/modules/account/repo/AccountRepositoryTest.java +++ b/src/test/java/auth_service/modules/account/repo/AccountRepositoryTest.java @@ -4,7 +4,6 @@ import auth_service.shared.test.AbstractPersistenceTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.Timestamp; @@ -15,7 +14,6 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; -@Import(AccountRepository.class) class AccountRepositoryTest extends AbstractPersistenceTest { private static final Instant NOW = Instant.now().truncatedTo(ChronoUnit.SECONDS); @@ -45,6 +43,8 @@ class AccountRepositoryTest extends AbstractPersistenceTest { @BeforeEach void seed() { + // Integration tests share the database and commit their rows. + jdbc.update("DELETE FROM users"); alice = insertUser("alice@example.com"); bob = insertUser("bob@example.com"); } diff --git a/src/test/java/auth_service/modules/admin/api/AdminFlowTest.java b/src/test/java/auth_service/modules/admin/api/AdminFlowTest.java index aab9924..9173aa5 100644 --- a/src/test/java/auth_service/modules/admin/api/AdminFlowTest.java +++ b/src/test/java/auth_service/modules/admin/api/AdminFlowTest.java @@ -7,7 +7,6 @@ import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mock.web.MockHttpServletResponse; @@ -26,7 +25,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. /** * Roles, "view as" and the admin users API against a real database. */ -@AutoConfigureMockMvc class AdminFlowTest extends AbstractIntegrationTest { private static final String PASSWORD = "correct horse battery"; diff --git a/src/test/java/auth_service/modules/admin/repo/AdminUserRepositoryTest.java b/src/test/java/auth_service/modules/admin/repo/AdminUserRepositoryTest.java index 482a2a1..d65c63c 100644 --- a/src/test/java/auth_service/modules/admin/repo/AdminUserRepositoryTest.java +++ b/src/test/java/auth_service/modules/admin/repo/AdminUserRepositoryTest.java @@ -6,14 +6,12 @@ import auth_service.shared.test.AbstractPersistenceTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.JdbcTemplate; import java.time.Instant; import static org.assertj.core.api.Assertions.assertThat; -@Import(AdminUserRepository.class) class AdminUserRepositoryTest extends AbstractPersistenceTest { @Autowired diff --git a/src/test/java/auth_service/modules/hint/api/HintFlowTest.java b/src/test/java/auth_service/modules/hint/api/HintFlowTest.java index a5c84c5..bac17b1 100644 --- a/src/test/java/auth_service/modules/hint/api/HintFlowTest.java +++ b/src/test/java/auth_service/modules/hint/api/HintFlowTest.java @@ -7,7 +7,6 @@ import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.crypto.password.PasswordEncoder; @@ -24,7 +23,6 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@AutoConfigureMockMvc class HintFlowTest extends AbstractIntegrationTest { private static final String PASSWORD = "correct horse battery"; diff --git a/src/test/java/auth_service/modules/hint/repo/HintRepositoryTest.java b/src/test/java/auth_service/modules/hint/repo/HintRepositoryTest.java index d5d8988..9109e2a 100644 --- a/src/test/java/auth_service/modules/hint/repo/HintRepositoryTest.java +++ b/src/test/java/auth_service/modules/hint/repo/HintRepositoryTest.java @@ -9,7 +9,6 @@ import auth_service.shared.test.AbstractPersistenceTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.JdbcTemplate; import java.time.Duration; @@ -19,7 +18,6 @@ import java.time.temporal.ChronoUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; -@Import(HintRepository.class) class HintRepositoryTest extends AbstractPersistenceTest { private static final Instant NOW = Instant.now().truncatedTo(ChronoUnit.MILLIS); diff --git a/src/test/java/auth_service/modules/login/persistence/MinimalUserRepositoryTest.java b/src/test/java/auth_service/modules/login/persistence/MinimalUserRepositoryTest.java index 1727f6d..510218b 100644 --- a/src/test/java/auth_service/modules/login/persistence/MinimalUserRepositoryTest.java +++ b/src/test/java/auth_service/modules/login/persistence/MinimalUserRepositoryTest.java @@ -5,7 +5,6 @@ import auth_service.shared.security.Role; import auth_service.shared.test.AbstractPersistenceTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.JdbcTemplate; import java.time.LocalDateTime; @@ -15,7 +14,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -@Import(MinimalUserRepositoryImpl.class) class MinimalUserRepositoryTest extends AbstractPersistenceTest { @Autowired diff --git a/src/test/java/auth_service/modules/profile/api/PublicProfileControllerTest.java b/src/test/java/auth_service/modules/profile/api/PublicProfileControllerTest.java index 6719437..15c0ecf 100644 --- a/src/test/java/auth_service/modules/profile/api/PublicProfileControllerTest.java +++ b/src/test/java/auth_service/modules/profile/api/PublicProfileControllerTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.web.servlet.MockMvc; @@ -25,7 +24,6 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@AutoConfigureMockMvc class PublicProfileControllerTest extends AbstractIntegrationTest { @Autowired diff --git a/src/test/java/auth_service/modules/registration/persistence/RegisteredUserRepositoryTest.java b/src/test/java/auth_service/modules/registration/persistence/RegisteredUserRepositoryTest.java index aaf2788..672206c 100644 --- a/src/test/java/auth_service/modules/registration/persistence/RegisteredUserRepositoryTest.java +++ b/src/test/java/auth_service/modules/registration/persistence/RegisteredUserRepositoryTest.java @@ -4,7 +4,6 @@ import auth_service.modules.registration.model.Registration; import auth_service.shared.test.AbstractPersistenceTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Import; import org.springframework.dao.DuplicateKeyException; import org.springframework.jdbc.core.JdbcTemplate; @@ -15,7 +14,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -@Import(RegisteredUserRepositoryImpl.class) class RegisteredUserRepositoryTest extends AbstractPersistenceTest { @Autowired @@ -38,9 +36,9 @@ class RegisteredUserRepositoryTest extends AbstractPersistenceTest { @Test void existsByDisplayName_IgnoresCase() { - registeredUserRepository.save(registration("named@example.com", "Lobo")); + registeredUserRepository.save(registration("named@example.com", "RegisteredName")); - assertTrue(registeredUserRepository.existsByDisplayName("lobo")); + assertTrue(registeredUserRepository.existsByDisplayName("registeredname")); assertFalse(registeredUserRepository.existsByDisplayName("Wolf")); } diff --git a/src/test/java/auth_service/modules/user/persistence/UserRepositoryTest.java b/src/test/java/auth_service/modules/user/persistence/UserRepositoryTest.java index 830cf98..12882ed 100644 --- a/src/test/java/auth_service/modules/user/persistence/UserRepositoryTest.java +++ b/src/test/java/auth_service/modules/user/persistence/UserRepositoryTest.java @@ -12,7 +12,6 @@ import auth_service.shared.test.AbstractPersistenceTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.jpa.test.autoconfigure.TestEntityManager; -import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.JdbcTemplate; import java.time.LocalDateTime; @@ -21,7 +20,6 @@ import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; -@Import(UserRepositoryImpl.class) class UserRepositoryTest extends AbstractPersistenceTest { @Autowired @@ -224,13 +222,13 @@ class UserRepositoryTest extends AbstractPersistenceTest { var settings = UserSettings.builder().language("en").theme("light").build(); // Act - updateSettings(owner.getId(), "Lobo", settings); + updateSettings(owner.getId(), "RepoName", settings); entityManager.flush(); // Assert - assertEquals("Lobo", findByEmail("owner2@example.com").orElseThrow().getDisplayName()); - assertFalse(userRepository.isDisplayNameUsedByOtherUser(owner.getId(), "lobo")); - assertTrue(userRepository.isDisplayNameUsedByOtherUser(other.getId(), "LOBO")); + assertEquals("RepoName", findByEmail("owner2@example.com").orElseThrow().getDisplayName()); + assertFalse(userRepository.isDisplayNameUsedByOtherUser(owner.getId(), "reponame")); + assertTrue(userRepository.isDisplayNameUsedByOtherUser(other.getId(), "REPONAME")); assertFalse(userRepository.isDisplayNameUsedByOtherUser(other.getId(), "Wolf")); } @@ -243,7 +241,7 @@ class UserRepositoryTest extends AbstractPersistenceTest { .registrationDate(LocalDateTime.now()) .build()); - updateSettings(userEntity.getId(), "Lobo", + updateSettings(userEntity.getId(), "RepoName", UserSettings.builder().language("de").theme("dark").publicProfile(true).build()); entityManager.flush(); entityManager.clear(); diff --git a/src/test/java/auth_service/shared/test/AbstractIntegrationTest.java b/src/test/java/auth_service/shared/test/AbstractIntegrationTest.java index eb371e6..e5c99e2 100644 --- a/src/test/java/auth_service/shared/test/AbstractIntegrationTest.java +++ b/src/test/java/auth_service/shared/test/AbstractIntegrationTest.java @@ -1,8 +1,12 @@ package auth_service.shared.test; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; import org.junit.jupiter.api.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.context.DynamicPropertyRegistry; @@ -13,25 +17,21 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.util.Base64; +import java.util.Date; /** * Base class for integration tests. * Full Spring context with Testcontainers. */ @Tag("IntegrationTest") +@AutoConfigureMockMvc // The account cleanup must not run by itself, and Core is never reachable in tests. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {"app.account-cleanup.cron=-", "app.core.internal-url=http://127.0.0.1:1"}) public abstract class AbstractIntegrationTest { - // One container per JVM, not per class: Spring caches the context across test classes, - // and a per-class container would be stopped under the cached context. @ServiceConnection - static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:17-alpine"); - - static { - postgres.start(); - } + static PostgreSQLContainer postgres = TestPostgres.CONTAINER; @Autowired private JdbcTemplate baseJdbc; @@ -54,6 +54,19 @@ public abstract class AbstractIntegrationTest { registry.add("auth.registration.invitation-code", () -> "test-invite-code"); } + /** + * The same token, signed again with an expiry in the past. + */ + protected static String expiredCopy(String token) { + Jws jws = Jwts.parser().verifyWith(KEYS.getPublic()).build().parseSignedClaims(token); + return Jwts.builder() + .header().keyId(jws.getHeader().getKeyId()).and() + .claims(jws.getPayload()) + .expiration(new Date(System.currentTimeMillis() - 1000)) + .signWith(KEYS.getPrivate(), Jwts.SIG.EdDSA) + .compact(); + } + private static KeyPair generateKeys() { try { return KeyPairGenerator.getInstance("Ed25519").generateKeyPair(); diff --git a/src/test/java/auth_service/shared/test/AbstractPersistenceTest.java b/src/test/java/auth_service/shared/test/AbstractPersistenceTest.java index 9ddd1aa..cf86eab 100644 --- a/src/test/java/auth_service/shared/test/AbstractPersistenceTest.java +++ b/src/test/java/auth_service/shared/test/AbstractPersistenceTest.java @@ -4,6 +4,9 @@ import org.junit.jupiter.api.Tag; import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.stereotype.Repository; import org.testcontainers.containers.PostgreSQLContainer; /** @@ -11,16 +14,11 @@ import org.testcontainers.containers.PostgreSQLContainer; * Slices only the persistence layer with Testcontainers. */ @Tag("PersistenceTest") -@DataJpaTest +// Every @Repository, so all persistence tests share one Spring context. +@DataJpaTest(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class)) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public abstract class AbstractPersistenceTest { - // One container per JVM, not per class: Spring caches the context across test classes, - // and a per-class container would be stopped under the cached context. @ServiceConnection - static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:17-alpine"); - - static { - postgres.start(); - } + static PostgreSQLContainer postgres = TestPostgres.CONTAINER; } diff --git a/src/test/java/auth_service/shared/test/TestPostgres.java b/src/test/java/auth_service/shared/test/TestPostgres.java new file mode 100644 index 0000000..5f358e9 --- /dev/null +++ b/src/test/java/auth_service/shared/test/TestPostgres.java @@ -0,0 +1,19 @@ +package auth_service.shared.test; + +import org.testcontainers.containers.PostgreSQLContainer; + +/** + * One database for all integration and persistence tests of a JVM. Integration tests commit their rows, so every + * test uses emails and display names no other test uses. + */ +final class TestPostgres { + + static final PostgreSQLContainer CONTAINER = new PostgreSQLContainer<>("postgres:17-alpine"); + + static { + CONTAINER.start(); + } + + private TestPostgres() { + } +}