Public Access
Added new tests fixed some problem
This commit is contained in:
1 parent
14a4ccfb87
commit
20402eb7c4
32 files changed
+11120
-78
No files matched your search
+10713
File diff suppressed because it is too large.
Load diff
@@ -105,6 +105,11 @@
|
|||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-jpa-test</artifactId>
|
<artifactId>spring-boot-starter-data-jpa-test</artifactId>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package auth_service.configuration.security;
|
package auth_service.configuration.security;
|
||||||
|
|
||||||
|
import auth_service.domain.login.service.TokenGenerator;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import io.jsonwebtoken.security.Keys;
|
import io.jsonwebtoken.security.Keys;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -12,7 +13,7 @@ import java.util.Date;
|
|||||||
* Service responsible for generating and validating JSON Web Tokens (JWT).
|
* Service responsible for generating and validating JSON Web Tokens (JWT).
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class JwtService {
|
public class JwtService implements TokenGenerator {
|
||||||
|
|
||||||
// Token validity: 24 hours in milliseconds
|
// Token validity: 24 hours in milliseconds
|
||||||
private static final long JWT_EXPIRATION_MS = 86400000;
|
private static final long JWT_EXPIRATION_MS = 86400000;
|
||||||
@@ -24,10 +25,10 @@ public class JwtService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a secure JWT for the authenticated user.
|
* Generates a secure JWT for the authenticated user.
|
||||||
* * @param user The authenticated user entity
|
|
||||||
*
|
*
|
||||||
* @return A signed JWT string
|
* @return A signed JWT string
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public String generateToken(String userId, String email) {
|
public String generateToken(String userId, String email) {
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.subject(email)
|
.subject(email)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package auth_service.domain.login.controller;
|
package auth_service.domain.login.controller;
|
||||||
|
|
||||||
import auth_service.configuration.security.JwtService;
|
|
||||||
import auth_service.domain.login.controller.dto.request.LoginRequest;
|
import auth_service.domain.login.controller.dto.request.LoginRequest;
|
||||||
import auth_service.domain.login.controller.dto.response.LoginResponse;
|
import auth_service.domain.login.controller.dto.response.LoginResponse;
|
||||||
import auth_service.domain.login.controller.mapper.ApiMapper;
|
import auth_service.domain.login.controller.mapper.ApiMapper;
|
||||||
@@ -22,12 +21,11 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
public class LoginController {
|
public class LoginController {
|
||||||
|
|
||||||
private final LoginService loginService;
|
private final LoginService loginService;
|
||||||
private final JwtService jwtService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticates a user based on email and password.
|
* Authenticates a user based on email and password.
|
||||||
* * @param request The login credentials
|
|
||||||
*
|
*
|
||||||
|
* @param request The login credentials
|
||||||
* @return HTTP 200 on success, HTTP 401 on failure
|
* @return HTTP 200 on success, HTTP 401 on failure
|
||||||
*/
|
*/
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
@@ -35,8 +33,7 @@ public class LoginController {
|
|||||||
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
|
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
|
||||||
|
|
||||||
var user = ApiMapper.toModel(request);
|
var user = ApiMapper.toModel(request);
|
||||||
var authenticatedUser = loginService.login(user);
|
var token = loginService.login(user);
|
||||||
var token = jwtService.generateToken(authenticatedUser.getId() + "", authenticatedUser.getEmail());
|
|
||||||
return ResponseEntity.ok(new LoginResponse("Login successful", token));
|
return ResponseEntity.ok(new LoginResponse("Login successful", token));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package auth_service.domain.login.controller;
|
package auth_service.domain.login.controller;
|
||||||
|
|
||||||
import auth_service.domain.login.controller.dto.response.ErrorResponse;
|
import auth_service.shared.controller.dto.response.ErrorResponse;
|
||||||
import auth_service.domain.login.exception.InvalidCredentialsException;
|
import auth_service.domain.login.exception.InvalidCredentialsException;
|
||||||
import auth_service.domain.login.exception.UserNotFoundException;
|
import auth_service.domain.login.exception.UserNotFoundException;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
package auth_service.domain.login.controller.dto.response;
|
|
||||||
|
|
||||||
public record ErrorResponse(String code, String message) {
|
|
||||||
}
|
|
||||||
@@ -15,19 +15,21 @@ import org.springframework.stereotype.Service;
|
|||||||
public class LoginService {
|
public class LoginService {
|
||||||
private final MinimalUserRepository minimalUserRepository;
|
private final MinimalUserRepository minimalUserRepository;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final TokenGenerator tokenGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticates a user based on email and raw password.
|
* Authenticates a user and returns a token.
|
||||||
*
|
*
|
||||||
* @return The authenticated User model
|
* @return The generated token
|
||||||
*/
|
*/
|
||||||
public User login(User userToLogin) {
|
public String login(User userToLogin) {
|
||||||
var foundUser = minimalUserRepository.findByEmail(userToLogin.getEmail()).orElseThrow(() -> new UserNotFoundException("User not found with given email"));
|
var foundUser = minimalUserRepository.findByEmail(userToLogin.getEmail())
|
||||||
|
.orElseThrow(() -> new UserNotFoundException("User not found with given email"));
|
||||||
var loginSuccessful = passwordEncoder.matches(userToLogin.getPassword(), foundUser.getPassword());
|
var loginSuccessful = passwordEncoder.matches(userToLogin.getPassword(), foundUser.getPassword());
|
||||||
if (!loginSuccessful) {
|
if (!loginSuccessful) {
|
||||||
throw new InvalidCredentialsException("Invalid credentials");
|
throw new InvalidCredentialsException("Invalid credentials");
|
||||||
}
|
}
|
||||||
minimalUserRepository.updateLoginDate(foundUser.getId());
|
minimalUserRepository.updateLoginDate(foundUser.getId());
|
||||||
return foundUser;
|
return tokenGenerator.generateToken(String.valueOf(foundUser.getId()), foundUser.getEmail());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package auth_service.domain.login.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Port interface for token generation.
|
||||||
|
* Decouples the login service from specific token implementations (e.g. JWT).
|
||||||
|
*/
|
||||||
|
public interface TokenGenerator {
|
||||||
|
/**
|
||||||
|
* Generates a token for the given user.
|
||||||
|
*
|
||||||
|
* @param userId The unique identifier of the user.
|
||||||
|
* @param email The email address of the user.
|
||||||
|
* @return The generated token string.
|
||||||
|
*/
|
||||||
|
String generateToken(String userId, String email);
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package auth_service.domain.registration.controller;
|
package auth_service.domain.registration.controller;
|
||||||
|
|
||||||
import auth_service.domain.registration.controller.dto.response.ErrorResponse;
|
import auth_service.shared.controller.dto.response.ErrorResponse;
|
||||||
import auth_service.domain.registration.exception.EmailAlreadyInUseException;
|
import auth_service.domain.registration.exception.EmailAlreadyInUseException;
|
||||||
import auth_service.domain.registration.exception.InvalidInvitationCodeException;
|
import auth_service.domain.registration.exception.InvalidInvitationCodeException;
|
||||||
import auth_service.domain.registration.exception.SteamIdAlreadyRegisteredException;
|
import auth_service.domain.registration.exception.SteamIdAlreadyRegisteredException;
|
||||||
|
|||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
package auth_service.domain.registration.controller.dto.response;
|
|
||||||
|
|
||||||
public record ErrorResponse(String code, String message) {
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
package auth_service.domain.registration.service;
|
package auth_service.domain.registration.service;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CodeValidationService {
|
public class CodeValidationService {
|
||||||
|
|
||||||
private static final String RANDOM_INVITATION_CODE = "test-invite-code";
|
@Value("${auth.registration.invitation-code}")
|
||||||
|
private String invitationCode;
|
||||||
|
|
||||||
public boolean isCodeValid(String code) {
|
public boolean isCodeValid(String code) {
|
||||||
if (code == null || code.isEmpty()) {
|
if (code == null || code.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return code.equals(RANDOM_INVITATION_CODE);
|
return code.equals(invitationCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package auth_service.domain.user.controller;
|
package auth_service.domain.user.controller;
|
||||||
|
|
||||||
import auth_service.domain.user.controller.dto.response.ErrorResponse;
|
import auth_service.shared.controller.dto.response.ErrorResponse;
|
||||||
import auth_service.domain.user.exception.UserNotFoundException;
|
import auth_service.domain.user.exception.UserNotFoundException;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
package auth_service.domain.user.controller.dto.response;
|
|
||||||
|
|
||||||
public record ErrorResponse(String code, String message) {
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package auth_service.shared.controller.dto.response;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared error response record for all domains.
|
||||||
|
*
|
||||||
|
* @param code A machine-readable error code.
|
||||||
|
* @param message A human-readable error message.
|
||||||
|
*/
|
||||||
|
public record ErrorResponse(@NonNull String code, @NonNull String message) {
|
||||||
|
}
|
||||||
@@ -50,4 +50,9 @@ security:
|
|||||||
app:
|
app:
|
||||||
cors:
|
cors:
|
||||||
# Production default: strictly allow only the frontend domain
|
# Production default: strictly allow only the frontend domain
|
||||||
allowed-origins: "https://tbp.andreas-dahm.eu"
|
allowed-origins: "https://tbp.andreas-dahm.eu"
|
||||||
|
|
||||||
|
# Registration settings
|
||||||
|
auth:
|
||||||
|
registration:
|
||||||
|
invitation-code: ${AUTH_INVITATION_CODE}
|
||||||
@@ -1,25 +1,9 @@
|
|||||||
package auth_service;
|
package auth_service;
|
||||||
|
|
||||||
|
import auth_service.shared.test.AbstractIntegrationTest;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
|
||||||
import org.testcontainers.containers.PostgreSQLContainer;
|
|
||||||
import org.testcontainers.junit.jupiter.Container;
|
|
||||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
|
||||||
|
|
||||||
@SpringBootTest
|
class AuthServiceApplicationTests extends AbstractIntegrationTest {
|
||||||
@Testcontainers
|
|
||||||
class AuthServiceApplicationTests {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the PostgreSQL container.
|
|
||||||
*
|
|
||||||
* @ServiceConnection automatically configures the application properties
|
|
||||||
* (URL, username, password) for this specific container.
|
|
||||||
*/
|
|
||||||
@Container
|
|
||||||
@ServiceConnection
|
|
||||||
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17.9");
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void contextLoads() {
|
void contextLoads() {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package auth_service.architecture;
|
package auth_service.architecture;
|
||||||
|
|
||||||
|
import auth_service.shared.test.AbstractArchitectureTest;
|
||||||
import com.tngtech.archunit.core.importer.ImportOption;
|
import com.tngtech.archunit.core.importer.ImportOption;
|
||||||
import com.tngtech.archunit.junit.AnalyzeClasses;
|
import com.tngtech.archunit.junit.AnalyzeClasses;
|
||||||
import com.tngtech.archunit.junit.ArchTest;
|
import com.tngtech.archunit.junit.ArchTest;
|
||||||
@@ -14,7 +15,7 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
|
|||||||
* Analyzes all classes in the 'auth_service' package, ignoring test classes.
|
* Analyzes all classes in the 'auth_service' package, ignoring test classes.
|
||||||
*/
|
*/
|
||||||
@AnalyzeClasses(packages = "auth_service", importOptions = ImportOption.DoNotIncludeTests.class)
|
@AnalyzeClasses(packages = "auth_service", importOptions = ImportOption.DoNotIncludeTests.class)
|
||||||
class ArchitectureTest {
|
class ArchitectureTest extends AbstractArchitectureTest {
|
||||||
|
|
||||||
// ===================================================================================
|
// ===================================================================================
|
||||||
// 1. DOMAIN INDEPENDENCE (Slices)
|
// 1. DOMAIN INDEPENDENCE (Slices)
|
||||||
@@ -75,6 +76,24 @@ class ArchitectureTest {
|
|||||||
.should().dependOnClassesThat().resideInAPackage("..persistence..")
|
.should().dependOnClassesThat().resideInAPackage("..persistence..")
|
||||||
.because("Controllers must delegate to Services and should not access Persistence directly.");
|
.because("Controllers must delegate to Services and should not access Persistence directly.");
|
||||||
|
|
||||||
|
@ArchTest
|
||||||
|
static final ArchRule persistence_should_not_depend_on_controllers = noClasses()
|
||||||
|
.that().resideInAPackage("..persistence..")
|
||||||
|
.should().dependOnClassesThat().resideInAPackage("..controller..")
|
||||||
|
.because("Persistence layer must be independent of the Web/Controller layer.");
|
||||||
|
|
||||||
|
@ArchTest
|
||||||
|
static final ArchRule exceptions_should_be_isolated = noClasses()
|
||||||
|
.that().resideInAPackage("..exception..")
|
||||||
|
.should().dependOnClassesThat().resideInAnyPackage("..controller..", "..persistence.entities..")
|
||||||
|
.because("Exceptions should be part of the domain core and independent of infrastructure details.");
|
||||||
|
|
||||||
|
@ArchTest
|
||||||
|
static final ArchRule configuration_should_not_be_accessed_by_domain = noClasses()
|
||||||
|
.that().resideInAnyPackage("..domain..")
|
||||||
|
.should().dependOnClassesThat().resideInAPackage("auth_service.configuration..")
|
||||||
|
.because("Domain logic should not depend on Spring configuration classes.");
|
||||||
|
|
||||||
@ArchTest
|
@ArchTest
|
||||||
static final ArchRule services_should_be_annotated = classes()
|
static final ArchRule services_should_be_annotated = classes()
|
||||||
.that().resideInAPackage("..service..")
|
.that().resideInAPackage("..service..")
|
||||||
@@ -82,6 +101,16 @@ class ArchitectureTest {
|
|||||||
.should().beAnnotatedWith(org.springframework.stereotype.Service.class)
|
.should().beAnnotatedWith(org.springframework.stereotype.Service.class)
|
||||||
.because("Classes named *Service should be registered as Spring @Service beans.");
|
.because("Classes named *Service should be registered as Spring @Service beans.");
|
||||||
|
|
||||||
|
// ===================================================================================
|
||||||
|
// 5. SHARED PACKAGE RULES
|
||||||
|
// ===================================================================================
|
||||||
|
|
||||||
|
@ArchTest
|
||||||
|
static final ArchRule shared_should_not_depend_on_domains = noClasses()
|
||||||
|
.that().resideInAPackage("auth_service.shared..")
|
||||||
|
.should().dependOnClassesThat().resideInAPackage("auth_service.domain..")
|
||||||
|
.because("Shared classes should be independent of specific domains.");
|
||||||
|
|
||||||
@ArchTest
|
@ArchTest
|
||||||
static final ArchRule controllers_should_be_annotated = classes()
|
static final ArchRule controllers_should_be_annotated = classes()
|
||||||
.that().resideInAPackage("..controller..")
|
.that().resideInAPackage("..controller..")
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package auth_service.configuration.security;
|
package auth_service.configuration.security;
|
||||||
|
|
||||||
|
import auth_service.shared.test.AbstractUnitTest;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -12,8 +11,7 @@ import static org.mockito.Mockito.when;
|
|||||||
/**
|
/**
|
||||||
* Tests for JwtService.
|
* Tests for JwtService.
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MockitoExtension.class)
|
class JwtServiceTest extends AbstractUnitTest {
|
||||||
class JwtServiceTest {
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private JwtConfig jwtConfig;
|
private JwtConfig jwtConfig;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package auth_service.domain.login.controller;
|
package auth_service.domain.login.controller;
|
||||||
|
|
||||||
import auth_service.configuration.security.JwtAuthenticationFilter;
|
import auth_service.configuration.security.JwtAuthenticationFilter;
|
||||||
import auth_service.configuration.security.JwtService;
|
|
||||||
import auth_service.domain.login.controller.dto.request.LoginRequest;
|
import auth_service.domain.login.controller.dto.request.LoginRequest;
|
||||||
import auth_service.domain.login.exception.InvalidCredentialsException;
|
import auth_service.domain.login.exception.InvalidCredentialsException;
|
||||||
import auth_service.domain.login.model.User;
|
import auth_service.domain.login.model.User;
|
||||||
import auth_service.domain.login.service.LoginService;
|
import auth_service.domain.login.service.LoginService;
|
||||||
|
import auth_service.shared.test.AbstractWebMvcTest;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
|
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
|
||||||
@@ -28,7 +28,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
@WebMvcTest(LoginController.class)
|
@WebMvcTest(LoginController.class)
|
||||||
@AutoConfigureMockMvc(addFilters = false) // Skip security filters for simple controller test
|
@AutoConfigureMockMvc(addFilters = false) // Skip security filters for simple controller test
|
||||||
@AutoConfigureJsonTesters
|
@AutoConfigureJsonTesters
|
||||||
class LoginControllerTest {
|
class LoginControllerTest extends AbstractWebMvcTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
@@ -36,9 +36,6 @@ class LoginControllerTest {
|
|||||||
@MockitoBean
|
@MockitoBean
|
||||||
private LoginService loginService;
|
private LoginService loginService;
|
||||||
|
|
||||||
@MockitoBean
|
|
||||||
private JwtService jwtService;
|
|
||||||
|
|
||||||
@MockitoBean
|
@MockitoBean
|
||||||
private JwtAuthenticationFilter jwtAuthenticationFilter; // Needed because it's used in SecurityConfig
|
private JwtAuthenticationFilter jwtAuthenticationFilter; // Needed because it's used in SecurityConfig
|
||||||
|
|
||||||
@@ -49,9 +46,7 @@ class LoginControllerTest {
|
|||||||
void login_Success() throws Exception {
|
void login_Success() throws Exception {
|
||||||
// Arrange
|
// Arrange
|
||||||
var request = new LoginRequest("test@example.com", "password");
|
var request = new LoginRequest("test@example.com", "password");
|
||||||
var authenticatedUser = User.builder().id(815L).email("test@example.com").build();
|
when(loginService.login(any(User.class))).thenReturn("mock-token");
|
||||||
when(loginService.login(any(User.class))).thenReturn(authenticatedUser);
|
|
||||||
when(jwtService.generateToken("815", "test@example.com")).thenReturn("mock-token");
|
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
mockMvc.perform(post("/api/auth/login")
|
mockMvc.perform(post("/api/auth/login")
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package auth_service.domain.login.persistence;
|
||||||
|
|
||||||
|
import auth_service.domain.login.persistence.entities.MinimalUserEntity;
|
||||||
|
import auth_service.shared.test.AbstractPersistenceTest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.jpa.test.autoconfigure.TestEntityManager;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@Import(MinimalUserRepositoryImpl.class)
|
||||||
|
class MinimalUserRepositoryTest extends AbstractPersistenceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MinimalUserRepository minimalUserRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestEntityManager entityManager;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findByEmail_Success() {
|
||||||
|
// Arrange
|
||||||
|
var email = "minimal@example.com";
|
||||||
|
var entity = MinimalUserEntity.builder()
|
||||||
|
.email(email)
|
||||||
|
.password("password")
|
||||||
|
.enabled(true)
|
||||||
|
.build();
|
||||||
|
entityManager.persist(entity);
|
||||||
|
entityManager.flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = minimalUserRepository.findByEmail(email);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(result.isPresent());
|
||||||
|
assertEquals(email, result.get().getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateLoginDate_Success() {
|
||||||
|
// Arrange
|
||||||
|
var entity = MinimalUserEntity.builder()
|
||||||
|
.email("login@example.com")
|
||||||
|
.password("password")
|
||||||
|
.enabled(true)
|
||||||
|
.build();
|
||||||
|
entity = entityManager.persist(entity);
|
||||||
|
entityManager.flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
minimalUserRepository.updateLoginDate(entity.getId());
|
||||||
|
entityManager.clear(); // Clear cache to force reload from DB
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var updated = entityManager.find(MinimalUserEntity.class, entity.getId());
|
||||||
|
assertNotNull(updated.getLastLoginDate());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,11 @@ import auth_service.domain.login.exception.InvalidCredentialsException;
|
|||||||
import auth_service.domain.login.exception.UserNotFoundException;
|
import auth_service.domain.login.exception.UserNotFoundException;
|
||||||
import auth_service.domain.login.model.User;
|
import auth_service.domain.login.model.User;
|
||||||
import auth_service.domain.login.persistence.MinimalUserRepository;
|
import auth_service.domain.login.persistence.MinimalUserRepository;
|
||||||
|
import auth_service.shared.test.AbstractUnitTest;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -21,8 +20,7 @@ import static org.mockito.Mockito.when;
|
|||||||
/**
|
/**
|
||||||
* Tests for LoginService.
|
* Tests for LoginService.
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MockitoExtension.class)
|
class LoginServiceTest extends AbstractUnitTest {
|
||||||
class LoginServiceTest {
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private MinimalUserRepository minimalUserRepository;
|
private MinimalUserRepository minimalUserRepository;
|
||||||
@@ -30,6 +28,9 @@ class LoginServiceTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private PasswordEncoder passwordEncoder;
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private TokenGenerator tokenGenerator;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private LoginService loginService;
|
private LoginService loginService;
|
||||||
|
|
||||||
@@ -47,17 +48,19 @@ class LoginServiceTest {
|
|||||||
void login_Success() {
|
void login_Success() {
|
||||||
// Arrange
|
// Arrange
|
||||||
var dbUser = User.builder()
|
var dbUser = User.builder()
|
||||||
|
.id(815L)
|
||||||
.email("test@example.com")
|
.email("test@example.com")
|
||||||
.password("encodedPassword")
|
.password("encodedPassword")
|
||||||
.build();
|
.build();
|
||||||
when(minimalUserRepository.findByEmail(user.getEmail())).thenReturn(Optional.of(dbUser));
|
when(minimalUserRepository.findByEmail(user.getEmail())).thenReturn(Optional.of(dbUser));
|
||||||
when(passwordEncoder.matches(user.getPassword(), dbUser.getPassword())).thenReturn(true);
|
when(passwordEncoder.matches(user.getPassword(), dbUser.getPassword())).thenReturn(true);
|
||||||
|
when(tokenGenerator.generateToken("815", "test@example.com")).thenReturn("mock-token");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = loginService.login(user);
|
var result = loginService.login(user);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertEquals(dbUser, result);
|
assertEquals("mock-token", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+2
-1
@@ -6,6 +6,7 @@ import auth_service.domain.registration.controller.dto.request.RegistrationReque
|
|||||||
import auth_service.domain.registration.exception.EmailAlreadyInUseException;
|
import auth_service.domain.registration.exception.EmailAlreadyInUseException;
|
||||||
import auth_service.domain.registration.model.Registration;
|
import auth_service.domain.registration.model.Registration;
|
||||||
import auth_service.domain.registration.service.RegistrationService;
|
import auth_service.domain.registration.service.RegistrationService;
|
||||||
|
import auth_service.shared.test.AbstractWebMvcTest;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
|
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
|
||||||
@@ -28,7 +29,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
@WebMvcTest(RegistrationController.class)
|
@WebMvcTest(RegistrationController.class)
|
||||||
@AutoConfigureMockMvc(addFilters = false) // Skip security filters for simple controller test
|
@AutoConfigureMockMvc(addFilters = false) // Skip security filters for simple controller test
|
||||||
@AutoConfigureJsonTesters
|
@AutoConfigureJsonTesters
|
||||||
class RegistrationControllerTest {
|
class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
|
|||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
package auth_service.domain.registration.persistence;
|
||||||
|
|
||||||
|
import auth_service.domain.registration.model.Registration;
|
||||||
|
import auth_service.domain.registration.persistence.entities.RegisteredUserEntity;
|
||||||
|
import auth_service.shared.test.AbstractPersistenceTest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.jpa.test.autoconfigure.TestEntityManager;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@Import(RegisteredUserRepositoryImpl.class)
|
||||||
|
class RegisteredUserRepositoryTest extends AbstractPersistenceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RegisteredUserRepository registeredUserRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestEntityManager entityManager;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void existsByEmail_Success() {
|
||||||
|
// Arrange
|
||||||
|
var email = "exists@example.com";
|
||||||
|
var entity = RegisteredUserEntity.builder()
|
||||||
|
.email(email)
|
||||||
|
.password("password")
|
||||||
|
.enabled(true)
|
||||||
|
.steamId(111L)
|
||||||
|
.registrationDate(java.time.LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
entityManager.persist(entity);
|
||||||
|
entityManager.flush();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertTrue(registeredUserRepository.existsByEmail(email));
|
||||||
|
assertFalse(registeredUserRepository.existsByEmail("other@example.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void existsBySteamId_Success() {
|
||||||
|
// Arrange
|
||||||
|
var steamId = 222L;
|
||||||
|
var entity = RegisteredUserEntity.builder()
|
||||||
|
.email("steam@example.com")
|
||||||
|
.password("password")
|
||||||
|
.enabled(true)
|
||||||
|
.steamId(steamId)
|
||||||
|
.registrationDate(java.time.LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
entityManager.persist(entity);
|
||||||
|
entityManager.flush();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertTrue(registeredUserRepository.existsBySteamId(steamId));
|
||||||
|
assertFalse(registeredUserRepository.existsBySteamId(333L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void save_Success() {
|
||||||
|
// Arrange
|
||||||
|
var registration = Registration.builder()
|
||||||
|
.email("save@example.com")
|
||||||
|
.encryptedPassword("hashed")
|
||||||
|
.steamId(444L)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
long id = registeredUserRepository.save(registration);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(id > 0);
|
||||||
|
var saved = entityManager.find(RegisteredUserEntity.class, id);
|
||||||
|
assertNotNull(saved);
|
||||||
|
assertEquals("save@example.com", saved.getEmail());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,12 +5,11 @@ import auth_service.domain.registration.exception.InvalidInvitationCodeException
|
|||||||
import auth_service.domain.registration.exception.SteamIdAlreadyRegisteredException;
|
import auth_service.domain.registration.exception.SteamIdAlreadyRegisteredException;
|
||||||
import auth_service.domain.registration.model.Registration;
|
import auth_service.domain.registration.model.Registration;
|
||||||
import auth_service.domain.registration.persistence.RegisteredUserRepository;
|
import auth_service.domain.registration.persistence.RegisteredUserRepository;
|
||||||
|
import auth_service.shared.test.AbstractUnitTest;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@@ -21,8 +20,7 @@ import static org.mockito.Mockito.*;
|
|||||||
/**
|
/**
|
||||||
* Tests for RegistrationService.
|
* Tests for RegistrationService.
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MockitoExtension.class)
|
class RegistrationServiceTest extends AbstractUnitTest {
|
||||||
class RegistrationServiceTest {
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private CodeValidationService codeValidationService;
|
private CodeValidationService codeValidationService;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import auth_service.domain.user.exception.UserNotFoundException;
|
|||||||
import auth_service.domain.user.model.User;
|
import auth_service.domain.user.model.User;
|
||||||
import auth_service.domain.user.model.UserSettings;
|
import auth_service.domain.user.model.UserSettings;
|
||||||
import auth_service.domain.user.service.UserService;
|
import auth_service.domain.user.service.UserService;
|
||||||
|
import auth_service.shared.test.AbstractWebMvcTest;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -34,7 +35,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
@WebMvcTest(UserController.class)
|
@WebMvcTest(UserController.class)
|
||||||
@AutoConfigureMockMvc(addFilters = false)
|
@AutoConfigureMockMvc(addFilters = false)
|
||||||
@AutoConfigureJsonTesters
|
@AutoConfigureJsonTesters
|
||||||
class UserControllerTest {
|
class UserControllerTest extends AbstractWebMvcTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package auth_service.domain.user.persistence;
|
||||||
|
|
||||||
|
import auth_service.domain.user.model.UserSettings;
|
||||||
|
import auth_service.domain.user.persistence.entities.UserEntity;
|
||||||
|
import auth_service.shared.test.AbstractPersistenceTest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.jpa.test.autoconfigure.TestEntityManager;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@Import(UserRepositoryImpl.class)
|
||||||
|
class UserRepositoryTest extends AbstractPersistenceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestEntityManager entityManager;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findByEmail_Success() {
|
||||||
|
// Arrange
|
||||||
|
var email = "test@example.com";
|
||||||
|
var userEntity = UserEntity.builder()
|
||||||
|
.email(email)
|
||||||
|
.password("password")
|
||||||
|
.enabled(true)
|
||||||
|
.steamId(123456L)
|
||||||
|
.registrationDate(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
entityManager.persist(userEntity);
|
||||||
|
entityManager.flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = userRepository.findByEmail(email);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(result.isPresent());
|
||||||
|
assertEquals(email, result.get().getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateSettings_Success() {
|
||||||
|
// Arrange
|
||||||
|
var email = "settings@example.com";
|
||||||
|
var userEntity = UserEntity.builder()
|
||||||
|
.email(email)
|
||||||
|
.password("password")
|
||||||
|
.enabled(true)
|
||||||
|
.steamId(123456L)
|
||||||
|
.registrationDate(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
userEntity = entityManager.persist(userEntity);
|
||||||
|
entityManager.flush();
|
||||||
|
|
||||||
|
var settings = UserSettings.builder()
|
||||||
|
.language("de")
|
||||||
|
.theme("dark")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var updated = userRepository.updateSettings(userEntity.getId(), 654321L, settings);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(updated);
|
||||||
|
var updatedUser = entityManager.find(UserEntity.class, userEntity.getId());
|
||||||
|
assertEquals(654321L, updatedUser.getSteamId());
|
||||||
|
assertNotNull(updatedUser.getSettings());
|
||||||
|
assertEquals("de", updatedUser.getSettings().getLanguage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,10 @@ import auth_service.domain.user.exception.UserNotFoundException;
|
|||||||
import auth_service.domain.user.model.User;
|
import auth_service.domain.user.model.User;
|
||||||
import auth_service.domain.user.model.UserSettings;
|
import auth_service.domain.user.model.UserSettings;
|
||||||
import auth_service.domain.user.persistence.UserRepository;
|
import auth_service.domain.user.persistence.UserRepository;
|
||||||
|
import auth_service.shared.test.AbstractUnitTest;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -19,8 +18,7 @@ import static org.mockito.Mockito.*;
|
|||||||
/**
|
/**
|
||||||
* Tests for UserService.
|
* Tests for UserService.
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MockitoExtension.class)
|
class UserServiceTest extends AbstractUnitTest {
|
||||||
class UserServiceTest {
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private UserRepository userRepository;
|
private UserRepository userRepository;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package auth_service.shared.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for ArchUnit tests.
|
||||||
|
*/
|
||||||
|
@Tag("ArchitectureTest")
|
||||||
|
public abstract class AbstractArchitectureTest {
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package auth_service.shared.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for integration tests.
|
||||||
|
* Full Spring context with Testcontainers.
|
||||||
|
*/
|
||||||
|
@Tag("IntegrationTest")
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@Testcontainers
|
||||||
|
public abstract class AbstractIntegrationTest {
|
||||||
|
|
||||||
|
@Container
|
||||||
|
@ServiceConnection
|
||||||
|
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17-alpine");
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package auth_service.shared.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
|
||||||
|
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
|
||||||
|
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for persistence tests.
|
||||||
|
* Slices only the persistence layer with Testcontainers.
|
||||||
|
*/
|
||||||
|
@Tag("PersistenceTest")
|
||||||
|
@DataJpaTest
|
||||||
|
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
||||||
|
@Testcontainers
|
||||||
|
public abstract class AbstractPersistenceTest {
|
||||||
|
|
||||||
|
@Container
|
||||||
|
@ServiceConnection
|
||||||
|
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17-alpine");
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package auth_service.shared.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for unit tests.
|
||||||
|
* Isolated from Spring context.
|
||||||
|
*/
|
||||||
|
@Tag("UnitTest")
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
public abstract class AbstractUnitTest {
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package auth_service.shared.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for WebMvc tests.
|
||||||
|
* Slices the web layer.
|
||||||
|
*/
|
||||||
|
@Tag("WebMvcTest")
|
||||||
|
@WebMvcTest
|
||||||
|
public abstract class AbstractWebMvcTest {
|
||||||
|
}
|
||||||
Reference in new issue
Block a user