feat: initialisation du projet Olhar-API en Clean Architecture

- Spring Boot 3.4.1 + Gradle Kotlin DSL, Java 21
- Clean Architecture (domain / application / infrastructure / interfaces)
- Spring Security stateless avec JWT (JJWT 0.12.6)
- Flyway + PostgreSQL (migration V1 table users)
- SpringDoc OpenAPI / Swagger UI avec auth Bearer
- Testcontainers pour les tests d'intégration
- Use cases Register et Authenticate (endpoints POST /api/v1/auth/register et /login)
- GlobalExceptionHandler avec ProblemDetail (RFC 9457)
- docker-compose.yml pour Postgres local

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 05:56:46 +02:00
commit c230a999ab
36 changed files with 1131 additions and 0 deletions
@@ -0,0 +1,12 @@
package com.olhar.olharapi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OlharApiApplication {
public static void main(String[] args) {
SpringApplication.run(OlharApiApplication.class, args);
}
}
@@ -0,0 +1,7 @@
package com.olhar.olharapi.application.port.in;
public interface AuthenticateUserUseCase {
String authenticate(Command command);
record Command(String email, String password) {}
}
@@ -0,0 +1,9 @@
package com.olhar.olharapi.application.port.in;
import com.olhar.olharapi.domain.model.User;
public interface RegisterUserUseCase {
User register(Command command);
record Command(String email, String password) {}
}
@@ -0,0 +1,32 @@
package com.olhar.olharapi.application.usecase;
import com.olhar.olharapi.application.port.in.AuthenticateUserUseCase;
import com.olhar.olharapi.application.port.out.UserRepository;
import com.olhar.olharapi.domain.exception.UserNotFoundException;
import com.olhar.olharapi.domain.model.User;
import com.olhar.olharapi.infrastructure.security.JwtService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AuthenticateUserService implements AuthenticateUserUseCase {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
@Override
public String authenticate(Command command) {
User user = userRepository.findByEmail(command.email())
.orElseThrow(() -> new UserNotFoundException(command.email()));
if (!passwordEncoder.matches(command.password(), user.passwordHash())) {
throw new BadCredentialsException("Invalid credentials");
}
return jwtService.generateToken(user);
}
}
@@ -0,0 +1,39 @@
package com.olhar.olharapi.application.usecase;
import com.olhar.olharapi.application.port.in.RegisterUserUseCase;
import com.olhar.olharapi.application.port.out.UserRepository;
import com.olhar.olharapi.domain.exception.EmailAlreadyUsedException;
import com.olhar.olharapi.domain.model.User;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class RegisterUserService implements RegisterUserUseCase {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Override
@Transactional
public User register(Command command) {
if (userRepository.existsByEmail(command.email())) {
throw new EmailAlreadyUsedException(command.email());
}
User user = new User(
UUID.randomUUID(),
command.email(),
passwordEncoder.encode(command.password()),
User.Role.USER,
Instant.now()
);
return userRepository.save(user);
}
}
@@ -0,0 +1,7 @@
package com.olhar.olharapi.domain.exception;
public class EmailAlreadyUsedException extends RuntimeException {
public EmailAlreadyUsedException(String email) {
super("Email already in use: " + email);
}
}
@@ -0,0 +1,7 @@
package com.olhar.olharapi.domain.exception;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String email) {
super("User not found: " + email);
}
}
@@ -0,0 +1,16 @@
package com.olhar.olharapi.domain.model;
import java.time.Instant;
import java.util.UUID;
public record User(
UUID id,
String email,
String passwordHash,
Role role,
Instant createdAt
) {
public enum Role {
USER, ADMIN
}
}
@@ -0,0 +1,31 @@
package com.olhar.olharapi.infrastructure.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiConfig {
private static final String BEARER_SCHEME = "bearerAuth";
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info()
.title("Olhar API")
.description("API REST — Olhar")
.version("v1"))
.addSecurityItem(new SecurityRequirement().addList(BEARER_SCHEME))
.components(new Components()
.addSecuritySchemes(BEARER_SCHEME, new SecurityScheme()
.name(BEARER_SCHEME)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
@@ -0,0 +1,48 @@
package com.olhar.olharapi.infrastructure.config;
import com.olhar.olharapi.infrastructure.security.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/api/v1/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/api-docs/**",
"/v3/api-docs/**"
).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@@ -0,0 +1,37 @@
package com.olhar.olharapi.infrastructure.persistence.entity;
import jakarta.persistence.*;
import lombok.*;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserEntity {
@Id
private UUID id;
@Column(nullable = false, unique = true)
private String email;
@Column(name = "password_hash", nullable = false)
private String passwordHash;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private RoleEnum role;
@Column(name = "created_at", nullable = false)
private Instant createdAt;
public enum RoleEnum {
USER, ADMIN
}
}
@@ -0,0 +1,29 @@
package com.olhar.olharapi.infrastructure.persistence.mapper;
import com.olhar.olharapi.domain.model.User;
import com.olhar.olharapi.infrastructure.persistence.entity.UserEntity;
import org.springframework.stereotype.Component;
@Component
public class UserPersistenceMapper {
public User toDomain(UserEntity entity) {
return new User(
entity.getId(),
entity.getEmail(),
entity.getPasswordHash(),
User.Role.valueOf(entity.getRole().name()),
entity.getCreatedAt()
);
}
public UserEntity toEntity(User user) {
return UserEntity.builder()
.id(user.id())
.email(user.email())
.passwordHash(user.passwordHash())
.role(UserEntity.RoleEnum.valueOf(user.role().name()))
.createdAt(user.createdAt())
.build();
}
}
@@ -0,0 +1,12 @@
package com.olhar.olharapi.infrastructure.persistence.repository;
import com.olhar.olharapi.infrastructure.persistence.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface JpaUserRepository extends JpaRepository<UserEntity, UUID> {
Optional<UserEntity> findByEmail(String email);
boolean existsByEmail(String email);
}
@@ -0,0 +1,38 @@
package com.olhar.olharapi.infrastructure.persistence.repository;
import com.olhar.olharapi.application.port.out.UserRepository;
import com.olhar.olharapi.domain.model.User;
import com.olhar.olharapi.infrastructure.persistence.mapper.UserPersistenceMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Optional;
import java.util.UUID;
@Component
@RequiredArgsConstructor
public class UserRepositoryAdapter implements UserRepository {
private final JpaUserRepository jpaUserRepository;
private final UserPersistenceMapper mapper;
@Override
public User save(User user) {
return mapper.toDomain(jpaUserRepository.save(mapper.toEntity(user)));
}
@Override
public Optional<User> findByEmail(String email) {
return jpaUserRepository.findByEmail(email).map(mapper::toDomain);
}
@Override
public Optional<User> findById(UUID id) {
return jpaUserRepository.findById(id).map(mapper::toDomain);
}
@Override
public boolean existsByEmail(String email) {
return jpaUserRepository.existsByEmail(email);
}
}
@@ -0,0 +1,57 @@
package com.olhar.olharapi.infrastructure.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.List;
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String token = authHeader.substring(7);
if (!jwtService.isTokenValid(token)) {
filterChain.doFilter(request, response);
return;
}
String email = jwtService.extractEmail(token);
String role = jwtService.extractClaims(token).get("role", String.class);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
email,
null,
List.of(new SimpleGrantedAuthority("ROLE_" + role))
);
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(auth);
filterChain.doFilter(request, response);
}
}
@@ -0,0 +1,59 @@
package com.olhar.olharapi.infrastructure.security;
import com.olhar.olharapi.domain.model.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Service
public class JwtService {
private final SecretKey secretKey;
private final long expirationMs;
public JwtService(
@Value("${security.jwt.secret}") String secret,
@Value("${security.jwt.expiration-ms}") long expirationMs
) {
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
this.expirationMs = expirationMs;
}
public String generateToken(User user) {
Date now = new Date();
return Jwts.builder()
.subject(user.email())
.claim("role", user.role().name())
.claim("userId", user.id().toString())
.issuedAt(now)
.expiration(new Date(now.getTime() + expirationMs))
.signWith(secretKey)
.compact();
}
public Claims extractClaims(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();
}
public String extractEmail(String token) {
return extractClaims(token).getSubject();
}
public boolean isTokenValid(String token) {
try {
return extractClaims(token).getExpiration().after(new Date());
} catch (Exception e) {
return false;
}
}
}
@@ -0,0 +1,42 @@
package com.olhar.olharapi.interfaces.exception;
import com.olhar.olharapi.domain.exception.EmailAlreadyUsedException;
import com.olhar.olharapi.domain.exception.UserNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Map;
import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EmailAlreadyUsedException.class)
public ProblemDetail handleEmailAlreadyUsed(EmailAlreadyUsedException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, ex.getMessage());
}
@ExceptionHandler(UserNotFoundException.class)
public ProblemDetail handleUserNotFound(UserNotFoundException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
}
@ExceptionHandler(BadCredentialsException.class)
public ProblemDetail handleBadCredentials(BadCredentialsException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
Map<String, String> errors = ex.getBindingResult().getFieldErrors().stream()
.collect(Collectors.toMap(FieldError::getField, f -> f.getDefaultMessage() != null ? f.getDefaultMessage() : "Invalid value"));
ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Validation failed");
problem.setProperty("errors", errors);
return problem;
}
}
@@ -0,0 +1,46 @@
package com.olhar.olharapi.interfaces.rest.controller;
import com.olhar.olharapi.application.port.in.AuthenticateUserUseCase;
import com.olhar.olharapi.application.port.in.RegisterUserUseCase;
import com.olhar.olharapi.domain.model.User;
import com.olhar.olharapi.interfaces.rest.dto.request.LoginRequest;
import com.olhar.olharapi.interfaces.rest.dto.request.RegisterRequest;
import com.olhar.olharapi.interfaces.rest.dto.response.AuthResponse;
import com.olhar.olharapi.interfaces.rest.dto.response.UserResponse;
import com.olhar.olharapi.interfaces.rest.mapper.UserRestMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
@Tag(name = "Auth", description = "Inscription et authentification")
public class AuthController {
private final RegisterUserUseCase registerUserUseCase;
private final AuthenticateUserUseCase authenticateUserUseCase;
private final UserRestMapper userRestMapper;
@PostMapping("/register")
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "Créer un compte utilisateur")
public UserResponse register(@Valid @RequestBody RegisterRequest request) {
User user = registerUserUseCase.register(
new RegisterUserUseCase.Command(request.email(), request.password())
);
return userRestMapper.toResponse(user);
}
@PostMapping("/login")
@Operation(summary = "S'authentifier et obtenir un token JWT")
public AuthResponse login(@Valid @RequestBody LoginRequest request) {
String token = authenticateUserUseCase.authenticate(
new AuthenticateUserUseCase.Command(request.email(), request.password())
);
return new AuthResponse(token);
}
}
@@ -0,0 +1,9 @@
package com.olhar.olharapi.interfaces.rest.dto.request;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public record LoginRequest(
@Email @NotBlank String email,
@NotBlank String password
) {}
@@ -0,0 +1,10 @@
package com.olhar.olharapi.interfaces.rest.dto.request;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record RegisterRequest(
@Email @NotBlank String email,
@NotBlank @Size(min = 8) String password
) {}
@@ -0,0 +1,3 @@
package com.olhar.olharapi.interfaces.rest.dto.response;
public record AuthResponse(String token) {}
@@ -0,0 +1,6 @@
package com.olhar.olharapi.interfaces.rest.dto.response;
import java.time.Instant;
import java.util.UUID;
public record UserResponse(UUID id, String email, String role, Instant createdAt) {}
@@ -0,0 +1,18 @@
package com.olhar.olharapi.interfaces.rest.mapper;
import com.olhar.olharapi.domain.model.User;
import com.olhar.olharapi.interfaces.rest.dto.response.UserResponse;
import org.springframework.stereotype.Component;
@Component
public class UserRestMapper {
public UserResponse toResponse(User user) {
return new UserResponse(
user.id(),
user.email(),
user.role().name(),
user.createdAt()
);
}
}
+32
View File
@@ -0,0 +1,32 @@
spring:
datasource:
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:olhar}
username: ${DB_USER:olhar}
password: ${DB_PASSWORD:olhar}
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: validate
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
flyway:
enabled: true
locations: classpath:db/migration
server:
port: 8080
springdoc:
api-docs:
path: /api-docs
swagger-ui:
path: /swagger-ui.html
operationsSorter: method
security:
jwt:
secret: ${JWT_SECRET:changeme-in-production-use-a-long-random-string-at-least-256-bits}
expiration-ms: ${JWT_EXPIRATION_MS:86400000}
@@ -0,0 +1,7 @@
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'USER',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
@@ -0,0 +1,29 @@
package com.olhar.olharapi.infrastructure;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@Testcontainers
public abstract class AbstractIntegrationTest {
@Container
static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
.withDatabaseName("olhar_test")
.withUsername("olhar")
.withPassword("olhar");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.datasource.driver-class-name", () -> "org.postgresql.Driver");
}
}
@@ -0,0 +1,39 @@
package com.olhar.olharapi.infrastructure.security;
import com.olhar.olharapi.domain.model.User;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
class JwtServiceTest {
private JwtService jwtService;
@BeforeEach
void setUp() {
jwtService = new JwtService(
"test-secret-key-for-testing-only-must-be-long-enough-32-chars",
3600000L
);
}
@Test
void generateToken_shouldReturnValidToken() {
User user = new User(UUID.randomUUID(), "user@example.com", "hash", User.Role.USER, Instant.now());
String token = jwtService.generateToken(user);
assertThat(token).isNotBlank();
assertThat(jwtService.isTokenValid(token)).isTrue();
assertThat(jwtService.extractEmail(token)).isEqualTo("user@example.com");
}
@Test
void isTokenValid_shouldReturnFalse_forInvalidToken() {
assertThat(jwtService.isTokenValid("not.a.valid.token")).isFalse();
}
}
@@ -0,0 +1,61 @@
package com.olhar.olharapi.interfaces;
import com.olhar.olharapi.infrastructure.AbstractIntegrationTest;
import com.olhar.olharapi.interfaces.rest.dto.request.LoginRequest;
import com.olhar.olharapi.interfaces.rest.dto.request.RegisterRequest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
class AuthControllerIT extends AbstractIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void register_shouldCreateUser_whenEmailIsNew() {
RegisterRequest request = new RegisterRequest("test@example.com", "password123");
ResponseEntity<String> response = restTemplate.postForEntity("/api/v1/auth/register", request, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody()).contains("test@example.com");
}
@Test
void register_shouldReturnConflict_whenEmailAlreadyExists() {
RegisterRequest request = new RegisterRequest("duplicate@example.com", "password123");
restTemplate.postForEntity("/api/v1/auth/register", request, String.class);
ResponseEntity<String> response = restTemplate.postForEntity("/api/v1/auth/register", request, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
}
@Test
void login_shouldReturnToken_whenCredentialsAreValid() {
RegisterRequest registerRequest = new RegisterRequest("login@example.com", "password123");
restTemplate.postForEntity("/api/v1/auth/register", registerRequest, String.class);
LoginRequest loginRequest = new LoginRequest("login@example.com", "password123");
ResponseEntity<String> response = restTemplate.postForEntity("/api/v1/auth/login", loginRequest, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains("token");
}
@Test
void login_shouldReturnUnauthorized_whenPasswordIsWrong() {
RegisterRequest registerRequest = new RegisterRequest("wrongpass@example.com", "password123");
restTemplate.postForEntity("/api/v1/auth/register", registerRequest, String.class);
LoginRequest loginRequest = new LoginRequest("wrongpass@example.com", "wrongpassword");
ResponseEntity<String> response = restTemplate.postForEntity("/api/v1/auth/login", loginRequest, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
}
+14
View File
@@ -0,0 +1,14 @@
spring:
datasource:
url: jdbc:tc:postgresql:16:///olhar_test
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
jpa:
hibernate:
ddl-auto: validate
flyway:
enabled: true
security:
jwt:
secret: test-secret-key-for-testing-only-must-be-long-enough
expiration-ms: 3600000