1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 10:19:35 +01:00

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.
This commit is contained in:
Sebastian Frick 2026-02-19 21:33:44 +01:00
parent 5219c93dd1
commit 7b1c114693
9 changed files with 195 additions and 803 deletions

View file

@ -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<RoleEntity> 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);
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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<AuditLogEntity> 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<AuditLogEntity> 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<AuditLogEntity> 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<AuditLogEntity> 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<AuditLogEntity> 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<AuditLogEntity> 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);
}
}

View file

@ -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);
}
}