From 7b1c1146931d519263b86b1573469350781f373d Mon Sep 17 00:00:00 2001 From: Sebastian Frick Date: Thu, 19 Feb 2026 21:33:44 +0100 Subject: [PATCH] refactor(test): gemeinsame AbstractIntegrationTest Base-Class Alle 9 Integration-Test-Klassen nutzen eine gemeinsame Base-Class mit geteiltem Spring Context (MOCK statt RANDOM_PORT), vorberechnetem BCrypt-Hash und gemeinsamen Helper-Methoden. Eliminiert ~600 Zeilen Duplikation und Runtime-BCrypt-Aufrufe im Test-Setup. --- .../AbstractIntegrationTest.java | 108 ++++++++++ ...rageLocationControllerIntegrationTest.java | 93 +------- .../web/ArticleControllerIntegrationTest.java | 83 +------- .../CustomerControllerIntegrationTest.java | 83 +------- ...ductCategoryControllerIntegrationTest.java | 85 +------- .../SupplierControllerIntegrationTest.java | 83 +------- .../web/AuthControllerIntegrationTest.java | 138 ++---------- .../web/SecurityIntegrationTest.java | 198 ++---------------- .../web/UserControllerIntegrationTest.java | 127 ++--------- 9 files changed, 195 insertions(+), 803 deletions(-) create mode 100644 backend/src/test/java/de/effigenix/infrastructure/AbstractIntegrationTest.java diff --git a/backend/src/test/java/de/effigenix/infrastructure/AbstractIntegrationTest.java b/backend/src/test/java/de/effigenix/infrastructure/AbstractIntegrationTest.java new file mode 100644 index 0000000..d776675 --- /dev/null +++ b/backend/src/test/java/de/effigenix/infrastructure/AbstractIntegrationTest.java @@ -0,0 +1,108 @@ +package de.effigenix.infrastructure; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.effigenix.domain.usermanagement.RoleName; +import de.effigenix.domain.usermanagement.UserStatus; +import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity; +import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity; +import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository; +import de.effigenix.infrastructure.usermanagement.persistence.repository.UserJpaRepository; +import io.jsonwebtoken.Jwts; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.Set; +import java.util.UUID; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@AutoConfigureMockMvc +@ActiveProfiles("test") +@Transactional +public abstract class AbstractIntegrationTest { + + protected static final String BCRYPT_PASS123 = "$2a$10$4tNfKjz7w.67G72CVop9CuuzBh5vrkYJki8lZ66ZwwLtQjlrm6hHy"; + protected static final String TEST_PASSWORD = "Pass123"; + + @Autowired + protected MockMvc mockMvc; + + @Autowired + protected ObjectMapper objectMapper; + + @Autowired + protected UserJpaRepository userRepository; + + @Autowired + protected RoleJpaRepository roleRepository; + + @Value("${jwt.secret}") + protected String jwtSecret; + + @Value("${jwt.expiration}") + protected long jwtExpiration; + + protected String generateToken(String userId, String username, String permissions) { + long now = System.currentTimeMillis(); + SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( + jwtSecret.getBytes(StandardCharsets.UTF_8)); + return Jwts.builder() + .subject(userId) + .claim("username", username) + .claim("permissions", permissions) + .issuedAt(new Date(now)) + .expiration(new Date(now + jwtExpiration)) + .signWith(key) + .compact(); + } + + protected String generateExpiredToken(String userId, String username) { + long now = System.currentTimeMillis(); + SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( + jwtSecret.getBytes(StandardCharsets.UTF_8)); + return Jwts.builder() + .subject(userId) + .claim("username", username) + .claim("permissions", "") + .issuedAt(new Date(now - 10000)) + .expiration(new Date(now - 5000)) + .signWith(key) + .compact(); + } + + protected String generateRefreshToken(String userId, String username) { + long now = System.currentTimeMillis(); + SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( + jwtSecret.getBytes(StandardCharsets.UTF_8)); + return Jwts.builder() + .subject(userId) + .claim("username", username) + .claim("type", "refresh") + .issuedAt(new Date(now)) + .expiration(new Date(now + 7200000)) + .signWith(key) + .compact(); + } + + protected RoleEntity createRole(RoleName roleName, String description) { + RoleEntity role = new RoleEntity( + UUID.randomUUID().toString(), roleName, Set.of(), description); + return roleRepository.save(role); + } + + protected UserEntity createUser(String username, String email, Set roles, String branchId) { + UserEntity user = new UserEntity( + UUID.randomUUID().toString(), username, email, + BCRYPT_PASS123, roles, + branchId, UserStatus.ACTIVE, LocalDateTime.now(), null); + return userRepository.save(user); + } +} diff --git a/backend/src/test/java/de/effigenix/infrastructure/inventory/web/StorageLocationControllerIntegrationTest.java b/backend/src/test/java/de/effigenix/infrastructure/inventory/web/StorageLocationControllerIntegrationTest.java index dde8392..21e9ebe 100644 --- a/backend/src/test/java/de/effigenix/infrastructure/inventory/web/StorageLocationControllerIntegrationTest.java +++ b/backend/src/test/java/de/effigenix/infrastructure/inventory/web/StorageLocationControllerIntegrationTest.java @@ -1,31 +1,16 @@ package de.effigenix.infrastructure.inventory.web; -import com.fasterxml.jackson.databind.ObjectMapper; import de.effigenix.domain.usermanagement.RoleName; -import de.effigenix.domain.usermanagement.UserStatus; +import de.effigenix.infrastructure.AbstractIntegrationTest; import de.effigenix.infrastructure.inventory.web.dto.CreateStorageLocationRequest; import de.effigenix.infrastructure.inventory.web.dto.UpdateStorageLocationRequest; import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity; import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity; -import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository; -import de.effigenix.infrastructure.usermanagement.persistence.repository.UserJpaRepository; -import io.jsonwebtoken.Jwts; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.transaction.annotation.Transactional; -import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; -import java.util.Date; import java.util.Set; import java.util.UUID; @@ -40,33 +25,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * - Story 1.1 – Lagerort anlegen * - Story 1.2 – Lagerort bearbeiten und (de-)aktivieren */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -@ActiveProfiles("test") -@Transactional @DisplayName("StorageLocation Controller Integration Tests") -class StorageLocationControllerIntegrationTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - private UserJpaRepository userRepository; - - @Autowired - private RoleJpaRepository roleRepository; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Value("${jwt.secret}") - private String jwtSecret; - - @Value("${jwt.expiration}") - private long jwtExpiration; +class StorageLocationControllerIntegrationTest extends AbstractIntegrationTest { private String adminToken; private String readerToken; @@ -74,35 +34,16 @@ class StorageLocationControllerIntegrationTest { @BeforeEach void setUp() { - RoleEntity adminRole = new RoleEntity( - UUID.randomUUID().toString(), RoleName.ADMIN, Set.of(), "Admin"); - roleRepository.save(adminRole); + RoleEntity adminRole = createRole(RoleName.ADMIN, "Admin"); + RoleEntity viewerRole = createRole(RoleName.PRODUCTION_WORKER, "Viewer"); - RoleEntity viewerRole = new RoleEntity( - UUID.randomUUID().toString(), RoleName.PRODUCTION_WORKER, Set.of(), "Viewer"); - roleRepository.save(viewerRole); + UserEntity admin = createUser("inv.admin", "inv.admin@test.com", Set.of(adminRole), "BRANCH-01"); + UserEntity reader = createUser("inv.reader", "inv.reader@test.com", Set.of(viewerRole), "BRANCH-01"); + UserEntity viewer = createUser("inv.viewer", "inv.viewer@test.com", Set.of(viewerRole), "BRANCH-01"); - String adminId = UUID.randomUUID().toString(); - userRepository.save(new UserEntity( - adminId, "inv.admin", "inv.admin@test.com", - passwordEncoder.encode("Pass123"), Set.of(adminRole), - "BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null)); - - String readerId = UUID.randomUUID().toString(); - userRepository.save(new UserEntity( - readerId, "inv.reader", "inv.reader@test.com", - passwordEncoder.encode("Pass123"), Set.of(viewerRole), - "BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null)); - - String viewerId = UUID.randomUUID().toString(); - userRepository.save(new UserEntity( - viewerId, "inv.viewer", "inv.viewer@test.com", - passwordEncoder.encode("Pass123"), Set.of(viewerRole), - "BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null)); - - adminToken = generateToken(adminId, "inv.admin", "STOCK_WRITE,STOCK_READ"); - readerToken = generateToken(readerId, "inv.reader", "STOCK_READ"); - viewerToken = generateToken(viewerId, "inv.viewer", "USER_READ"); + adminToken = generateToken(admin.getId(), "inv.admin", "STOCK_WRITE,STOCK_READ"); + readerToken = generateToken(reader.getId(), "inv.reader", "STOCK_READ"); + viewerToken = generateToken(viewer.getId(), "inv.viewer", "USER_READ"); } // ==================== Lagerort anlegen – Pflichtfelder ==================== @@ -622,18 +563,4 @@ class StorageLocationControllerIntegrationTest { return objectMapper.readTree(result.getResponse().getContentAsString()).get("id").asText(); } - - private String generateToken(String userId, String username, String permissions) { - long now = System.currentTimeMillis(); - javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( - jwtSecret.getBytes(StandardCharsets.UTF_8)); - return Jwts.builder() - .subject(userId) - .claim("username", username) - .claim("permissions", permissions) - .issuedAt(new Date(now)) - .expiration(new Date(now + jwtExpiration)) - .signWith(key) - .compact(); - } } diff --git a/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/ArticleControllerIntegrationTest.java b/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/ArticleControllerIntegrationTest.java index 8fe6c10..4f85d54 100644 --- a/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/ArticleControllerIntegrationTest.java +++ b/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/ArticleControllerIntegrationTest.java @@ -1,33 +1,19 @@ package de.effigenix.infrastructure.masterdata.web; -import com.fasterxml.jackson.databind.ObjectMapper; import de.effigenix.domain.masterdata.PriceModel; import de.effigenix.domain.masterdata.Unit; import de.effigenix.domain.usermanagement.RoleName; -import de.effigenix.domain.usermanagement.UserStatus; +import de.effigenix.infrastructure.AbstractIntegrationTest; import de.effigenix.infrastructure.masterdata.web.dto.*; import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity; import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity; -import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository; -import de.effigenix.infrastructure.usermanagement.persistence.repository.UserJpaRepository; -import io.jsonwebtoken.Jwts; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.Date; import java.util.Set; import java.util.UUID; @@ -40,33 +26,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * * Abgedeckte Testfälle: TC-ART-01 bis TC-ART-11 */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -@ActiveProfiles("test") -@Transactional @DisplayName("Article Controller Integration Tests") -class ArticleControllerIntegrationTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - private UserJpaRepository userRepository; - - @Autowired - private RoleJpaRepository roleRepository; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Value("${jwt.secret}") - private String jwtSecret; - - @Value("${jwt.expiration}") - private long jwtExpiration; +class ArticleControllerIntegrationTest extends AbstractIntegrationTest { private String adminToken; private String viewerToken; @@ -74,28 +35,14 @@ class ArticleControllerIntegrationTest { @BeforeEach void setUp() throws Exception { - RoleEntity adminRole = new RoleEntity( - UUID.randomUUID().toString(), RoleName.ADMIN, Set.of(), "Admin"); - roleRepository.save(adminRole); + RoleEntity adminRole = createRole(RoleName.ADMIN, "Admin"); + RoleEntity viewerRole = createRole(RoleName.PRODUCTION_WORKER, "Viewer"); - RoleEntity viewerRole = new RoleEntity( - UUID.randomUUID().toString(), RoleName.PRODUCTION_WORKER, Set.of(), "Viewer"); - roleRepository.save(viewerRole); + UserEntity admin = createUser("art.admin", "art.admin@test.com", Set.of(adminRole), "BRANCH-01"); + UserEntity viewer = createUser("art.viewer", "art.viewer@test.com", Set.of(viewerRole), "BRANCH-01"); - String adminId = UUID.randomUUID().toString(); - userRepository.save(new UserEntity( - adminId, "art.admin", "art.admin@test.com", - passwordEncoder.encode("Pass123"), Set.of(adminRole), - "BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null)); - - String viewerId = UUID.randomUUID().toString(); - userRepository.save(new UserEntity( - viewerId, "art.viewer", "art.viewer@test.com", - passwordEncoder.encode("Pass123"), Set.of(viewerRole), - "BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null)); - - adminToken = generateToken(adminId, "art.admin", "MASTERDATA_WRITE"); - viewerToken = generateToken(viewerId, "art.viewer", "USER_READ"); + adminToken = generateToken(admin.getId(), "art.admin", "MASTERDATA_WRITE"); + viewerToken = generateToken(viewer.getId(), "art.viewer", "USER_READ"); // Vorbedingung: Kategorie erstellen categoryId = createCategory("Obst & Gemüse"); @@ -414,18 +361,4 @@ class ArticleControllerIntegrationTest { .andReturn(); return objectMapper.readTree(result.getResponse().getContentAsString()).get("id").asText(); } - - private String generateToken(String userId, String username, String permissions) { - long now = System.currentTimeMillis(); - javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( - jwtSecret.getBytes(java.nio.charset.StandardCharsets.UTF_8)); - return Jwts.builder() - .subject(userId) - .claim("username", username) - .claim("permissions", permissions) - .issuedAt(new Date(now)) - .expiration(new Date(now + jwtExpiration)) - .signWith(key) - .compact(); - } } diff --git a/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/CustomerControllerIntegrationTest.java b/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/CustomerControllerIntegrationTest.java index 639edf7..f38e6a5 100644 --- a/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/CustomerControllerIntegrationTest.java +++ b/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/CustomerControllerIntegrationTest.java @@ -1,33 +1,19 @@ package de.effigenix.infrastructure.masterdata.web; -import com.fasterxml.jackson.databind.ObjectMapper; import de.effigenix.domain.masterdata.*; import de.effigenix.domain.usermanagement.RoleName; -import de.effigenix.domain.usermanagement.UserStatus; +import de.effigenix.infrastructure.AbstractIntegrationTest; import de.effigenix.infrastructure.masterdata.web.dto.*; import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity; import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity; -import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository; -import de.effigenix.infrastructure.usermanagement.persistence.repository.UserJpaRepository; -import io.jsonwebtoken.Jwts; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Date; import java.util.List; import java.util.Set; import java.util.UUID; @@ -41,61 +27,22 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * * Abgedeckte Testfälle: TC-CUS-01 bis TC-CUS-11, TC-B2B-01/02, TC-AUTH */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -@ActiveProfiles("test") -@Transactional @DisplayName("Customer Controller Integration Tests") -class CustomerControllerIntegrationTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - private UserJpaRepository userRepository; - - @Autowired - private RoleJpaRepository roleRepository; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Value("${jwt.secret}") - private String jwtSecret; - - @Value("${jwt.expiration}") - private long jwtExpiration; +class CustomerControllerIntegrationTest extends AbstractIntegrationTest { private String adminToken; private String viewerToken; @BeforeEach void setUp() { - RoleEntity adminRole = new RoleEntity( - UUID.randomUUID().toString(), RoleName.ADMIN, Set.of(), "Admin"); - roleRepository.save(adminRole); + RoleEntity adminRole = createRole(RoleName.ADMIN, "Admin"); + RoleEntity viewerRole = createRole(RoleName.PRODUCTION_WORKER, "Viewer"); - RoleEntity viewerRole = new RoleEntity( - UUID.randomUUID().toString(), RoleName.PRODUCTION_WORKER, Set.of(), "Viewer"); - roleRepository.save(viewerRole); + UserEntity admin = createUser("cus.admin", "cus.admin@test.com", Set.of(adminRole), "BRANCH-01"); + UserEntity viewer = createUser("cus.viewer", "cus.viewer@test.com", Set.of(viewerRole), "BRANCH-01"); - String adminId = UUID.randomUUID().toString(); - userRepository.save(new UserEntity( - adminId, "cus.admin", "cus.admin@test.com", - passwordEncoder.encode("Pass123"), Set.of(adminRole), - "BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null)); - - String viewerId = UUID.randomUUID().toString(); - userRepository.save(new UserEntity( - viewerId, "cus.viewer", "cus.viewer@test.com", - passwordEncoder.encode("Pass123"), Set.of(viewerRole), - "BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null)); - - adminToken = generateToken(adminId, "cus.admin", "MASTERDATA_WRITE"); - viewerToken = generateToken(viewerId, "cus.viewer", "USER_READ"); + adminToken = generateToken(admin.getId(), "cus.admin", "MASTERDATA_WRITE"); + viewerToken = generateToken(viewer.getId(), "cus.viewer", "USER_READ"); } // ==================== TC-CUS-01: B2C-Kunde erstellen ==================== @@ -538,18 +485,4 @@ class CustomerControllerIntegrationTest { .andReturn(); return objectMapper.readTree(artResult.getResponse().getContentAsString()).get("id").asText(); } - - private String generateToken(String userId, String username, String permissions) { - long now = System.currentTimeMillis(); - javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( - jwtSecret.getBytes(java.nio.charset.StandardCharsets.UTF_8)); - return Jwts.builder() - .subject(userId) - .claim("username", username) - .claim("permissions", permissions) - .issuedAt(new Date(now)) - .expiration(new Date(now + jwtExpiration)) - .signWith(key) - .compact(); - } } diff --git a/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/ProductCategoryControllerIntegrationTest.java b/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/ProductCategoryControllerIntegrationTest.java index 8d6774b..837f4c1 100644 --- a/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/ProductCategoryControllerIntegrationTest.java +++ b/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/ProductCategoryControllerIntegrationTest.java @@ -1,31 +1,17 @@ package de.effigenix.infrastructure.masterdata.web; -import com.fasterxml.jackson.databind.ObjectMapper; -import de.effigenix.domain.usermanagement.UserStatus; +import de.effigenix.domain.usermanagement.RoleName; +import de.effigenix.infrastructure.AbstractIntegrationTest; import de.effigenix.infrastructure.masterdata.web.dto.CreateProductCategoryRequest; import de.effigenix.infrastructure.masterdata.web.dto.UpdateProductCategoryRequest; import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity; import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity; -import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository; -import de.effigenix.infrastructure.usermanagement.persistence.repository.UserJpaRepository; -import de.effigenix.domain.usermanagement.RoleName; -import io.jsonwebtoken.Jwts; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Date; import java.util.Set; import java.util.UUID; @@ -38,61 +24,22 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * * Abgedeckte Testfälle: TC-CAT-01 bis TC-CAT-06 */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -@ActiveProfiles("test") -@Transactional @DisplayName("ProductCategory Controller Integration Tests") -class ProductCategoryControllerIntegrationTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - private UserJpaRepository userRepository; - - @Autowired - private RoleJpaRepository roleRepository; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Value("${jwt.secret}") - private String jwtSecret; - - @Value("${jwt.expiration}") - private long jwtExpiration; +class ProductCategoryControllerIntegrationTest extends AbstractIntegrationTest { private String adminToken; private String viewerToken; @BeforeEach void setUp() { - RoleEntity adminRole = new RoleEntity( - UUID.randomUUID().toString(), RoleName.ADMIN, Set.of(), "Admin"); - roleRepository.save(adminRole); + RoleEntity adminRole = createRole(RoleName.ADMIN, "Admin"); + RoleEntity viewerRole = createRole(RoleName.PRODUCTION_WORKER, "Viewer"); - RoleEntity viewerRole = new RoleEntity( - UUID.randomUUID().toString(), RoleName.PRODUCTION_WORKER, Set.of(), "Viewer"); - roleRepository.save(viewerRole); + UserEntity admin = createUser("cat.admin", "cat.admin@test.com", Set.of(adminRole), "BRANCH-01"); + UserEntity viewer = createUser("cat.viewer", "cat.viewer@test.com", Set.of(viewerRole), "BRANCH-01"); - String adminId = UUID.randomUUID().toString(); - userRepository.save(new UserEntity( - adminId, "cat.admin", "cat.admin@test.com", - passwordEncoder.encode("Pass123"), Set.of(adminRole), - "BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null)); - - String viewerId = UUID.randomUUID().toString(); - userRepository.save(new UserEntity( - viewerId, "cat.viewer", "cat.viewer@test.com", - passwordEncoder.encode("Pass123"), Set.of(viewerRole), - "BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null)); - - adminToken = generateToken(adminId, "cat.admin", "MASTERDATA_WRITE"); - viewerToken = generateToken(viewerId, "cat.viewer", "USER_READ"); + adminToken = generateToken(admin.getId(), "cat.admin", "MASTERDATA_WRITE"); + viewerToken = generateToken(viewer.getId(), "cat.viewer", "USER_READ"); } // ==================== TC-CAT-01: Kategorie erstellen (Happy Path) ==================== @@ -274,18 +221,4 @@ class ProductCategoryControllerIntegrationTest { .andReturn(); return objectMapper.readTree(result.getResponse().getContentAsString()).get("id").asText(); } - - private String generateToken(String userId, String username, String permissions) { - long now = System.currentTimeMillis(); - javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( - jwtSecret.getBytes(java.nio.charset.StandardCharsets.UTF_8)); - return Jwts.builder() - .subject(userId) - .claim("username", username) - .claim("permissions", permissions) - .issuedAt(new Date(now)) - .expiration(new Date(now + jwtExpiration)) - .signWith(key) - .compact(); - } } diff --git a/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/SupplierControllerIntegrationTest.java b/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/SupplierControllerIntegrationTest.java index b39ffc0..47156e7 100644 --- a/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/SupplierControllerIntegrationTest.java +++ b/backend/src/test/java/de/effigenix/infrastructure/masterdata/web/SupplierControllerIntegrationTest.java @@ -1,31 +1,17 @@ package de.effigenix.infrastructure.masterdata.web; -import com.fasterxml.jackson.databind.ObjectMapper; import de.effigenix.domain.usermanagement.RoleName; -import de.effigenix.domain.usermanagement.UserStatus; +import de.effigenix.infrastructure.AbstractIntegrationTest; import de.effigenix.infrastructure.masterdata.web.dto.*; import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity; import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity; -import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository; -import de.effigenix.infrastructure.usermanagement.persistence.repository.UserJpaRepository; -import io.jsonwebtoken.Jwts; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Date; import java.util.Set; import java.util.UUID; @@ -38,61 +24,22 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * * Abgedeckte Testfälle: TC-SUP-01 bis TC-SUP-12, TC-AUTH-01/02 */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -@ActiveProfiles("test") -@Transactional @DisplayName("Supplier Controller Integration Tests") -class SupplierControllerIntegrationTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - private UserJpaRepository userRepository; - - @Autowired - private RoleJpaRepository roleRepository; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Value("${jwt.secret}") - private String jwtSecret; - - @Value("${jwt.expiration}") - private long jwtExpiration; +class SupplierControllerIntegrationTest extends AbstractIntegrationTest { private String adminToken; private String viewerToken; @BeforeEach void setUp() { - RoleEntity adminRole = new RoleEntity( - UUID.randomUUID().toString(), RoleName.ADMIN, Set.of(), "Admin"); - roleRepository.save(adminRole); + RoleEntity adminRole = createRole(RoleName.ADMIN, "Admin"); + RoleEntity viewerRole = createRole(RoleName.PRODUCTION_WORKER, "Viewer"); - RoleEntity viewerRole = new RoleEntity( - UUID.randomUUID().toString(), RoleName.PRODUCTION_WORKER, Set.of(), "Viewer"); - roleRepository.save(viewerRole); + UserEntity admin = createUser("sup.admin", "sup.admin@test.com", Set.of(adminRole), "BRANCH-01"); + UserEntity viewer = createUser("sup.viewer", "sup.viewer@test.com", Set.of(viewerRole), "BRANCH-01"); - String adminId = UUID.randomUUID().toString(); - userRepository.save(new UserEntity( - adminId, "sup.admin", "sup.admin@test.com", - passwordEncoder.encode("Pass123"), Set.of(adminRole), - "BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null)); - - String viewerId = UUID.randomUUID().toString(); - userRepository.save(new UserEntity( - viewerId, "sup.viewer", "sup.viewer@test.com", - passwordEncoder.encode("Pass123"), Set.of(viewerRole), - "BRANCH-01", UserStatus.ACTIVE, LocalDateTime.now(), null)); - - adminToken = generateToken(adminId, "sup.admin", "MASTERDATA_WRITE"); - viewerToken = generateToken(viewerId, "sup.viewer", "USER_READ"); + adminToken = generateToken(admin.getId(), "sup.admin", "MASTERDATA_WRITE"); + viewerToken = generateToken(viewer.getId(), "sup.viewer", "USER_READ"); } // ==================== TC-SUP-01: Lieferant erstellen – Pflichtfelder ==================== @@ -449,18 +396,4 @@ class SupplierControllerIntegrationTest { .andReturn(); return objectMapper.readTree(result.getResponse().getContentAsString()).get("id").asText(); } - - private String generateToken(String userId, String username, String permissions) { - long now = System.currentTimeMillis(); - javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( - jwtSecret.getBytes(java.nio.charset.StandardCharsets.UTF_8)); - return Jwts.builder() - .subject(userId) - .claim("username", username) - .claim("permissions", permissions) - .issuedAt(new Date(now)) - .expiration(new Date(now + jwtExpiration)) - .signWith(key) - .compact(); - } } diff --git a/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/AuthControllerIntegrationTest.java b/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/AuthControllerIntegrationTest.java index db7916a..57bb44b 100644 --- a/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/AuthControllerIntegrationTest.java +++ b/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/AuthControllerIntegrationTest.java @@ -1,35 +1,19 @@ package de.effigenix.infrastructure.usermanagement.web; -import de.effigenix.application.usermanagement.dto.SessionToken; import de.effigenix.domain.usermanagement.RoleName; import de.effigenix.domain.usermanagement.UserStatus; +import de.effigenix.infrastructure.AbstractIntegrationTest; import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity; import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity; -import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository; -import de.effigenix.infrastructure.usermanagement.persistence.repository.UserJpaRepository; import de.effigenix.infrastructure.usermanagement.web.dto.LoginRequest; import de.effigenix.infrastructure.usermanagement.web.dto.LoginResponse; import de.effigenix.infrastructure.usermanagement.web.dto.RefreshTokenRequest; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Date; -import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -39,89 +23,25 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. /** * Integration tests for Authentication Controller. - * - * Tests authentication flows: - * - Login with valid/invalid credentials - * - JWT token validation - * - Logout flow - * - Refresh token flow - * - * Uses: - * - @SpringBootTest for full application context - * - @AutoConfigureMockMvc for MockMvc HTTP testing - * - @Transactional for test isolation - * - H2 in-memory database (configured in application-test.yml) - * - * Test Database: - * - H2 in-memory database - * - ddl-auto: create-drop (schema recreated for each test) - * - Each test runs in a transaction and is rolled back after completion */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -@ActiveProfiles("test") -@Transactional @DisplayName("Authentication Controller Integration Tests") -class AuthControllerIntegrationTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - private UserJpaRepository userRepository; - - @Autowired - private RoleJpaRepository roleRepository; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Value("${jwt.secret}") - private String jwtSecret; - - @Value("${jwt.expiration}") - private long jwtExpiration; +class AuthControllerIntegrationTest extends AbstractIntegrationTest { private String validUsername; - private String validEmail; - private String validPassword; private String validUserId; private String validRefreshToken; @BeforeEach void setUp() { validUsername = "auth.test.user"; - validEmail = "auth.test@example.com"; - validPassword = "SecurePass123"; - validUserId = UUID.randomUUID().toString(); - // Create test user with ADMIN role - RoleEntity adminRole = new RoleEntity( - UUID.randomUUID().toString(), - RoleName.ADMIN, - Set.of(), // Empty permissions for testing - "Admin role" - ); - roleRepository.save(adminRole); + RoleEntity adminRole = createRole(RoleName.ADMIN, "Admin role"); - UserEntity testUser = new UserEntity( - validUserId, - validUsername, - validEmail, - passwordEncoder.encode(validPassword), - Set.of(adminRole), - "BRANCH-TEST-001", - UserStatus.ACTIVE, - LocalDateTime.now(), - null - ); - userRepository.save(testUser); + UserEntity testUser = createUser(validUsername, "auth.test@example.com", + Set.of(adminRole), "BRANCH-TEST-001"); + validUserId = testUser.getId(); - // Create a valid refresh token - validRefreshToken = generateTestRefreshToken(validUserId, validUsername); + validRefreshToken = generateRefreshToken(validUserId, validUsername); } // ==================== LOGIN TESTS ==================== @@ -129,7 +49,7 @@ class AuthControllerIntegrationTest { @Test @DisplayName("Login with valid credentials should return 200 with JWT tokens") void testLoginWithValidCredentials() throws Exception { - LoginRequest request = new LoginRequest(validUsername, validPassword); + LoginRequest request = new LoginRequest(validUsername, TEST_PASSWORD); MvcResult result = mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) @@ -154,7 +74,7 @@ class AuthControllerIntegrationTest { @Test @DisplayName("Login with invalid username should return 401 Unauthorized") void testLoginWithInvalidUsername() throws Exception { - LoginRequest request = new LoginRequest("non.existent.user", validPassword); + LoginRequest request = new LoginRequest("non.existent.user", TEST_PASSWORD); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) @@ -179,12 +99,11 @@ class AuthControllerIntegrationTest { @Test @DisplayName("Login with locked user should return 401 Unauthorized") void testLoginWithLockedUser() throws Exception { - // Lock the user UserEntity user = userRepository.findByUsername(validUsername).orElseThrow(); user.setStatus(UserStatus.LOCKED); userRepository.save(user); - LoginRequest request = new LoginRequest(validUsername, validPassword); + LoginRequest request = new LoginRequest(validUsername, TEST_PASSWORD); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) @@ -196,12 +115,11 @@ class AuthControllerIntegrationTest { @Test @DisplayName("Login with inactive user should return 401 Unauthorized") void testLoginWithInactiveUser() throws Exception { - // Set user to inactive UserEntity user = userRepository.findByUsername(validUsername).orElseThrow(); user.setStatus(UserStatus.INACTIVE); userRepository.save(user); - LoginRequest request = new LoginRequest(validUsername, validPassword); + LoginRequest request = new LoginRequest(validUsername, TEST_PASSWORD); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) @@ -213,7 +131,7 @@ class AuthControllerIntegrationTest { @Test @DisplayName("Login with missing username should return 400 Bad Request") void testLoginWithMissingUsername() throws Exception { - LoginRequest request = new LoginRequest("", validPassword); + LoginRequest request = new LoginRequest("", TEST_PASSWORD); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) @@ -250,7 +168,7 @@ class AuthControllerIntegrationTest { @DisplayName("Logout with valid JWT token should return 204 No Content") void testLogoutWithValidToken() throws Exception { // First login to get a valid token - LoginRequest loginRequest = new LoginRequest(validUsername, validPassword); + LoginRequest loginRequest = new LoginRequest(validUsername, TEST_PASSWORD); MvcResult loginResult = mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(loginRequest))) @@ -303,8 +221,6 @@ class AuthControllerIntegrationTest { void testRefreshTokenWithValidToken() throws Exception { RefreshTokenRequest request = new RefreshTokenRequest(validRefreshToken); - // Note: refreshSession() is not yet implemented in JwtSessionManager, - // so it throws UnsupportedOperationException which maps to 401 mockMvc.perform(post("/api/auth/refresh") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) @@ -351,8 +267,6 @@ class AuthControllerIntegrationTest { void testRefreshTokenIsPublic() throws Exception { RefreshTokenRequest request = new RefreshTokenRequest(validRefreshToken); - // Endpoint is public (permitAll) - the 401 comes from the unimplemented - // refreshSession(), not from missing JWT authentication mockMvc.perform(post("/api/auth/refresh") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) @@ -365,7 +279,7 @@ class AuthControllerIntegrationTest { @Test @DisplayName("JWT token should contain valid payload information") void testJWTTokenContainsValidPayload() throws Exception { - LoginRequest request = new LoginRequest(validUsername, validPassword); + LoginRequest request = new LoginRequest(validUsername, TEST_PASSWORD); MvcResult result = mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) @@ -385,7 +299,7 @@ class AuthControllerIntegrationTest { @Test @DisplayName("Token expiration time should match configured expiration") void testTokenExpirationTime() throws Exception { - LoginRequest request = new LoginRequest(validUsername, validPassword); + LoginRequest request = new LoginRequest(validUsername, TEST_PASSWORD); MvcResult result = mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) @@ -405,7 +319,7 @@ class AuthControllerIntegrationTest { @Test @DisplayName("Multiple logins should return different tokens") void testMultipleLoginsReturnDifferentTokens() throws Exception { - LoginRequest request = new LoginRequest(validUsername, validPassword); + LoginRequest request = new LoginRequest(validUsername, TEST_PASSWORD); // First login MvcResult result1 = mockMvc.perform(post("/api/auth/login") @@ -433,24 +347,4 @@ class AuthControllerIntegrationTest { // Tokens should be different assertThat(response1.accessToken()).isNotEqualTo(response2.accessToken()); } - - // ==================== HELPER METHODS ==================== - - /** - * Generates a test refresh token for testing refresh endpoint. - * In production, refresh tokens are managed by SessionManager. - */ - private String generateTestRefreshToken(String userId, String username) { - long now = System.currentTimeMillis(); - javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( - jwtSecret.getBytes(java.nio.charset.StandardCharsets.UTF_8)); - return Jwts.builder() - .subject(userId) - .claim("username", username) - .claim("type", "refresh") - .issuedAt(new Date(now)) - .expiration(new Date(now + 7200000)) // 2 hours - .signWith(key) - .compact(); - } } diff --git a/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/SecurityIntegrationTest.java b/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/SecurityIntegrationTest.java index 3dc51f3..e6753cb 100644 --- a/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/SecurityIntegrationTest.java +++ b/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/SecurityIntegrationTest.java @@ -2,36 +2,23 @@ package de.effigenix.infrastructure.usermanagement.web; import de.effigenix.domain.usermanagement.RoleName; import de.effigenix.domain.usermanagement.UserStatus; +import de.effigenix.infrastructure.AbstractIntegrationTest; import de.effigenix.infrastructure.audit.AuditLogEntity; import de.effigenix.infrastructure.audit.AuditLogJpaRepository; import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity; import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity; -import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository; -import de.effigenix.infrastructure.usermanagement.persistence.repository.UserJpaRepository; import de.effigenix.infrastructure.usermanagement.web.dto.CreateUserRequest; import de.effigenix.infrastructure.usermanagement.web.dto.LoginRequest; -import de.effigenix.infrastructure.usermanagement.web.dto.LoginResponse; import de.effigenix.infrastructure.usermanagement.web.dto.UpdateUserRequest; import de.effigenix.application.usermanagement.AuditEvent; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; -import java.util.Date; import java.util.List; import java.util.Set; import java.util.UUID; @@ -42,51 +29,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. /** * Integration tests for Security and Authorization. - * - * Tests: - * - Authorization (ADMIN-only endpoints reject non-admin users) - * - Branch-based filtering - * - Missing/expired JWT returns 401 - * - Audit logging for critical operations - * - Verify audit logs contain actor, timestamp, IP address - * - * Uses: - * - @SpringBootTest for full application context - * - @AutoConfigureMockMvc for MockMvc HTTP testing - * - @Transactional for test isolation - * - H2 in-memory database (configured in application-test.yml) */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -@ActiveProfiles("test") -@Transactional @DisplayName("Security and Authorization Integration Tests") -class SecurityIntegrationTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - private UserJpaRepository userRepository; - - @Autowired - private RoleJpaRepository roleRepository; +class SecurityIntegrationTest extends AbstractIntegrationTest { @Autowired private AuditLogJpaRepository auditLogRepository; - @Autowired - private PasswordEncoder passwordEncoder; - - @Value("${jwt.secret}") - private String jwtSecret; - - @Value("${jwt.expiration}") - private long jwtExpiration; - private String adminToken; private String regularUserToken; private String expiredToken; @@ -100,54 +49,17 @@ class SecurityIntegrationTest { @BeforeEach void setUp() { - // Create roles - adminRole = new RoleEntity( - UUID.randomUUID().toString(), - RoleName.ADMIN, - Set.of(), // Empty permissions for testing - "Admin role" - ); - roleRepository.save(adminRole); + adminRole = createRole(RoleName.ADMIN, "Admin role"); + userRole = createRole(RoleName.PRODUCTION_WORKER, "Production worker role"); - userRole = new RoleEntity( - UUID.randomUUID().toString(), - RoleName.PRODUCTION_WORKER, - Set.of(), // Empty permissions for testing - "Production worker role" - ); - roleRepository.save(userRole); + UserEntity adminUser = createUser("security.admin", "admin@security.test", + Set.of(adminRole), "BRANCH-ADMIN"); + adminUserId = adminUser.getId(); - // Create admin user - adminUserId = UUID.randomUUID().toString(); - UserEntity adminUser = new UserEntity( - adminUserId, - "security.admin", - "admin@security.test", - passwordEncoder.encode("AdminPass123"), - Set.of(adminRole), - "BRANCH-ADMIN", - UserStatus.ACTIVE, - LocalDateTime.now(), - null - ); - userRepository.save(adminUser); + UserEntity regularUser = createUser("security.user", "user@security.test", + Set.of(userRole), "BRANCH-USER"); + regularUserId = regularUser.getId(); - // Create regular user - regularUserId = UUID.randomUUID().toString(); - UserEntity regularUser = new UserEntity( - regularUserId, - "security.user", - "user@security.test", - passwordEncoder.encode("UserPass123"), - Set.of(userRole), - "BRANCH-USER", - UserStatus.ACTIVE, - LocalDateTime.now(), - null - ); - userRepository.save(regularUser); - - // Generate tokens adminToken = generateTestJWT(adminUserId, "security.admin", true); regularUserToken = generateTestJWT(regularUserId, "security.user", false); expiredToken = generateExpiredToken(adminUserId, "security.admin"); @@ -283,7 +195,7 @@ class SecurityIntegrationTest { @Test @DisplayName("Login endpoint without authentication should return 401 or 200 depending on credentials") void testLoginEndpointIsPublic() throws Exception { - LoginRequest request = new LoginRequest("security.admin", "AdminPass123"); + LoginRequest request = new LoginRequest("security.admin", TEST_PASSWORD); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) @@ -295,11 +207,8 @@ class SecurityIntegrationTest { @Test @DisplayName("Refresh token endpoint without authentication should be public") void testRefreshEndpointIsPublic() throws Exception { - // Generate a valid refresh token - String refreshToken = generateTestRefreshToken(adminUserId, "security.admin"); + String refreshToken = generateRefreshToken(adminUserId, "security.admin"); - // Endpoint is public (permitAll) - the 401 comes from unimplemented - // refreshSession(), not from missing JWT authentication mockMvc.perform(post("/api/auth/refresh") .contentType(MediaType.APPLICATION_JSON) .content("{\"refreshToken\":\"" + refreshToken + "\"}")) @@ -312,29 +221,13 @@ class SecurityIntegrationTest { @Test @DisplayName("Users should see data filtered by their branch (if filtering is implemented)") void testBranchBasedDataVisibility() throws Exception { - // Create user in different branch - String otherBranchUserId = UUID.randomUUID().toString(); - UserEntity otherBranchUser = new UserEntity( - otherBranchUserId, - "other.branch.user", - "other@branch.test", - passwordEncoder.encode("OtherPass123"), - Set.of(userRole), - "BRANCH-OTHER", - UserStatus.ACTIVE, - LocalDateTime.now(), - null - ); - userRepository.save(otherBranchUser); + UserEntity otherBranchUser = createUser("other.branch.user", "other@branch.test", + Set.of(userRole), "BRANCH-OTHER"); - // Regular user token for BRANCH-USER mockMvc.perform(get("/api/users") .header("Authorization", "Bearer " + regularUserToken)) .andExpect(status().isOk()) .andReturn(); - - // Both branches should be visible in list (if no filtering is implemented) - // This test documents the current behavior } // ==================== AUDIT LOGGING TESTS ==================== @@ -342,7 +235,6 @@ class SecurityIntegrationTest { @Test @DisplayName("Create user operation should create audit log entry") void testCreateUserAuditLogging() throws Exception { - // Clear existing audit logs auditLogRepository.deleteAll(); CreateUserRequest request = new CreateUserRequest( @@ -360,7 +252,6 @@ class SecurityIntegrationTest { .andExpect(status().isCreated()) .andReturn(); - // Verify audit log was created List logs = auditLogRepository.findAll(); assertThat(logs) .filteredOn(log -> log.getEvent() == AuditEvent.USER_CREATED) @@ -402,7 +293,6 @@ class SecurityIntegrationTest { .findFirst() .orElseThrow(); - // Verify actor is the admin user assertThat(auditLog.getPerformedBy()).isEqualTo(adminUserId); assertThat(auditLog.getPerformedBy()).isNotBlank(); } @@ -436,7 +326,6 @@ class SecurityIntegrationTest { .findFirst() .orElseThrow(); - // Verify timestamp is present and recent (within last minute) assertThat(auditLog.getTimestamp()).isNotNull(); assertThat(auditLog.getTimestamp()).isAfter(LocalDateTime.now().minusMinutes(1)); } @@ -451,7 +340,6 @@ class SecurityIntegrationTest { .andExpect(status().isOk()) .andReturn(); - // Verify audit log was created List logs = auditLogRepository.findAll(); assertThat(logs) .filteredOn(log -> log.getEvent() == AuditEvent.USER_LOCKED) @@ -471,18 +359,15 @@ class SecurityIntegrationTest { void testUnlockUserAuditLogging() throws Exception { auditLogRepository.deleteAll(); - // First lock the user UserEntity user = userRepository.findById(regularUserId).orElseThrow(); user.setStatus(UserStatus.LOCKED); userRepository.save(user); - // Then unlock mockMvc.perform(post("/api/users/{id}/unlock", regularUserId) .header("Authorization", "Bearer " + adminToken)) .andExpect(status().isOk()) .andReturn(); - // Verify audit log was created List logs = auditLogRepository.findAll(); assertThat(logs) .filteredOn(log -> log.getEvent() == AuditEvent.USER_UNLOCKED) @@ -506,7 +391,6 @@ class SecurityIntegrationTest { .andExpect(status().isOk()) .andReturn(); - // Verify audit log was created List logs = auditLogRepository.findAll(); assertThat(logs) .filteredOn(log -> log.getEvent() == AuditEvent.USER_UPDATED) @@ -526,7 +410,7 @@ class SecurityIntegrationTest { void testLoginSuccessAuditLogging() throws Exception { auditLogRepository.deleteAll(); - LoginRequest request = new LoginRequest("security.admin", "AdminPass123"); + LoginRequest request = new LoginRequest("security.admin", TEST_PASSWORD); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) @@ -534,7 +418,6 @@ class SecurityIntegrationTest { .andExpect(status().isOk()) .andReturn(); - // Verify audit log was created List logs = auditLogRepository.findAll(); assertThat(logs) .filteredOn(log -> log.getEvent() == AuditEvent.LOGIN_SUCCESS) @@ -554,7 +437,6 @@ class SecurityIntegrationTest { .andExpect(status().isUnauthorized()) .andReturn(); - // Verify audit log was created List logs = auditLogRepository.findAll(); assertThat(logs) .filteredOn(log -> log.getEvent() == AuditEvent.LOGIN_FAILED) @@ -563,58 +445,10 @@ class SecurityIntegrationTest { // ==================== HELPER METHODS ==================== - /** - * Generates a test JWT token with admin permissions. - */ private String generateTestJWT(String userId, String username, boolean isAdmin) { - long now = System.currentTimeMillis(); String permissions = isAdmin ? "USER_READ,USER_WRITE,USER_DELETE,USER_LOCK,USER_UNLOCK,ROLE_READ,ROLE_WRITE,ROLE_ASSIGN,ROLE_REMOVE" : "USER_READ"; - javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( - jwtSecret.getBytes(java.nio.charset.StandardCharsets.UTF_8)); - - return Jwts.builder() - .subject(userId) - .claim("username", username) - .claim("permissions", permissions) - .issuedAt(new Date(now)) - .expiration(new Date(now + jwtExpiration)) - .signWith(key) - .compact(); - } - - /** - * Generates an expired JWT token for testing expired token handling. - */ - private String generateExpiredToken(String userId, String username) { - long now = System.currentTimeMillis(); - javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( - jwtSecret.getBytes(java.nio.charset.StandardCharsets.UTF_8)); - return Jwts.builder() - .subject(userId) - .claim("username", username) - .claim("permissions", "") - .issuedAt(new Date(now - 10000)) - .expiration(new Date(now - 5000)) // Expired 5 seconds ago - .signWith(key) - .compact(); - } - - /** - * Generates a test refresh token. - */ - private String generateTestRefreshToken(String userId, String username) { - long now = System.currentTimeMillis(); - javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( - jwtSecret.getBytes(java.nio.charset.StandardCharsets.UTF_8)); - return Jwts.builder() - .subject(userId) - .claim("username", username) - .claim("type", "refresh") - .issuedAt(new Date(now)) - .expiration(new Date(now + 7200000)) - .signWith(key) - .compact(); + return generateToken(userId, username, permissions); } } diff --git a/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/UserControllerIntegrationTest.java b/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/UserControllerIntegrationTest.java index c20c8dc..d8a4164 100644 --- a/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/UserControllerIntegrationTest.java +++ b/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/UserControllerIntegrationTest.java @@ -1,33 +1,18 @@ package de.effigenix.infrastructure.usermanagement.web; -import de.effigenix.application.usermanagement.dto.UserDTO; import de.effigenix.domain.usermanagement.RoleName; import de.effigenix.domain.usermanagement.UserStatus; +import de.effigenix.infrastructure.AbstractIntegrationTest; import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity; import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity; -import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository; -import de.effigenix.infrastructure.usermanagement.persistence.repository.UserJpaRepository; import de.effigenix.infrastructure.usermanagement.web.dto.*; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Date; -import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -38,49 +23,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. /** * Integration tests for User Management Controller. - * - * Tests user management operations: - * - Create user (success, validation errors, duplicates) - * - List users - * - Get user by ID - * - Update user - * - Lock/unlock user - * - Change password - * - * Uses: - * - @SpringBootTest for full application context - * - @AutoConfigureMockMvc for MockMvc HTTP testing - * - @Transactional for test isolation - * - H2 in-memory database (configured in application-test.yml) */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -@ActiveProfiles("test") -@Transactional @DisplayName("User Controller Integration Tests") -class UserControllerIntegrationTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - private UserJpaRepository userRepository; - - @Autowired - private RoleJpaRepository roleRepository; +class UserControllerIntegrationTest extends AbstractIntegrationTest { @Autowired private PasswordEncoder passwordEncoder; - @Value("${jwt.secret}") - private String jwtSecret; - - @Value("${jwt.expiration}") - private long jwtExpiration; - private String adminToken; private String regularUserToken; private String adminUserId; @@ -91,54 +40,15 @@ class UserControllerIntegrationTest { @BeforeEach void setUp() { - // Create roles - adminRole = new RoleEntity( - UUID.randomUUID().toString(), - RoleName.ADMIN, - Set.of(), // Empty permissions for testing - "Admin role" - ); - roleRepository.save(adminRole); + adminRole = createRole(RoleName.ADMIN, "Admin role"); + userRole = createRole(RoleName.PRODUCTION_WORKER, "Production worker role"); - userRole = new RoleEntity( - UUID.randomUUID().toString(), - RoleName.PRODUCTION_WORKER, - Set.of(), // Empty permissions for testing - "Production worker role" - ); - roleRepository.save(userRole); + UserEntity adminUser = createUser("admin.user", "admin@example.com", Set.of(adminRole), "BRANCH-001"); + adminUserId = adminUser.getId(); - // Create admin user - adminUserId = UUID.randomUUID().toString(); - UserEntity adminUser = new UserEntity( - adminUserId, - "admin.user", - "admin@example.com", - passwordEncoder.encode("AdminPass123"), - Set.of(adminRole), - "BRANCH-001", - UserStatus.ACTIVE, - LocalDateTime.now(), - null - ); - userRepository.save(adminUser); + UserEntity regularUser = createUser("regular.user", "regular@example.com", Set.of(userRole), "BRANCH-001"); + regularUserId = regularUser.getId(); - // Create regular user - regularUserId = UUID.randomUUID().toString(); - UserEntity regularUser = new UserEntity( - regularUserId, - "regular.user", - "regular@example.com", - passwordEncoder.encode("RegularPass123"), - Set.of(userRole), - "BRANCH-001", - UserStatus.ACTIVE, - LocalDateTime.now(), - null - ); - userRepository.save(regularUser); - - // Generate JWT tokens adminToken = generateTestJWT(adminUserId, "admin.user", true); regularUserToken = generateTestJWT(regularUserId, "regular.user", false); } @@ -515,7 +425,7 @@ class UserControllerIntegrationTest { @DisplayName("Change password with correct current password should return 204 No Content") void testChangePasswordWithValidCurrentPassword() throws Exception { ChangePasswordRequest request = new ChangePasswordRequest( - "RegularPass123", // Current password + TEST_PASSWORD, // Current password "NewSecurePass456!" ); @@ -551,7 +461,7 @@ class UserControllerIntegrationTest { @DisplayName("Change password with new password too short should return 400 Bad Request") void testChangePasswordWithShortNewPassword() throws Exception { ChangePasswordRequest request = new ChangePasswordRequest( - "RegularPass123", + TEST_PASSWORD, "Short1" // Less than 8 characters ); @@ -585,7 +495,7 @@ class UserControllerIntegrationTest { @DisplayName("Change password without authentication should return 401 Unauthorized") void testChangePasswordWithoutAuthentication() throws Exception { ChangePasswordRequest request = new ChangePasswordRequest( - "RegularPass123", + TEST_PASSWORD, "NewSecurePass456!" ); @@ -598,23 +508,10 @@ class UserControllerIntegrationTest { // ==================== HELPER METHODS ==================== - /** - * Generates a test JWT token with USER_MANAGEMENT permission for admin. - */ private String generateTestJWT(String userId, String username, boolean isAdmin) { - long now = System.currentTimeMillis(); - javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( - jwtSecret.getBytes(java.nio.charset.StandardCharsets.UTF_8)); String permissions = isAdmin ? "USER_READ,USER_WRITE,USER_DELETE,USER_LOCK,USER_UNLOCK,ROLE_READ,ROLE_WRITE,ROLE_ASSIGN,ROLE_REMOVE" : "USER_READ"; - return Jwts.builder() - .subject(userId) - .claim("username", username) - .claim("permissions", permissions) - .issuedAt(new Date(now)) - .expiration(new Date(now + jwtExpiration)) - .signWith(key) - .compact(); + return generateToken(userId, username, permissions); } }