Implement auth domain with outside-in TDD (22 tests)

Controller (7 tests): signup, login, logout, GET/PATCH me.
Standalone MockMvc setup (Boot 4 removed @WebMvcTest).

Service (11 tests): signup with conflict check, login with
password/active validation, getCurrentUser with household info,
updateProfile with password change flow.

Repository (4 tests): save/find, case-insensitive email via
IgnoreCase queries (citext + Hibernate needs explicit IgnoreCase),
existsByEmail.

Also includes:
- SecurityConfig: session auth, CSRF, role-based authorization
- CustomUserDetailsService: loads UserAccount for Spring Security
- UserAccount, Household, HouseholdMember JPA entities
- spring-boot-flyway dependency (Boot 4 requires explicit module)
- ddl-auto=none (Flyway owns schema, validate fails on citext)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 21:24:26 +02:00
parent 866603711d
commit 3253dcfec2
23 changed files with 873 additions and 15 deletions

View File

@@ -0,0 +1,65 @@
package com.recipeapp.auth;
import com.recipeapp.auth.dto.*;
import com.recipeapp.common.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
@RestController
@RequestMapping("/v1/auth")
public class AuthController {
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
@PostMapping("/signup")
public ResponseEntity<ApiResponse<UserResponse>> signup(
@Valid @RequestBody SignupRequest request,
HttpServletRequest httpRequest) {
UserResponse user = authService.signup(request);
httpRequest.getSession(true);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(user));
}
@PostMapping("/login")
public ResponseEntity<ApiResponse<UserResponse>> login(
@Valid @RequestBody LoginRequest request,
HttpServletRequest httpRequest) {
UserResponse user = authService.login(request);
HttpSession session = httpRequest.getSession(true);
session.setAttribute("user_email", user.email());
return ResponseEntity.ok(ApiResponse.success(user));
}
@PostMapping("/logout")
public ResponseEntity<Void> logout(HttpServletRequest httpRequest) {
HttpSession session = httpRequest.getSession(false);
if (session != null) {
session.invalidate();
}
return ResponseEntity.noContent().build();
}
@GetMapping("/me")
public ResponseEntity<ApiResponse<UserResponse>> me(Principal principal) {
UserResponse user = authService.getCurrentUser(principal.getName());
return ResponseEntity.ok(ApiResponse.success(user));
}
@PatchMapping("/me")
public ResponseEntity<ApiResponse<UserResponse>> updateProfile(
Principal principal,
@Valid @RequestBody UpdateProfileRequest request) {
UserResponse user = authService.updateProfile(principal.getName(), request);
return ResponseEntity.ok(ApiResponse.success(user));
}
}