From db7647f7f43d0060faaa0cbc71dfef38c6f516a5 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:12:50 +0200 Subject: [PATCH] refactor: remove Phase 1 auth endpoints, switch to Logto OIDC Auth is now handled by Logto. Removed AuthController, AuthService, and related DTOs. Integration tests use Spring Security JWT mocks. Ed25519 JwtService retained for machine token signing. Co-Authored-By: Claude Sonnet 4.6 --- .../cameleer/saas/auth/AuthController.java | 54 ------ .../cameleer/saas/auth/AuthService.java | 82 --------- .../cameleer/saas/auth/dto/AuthResponse.java | 8 - .../cameleer/saas/auth/dto/LoginRequest.java | 10 - .../saas/auth/dto/RegisterRequest.java | 12 -- .../cameleer/saas/config/SecurityConfig.java | 1 - .../saas/license/LicenseController.java | 10 +- .../saas/tenant/TenantController.java | 11 +- .../saas/auth/AuthControllerTest.java | 126 ------------- .../cameleer/saas/auth/AuthServiceTest.java | 171 ------------------ .../saas/license/LicenseControllerTest.java | 35 +--- .../saas/tenant/TenantControllerTest.java | 29 +-- 12 files changed, 34 insertions(+), 515 deletions(-) delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/AuthController.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/AuthService.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/dto/AuthResponse.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/dto/LoginRequest.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/dto/RegisterRequest.java delete mode 100644 src/test/java/net/siegeln/cameleer/saas/auth/AuthControllerTest.java delete mode 100644 src/test/java/net/siegeln/cameleer/saas/auth/AuthServiceTest.java diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/AuthController.java b/src/main/java/net/siegeln/cameleer/saas/auth/AuthController.java deleted file mode 100644 index 432daf1..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/AuthController.java +++ /dev/null @@ -1,54 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.validation.Valid; -import net.siegeln.cameleer.saas.auth.dto.AuthResponse; -import net.siegeln.cameleer.saas.auth.dto.LoginRequest; -import net.siegeln.cameleer.saas.auth.dto.RegisterRequest; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/auth") -public class AuthController { - - private final AuthService authService; - - public AuthController(AuthService authService) { - this.authService = authService; - } - - @PostMapping("/register") - public ResponseEntity register(@Valid @RequestBody RegisterRequest request, - HttpServletRequest httpRequest) { - try { - var response = authService.register(request, extractClientIp(httpRequest)); - return ResponseEntity.status(HttpStatus.CREATED).body(response); - } catch (IllegalArgumentException e) { - return ResponseEntity.status(HttpStatus.CONFLICT).build(); - } - } - - @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody LoginRequest request, - HttpServletRequest httpRequest) { - try { - var response = authService.login(request, extractClientIp(httpRequest)); - return ResponseEntity.ok(response); - } catch (IllegalArgumentException e) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } - } - - private String extractClientIp(HttpServletRequest request) { - String xForwardedFor = request.getHeader("X-Forwarded-For"); - if (xForwardedFor != null && !xForwardedFor.isEmpty()) { - return xForwardedFor.split(",")[0].trim(); - } - return request.getRemoteAddr(); - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/AuthService.java b/src/main/java/net/siegeln/cameleer/saas/auth/AuthService.java deleted file mode 100644 index 1df84b7..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/AuthService.java +++ /dev/null @@ -1,82 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import net.siegeln.cameleer.saas.audit.AuditAction; -import net.siegeln.cameleer.saas.audit.AuditService; -import net.siegeln.cameleer.saas.auth.dto.AuthResponse; -import net.siegeln.cameleer.saas.auth.dto.LoginRequest; -import net.siegeln.cameleer.saas.auth.dto.RegisterRequest; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; - -@Service -public class AuthService { - - private final UserRepository userRepository; - private final RoleRepository roleRepository; - private final PasswordEncoder passwordEncoder; - private final JwtService jwtService; - private final AuditService auditService; - - public AuthService(UserRepository userRepository, - RoleRepository roleRepository, - PasswordEncoder passwordEncoder, - JwtService jwtService, - AuditService auditService) { - this.userRepository = userRepository; - this.roleRepository = roleRepository; - this.passwordEncoder = passwordEncoder; - this.jwtService = jwtService; - this.auditService = auditService; - } - - public AuthResponse register(RegisterRequest request, String sourceIp) { - if (userRepository.existsByEmail(request.email())) { - throw new IllegalArgumentException("Email already registered"); - } - - var user = new UserEntity(); - user.setEmail(request.email()); - user.setName(request.name()); - user.setPassword(passwordEncoder.encode(request.password())); - - roleRepository.findByName("OWNER").ifPresent(role -> user.getRoles().add(role)); - - var saved = userRepository.save(user); - var token = jwtService.generateToken(saved); - - auditService.log( - saved.getId(), saved.getEmail(), null, - AuditAction.AUTH_REGISTER, null, - null, sourceIp, - "SUCCESS", null - ); - - return new AuthResponse(token, saved.getEmail(), saved.getName()); - } - - public AuthResponse login(LoginRequest request, String sourceIp) { - var user = userRepository.findByEmail(request.email()) - .orElseThrow(() -> new IllegalArgumentException("Invalid credentials")); - - if (!passwordEncoder.matches(request.password(), user.getPassword())) { - auditService.log( - user.getId(), user.getEmail(), null, - AuditAction.AUTH_LOGIN_FAILED, null, - null, sourceIp, - "FAILURE", null - ); - throw new IllegalArgumentException("Invalid credentials"); - } - - var token = jwtService.generateToken(user); - - auditService.log( - user.getId(), user.getEmail(), null, - AuditAction.AUTH_LOGIN, null, - null, sourceIp, - "SUCCESS", null - ); - - return new AuthResponse(token, user.getEmail(), user.getName()); - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/dto/AuthResponse.java b/src/main/java/net/siegeln/cameleer/saas/auth/dto/AuthResponse.java deleted file mode 100644 index ba53b4a..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/dto/AuthResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.siegeln.cameleer.saas.auth.dto; - -public record AuthResponse( - String token, - String email, - String name -) { -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/dto/LoginRequest.java b/src/main/java/net/siegeln/cameleer/saas/auth/dto/LoginRequest.java deleted file mode 100644 index 8602f8f..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/dto/LoginRequest.java +++ /dev/null @@ -1,10 +0,0 @@ -package net.siegeln.cameleer.saas.auth.dto; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; - -public record LoginRequest( - @NotBlank @Email String email, - @NotBlank String password -) { -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/dto/RegisterRequest.java b/src/main/java/net/siegeln/cameleer/saas/auth/dto/RegisterRequest.java deleted file mode 100644 index d9e7186..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/dto/RegisterRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.siegeln.cameleer.saas.auth.dto; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -public record RegisterRequest( - @NotBlank @Email String email, - @NotBlank String name, - @NotBlank @Size(min = 8, max = 128) String password -) { -} diff --git a/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java b/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java index 5b19438..17c5225 100644 --- a/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java +++ b/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java @@ -47,7 +47,6 @@ public class SecurityConfig { .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/actuator/health").permitAll() .requestMatchers("/auth/verify").permitAll() .anyRequest().authenticated() diff --git a/src/main/java/net/siegeln/cameleer/saas/license/LicenseController.java b/src/main/java/net/siegeln/cameleer/saas/license/LicenseController.java index a761d8f..e414f08 100644 --- a/src/main/java/net/siegeln/cameleer/saas/license/LicenseController.java +++ b/src/main/java/net/siegeln/cameleer/saas/license/LicenseController.java @@ -32,8 +32,14 @@ public class LicenseController { var tenant = tenantService.getById(tenantId).orElse(null); if (tenant == null) return ResponseEntity.notFound().build(); - UUID actorId = authentication.getCredentials() instanceof UUID uid - ? uid : UUID.fromString(authentication.getCredentials().toString()); + // Extract actor ID from JWT subject (Logto OIDC: sub may be a non-UUID string) + String sub = authentication.getName(); + UUID actorId; + try { + actorId = UUID.fromString(sub); + } catch (IllegalArgumentException e) { + actorId = UUID.nameUUIDFromBytes(sub.getBytes()); + } var license = licenseService.generateLicense(tenant, Duration.ofDays(365), actorId); return ResponseEntity.status(HttpStatus.CREATED).body(toResponse(license)); diff --git a/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java index ad06203..184b88e 100644 --- a/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java +++ b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java @@ -29,9 +29,14 @@ public class TenantController { public ResponseEntity create(@Valid @RequestBody CreateTenantRequest request, Authentication authentication) { try { - // Extract actor ID from authentication credentials (Phase 1: userId stored as credentials) - UUID actorId = authentication.getCredentials() instanceof UUID uid - ? uid : UUID.fromString(authentication.getCredentials().toString()); + // Extract actor ID from JWT subject (Logto OIDC: sub may be a non-UUID string) + String sub = authentication.getName(); + UUID actorId; + try { + actorId = UUID.fromString(sub); + } catch (IllegalArgumentException e) { + actorId = UUID.nameUUIDFromBytes(sub.getBytes()); + } var entity = tenantService.create(request, actorId); return ResponseEntity.status(HttpStatus.CREATED).body(toResponse(entity)); diff --git a/src/test/java/net/siegeln/cameleer/saas/auth/AuthControllerTest.java b/src/test/java/net/siegeln/cameleer/saas/auth/AuthControllerTest.java deleted file mode 100644 index d672c07..0000000 --- a/src/test/java/net/siegeln/cameleer/saas/auth/AuthControllerTest.java +++ /dev/null @@ -1,126 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import com.fasterxml.jackson.databind.ObjectMapper; -import net.siegeln.cameleer.saas.TestcontainersConfig; -import net.siegeln.cameleer.saas.TestSecurityConfig; -import net.siegeln.cameleer.saas.auth.dto.LoginRequest; -import net.siegeln.cameleer.saas.auth.dto.RegisterRequest; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest -@AutoConfigureMockMvc -@Import({TestcontainersConfig.class, TestSecurityConfig.class}) -@ActiveProfiles("test") -class AuthControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Test - void register_returns201WithToken() throws Exception { - var request = new RegisterRequest("newuser@example.com", "New User", "password123"); - - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.token").isNotEmpty()) - .andExpect(jsonPath("$.email").value("newuser@example.com")) - .andExpect(jsonPath("$.name").value("New User")); - } - - @Test - void register_returns409ForDuplicateEmail() throws Exception { - var request = new RegisterRequest("duplicate@example.com", "User One", "password123"); - - // First registration - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isCreated()); - - // Duplicate registration - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isConflict()); - } - - @Test - void login_returns200WithToken() throws Exception { - var registerRequest = new RegisterRequest("loginuser@example.com", "Login User", "password123"); - - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(registerRequest))) - .andExpect(status().isCreated()); - - var loginRequest = new LoginRequest("loginuser@example.com", "password123"); - - mockMvc.perform(post("/api/auth/login") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(loginRequest))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token").isNotEmpty()) - .andExpect(jsonPath("$.email").value("loginuser@example.com")); - } - - @Test - void login_returns401ForBadPassword() throws Exception { - var registerRequest = new RegisterRequest("badpass@example.com", "Bad Pass", "password123"); - - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(registerRequest))) - .andExpect(status().isCreated()); - - var loginRequest = new LoginRequest("badpass@example.com", "wrong-password"); - - mockMvc.perform(post("/api/auth/login") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(loginRequest))) - .andExpect(status().isUnauthorized()); - } - - @Test - void protectedEndpoint_returns401WithoutToken() throws Exception { - mockMvc.perform(get("/api/health/secured")) - .andExpect(status().isUnauthorized()); - } - - @Test - void protectedEndpoint_returns200WithValidToken() throws Exception { - // Register to get a token - var registerRequest = new RegisterRequest("secured@example.com", "Secured User", "password123"); - - var result = mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(registerRequest))) - .andExpect(status().isCreated()) - .andReturn(); - - var responseBody = objectMapper.readTree(result.getResponse().getContentAsString()); - String token = responseBody.get("token").asText(); - - // Access protected endpoint with token - mockMvc.perform(get("/api/health/secured") - .header("Authorization", "Bearer " + token)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.status").value("authenticated")); - } -} diff --git a/src/test/java/net/siegeln/cameleer/saas/auth/AuthServiceTest.java b/src/test/java/net/siegeln/cameleer/saas/auth/AuthServiceTest.java deleted file mode 100644 index 3eb8282..0000000 --- a/src/test/java/net/siegeln/cameleer/saas/auth/AuthServiceTest.java +++ /dev/null @@ -1,171 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import net.siegeln.cameleer.saas.audit.AuditAction; -import net.siegeln.cameleer.saas.audit.AuditService; -import net.siegeln.cameleer.saas.auth.dto.LoginRequest; -import net.siegeln.cameleer.saas.auth.dto.RegisterRequest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.crypto.password.PasswordEncoder; - -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class AuthServiceTest { - - @Mock - private UserRepository userRepository; - @Mock - private RoleRepository roleRepository; - @Mock - private PasswordEncoder passwordEncoder; - @Mock - private JwtService jwtService; - @Mock - private AuditService auditService; - - private AuthService authService; - - @BeforeEach - void setUp() { - authService = new AuthService(userRepository, roleRepository, - passwordEncoder, jwtService, auditService); - } - - @Test - void register_createsUserAndReturnsToken() { - var request = new RegisterRequest("user@example.com", "Test User", "password123"); - var ownerRole = new RoleEntity(); - ownerRole.setName("OWNER"); - - when(userRepository.existsByEmail("user@example.com")).thenReturn(false); - when(passwordEncoder.encode("password123")).thenReturn("encoded-password"); - when(roleRepository.findByName("OWNER")).thenReturn(Optional.of(ownerRole)); - when(userRepository.save(any(UserEntity.class))).thenAnswer(invocation -> { - UserEntity user = invocation.getArgument(0); - // simulate ID assignment by persistence - try { - var idField = UserEntity.class.getDeclaredField("id"); - idField.setAccessible(true); - idField.set(user, java.util.UUID.randomUUID()); - } catch (Exception e) { - throw new RuntimeException(e); - } - return user; - }); - when(jwtService.generateToken(any(UserEntity.class))).thenReturn("test-jwt-token"); - - var response = authService.register(request, "127.0.0.1"); - - assertNotNull(response); - assertEquals("test-jwt-token", response.token()); - assertEquals("user@example.com", response.email()); - assertEquals("Test User", response.name()); - - // Verify audit was logged - verify(auditService).log( - any(), eq("user@example.com"), eq(null), - eq(AuditAction.AUTH_REGISTER), eq(null), - eq(null), eq("127.0.0.1"), - eq("SUCCESS"), eq(null) - ); - } - - @Test - void register_rejectsDuplicateEmail() { - var request = new RegisterRequest("existing@example.com", "Test User", "password123"); - when(userRepository.existsByEmail("existing@example.com")).thenReturn(true); - - var exception = assertThrows(IllegalArgumentException.class, - () -> authService.register(request, "127.0.0.1")); - - assertEquals("Email already registered", exception.getMessage()); - verify(userRepository, never()).save(any()); - verify(auditService, never()).log(any(), any(), any(), any(), any(), any(), any(), any(), any()); - } - - @Test - void login_returnsTokenForValidCredentials() { - var request = new LoginRequest("user@example.com", "password123"); - var user = createUserWithId("user@example.com", "encoded-password"); - - when(userRepository.findByEmail("user@example.com")).thenReturn(Optional.of(user)); - when(passwordEncoder.matches("password123", "encoded-password")).thenReturn(true); - when(jwtService.generateToken(user)).thenReturn("login-jwt-token"); - - var response = authService.login(request, "192.168.1.1"); - - assertNotNull(response); - assertEquals("login-jwt-token", response.token()); - assertEquals("user@example.com", response.email()); - - verify(auditService).log( - any(), eq("user@example.com"), eq(null), - eq(AuditAction.AUTH_LOGIN), eq(null), - eq(null), eq("192.168.1.1"), - eq("SUCCESS"), eq(null) - ); - } - - @Test - void login_rejectsInvalidPassword() { - var request = new LoginRequest("user@example.com", "wrong-password"); - var user = createUserWithId("user@example.com", "encoded-password"); - - when(userRepository.findByEmail("user@example.com")).thenReturn(Optional.of(user)); - when(passwordEncoder.matches("wrong-password", "encoded-password")).thenReturn(false); - - assertThrows(IllegalArgumentException.class, - () -> authService.login(request, "192.168.1.1")); - - // Verify AUTH_LOGIN_FAILED audit was logged - verify(auditService).log( - any(), eq("user@example.com"), eq(null), - eq(AuditAction.AUTH_LOGIN_FAILED), eq(null), - eq(null), eq("192.168.1.1"), - eq("FAILURE"), eq(null) - ); - } - - @Test - void login_rejectsUnknownEmail() { - var request = new LoginRequest("unknown@example.com", "password123"); - - when(userRepository.findByEmail("unknown@example.com")).thenReturn(Optional.empty()); - - var exception = assertThrows(IllegalArgumentException.class, - () -> authService.login(request, "192.168.1.1")); - - assertEquals("Invalid credentials", exception.getMessage()); - verify(auditService, never()).log(any(), any(), any(), any(), any(), any(), any(), any(), any()); - } - - private UserEntity createUserWithId(String email, String password) { - var user = new UserEntity(); - user.setEmail(email); - user.setName("Test User"); - user.setPassword(password); - try { - var idField = UserEntity.class.getDeclaredField("id"); - idField.setAccessible(true); - idField.set(user, UUID.randomUUID()); - } catch (Exception e) { - throw new RuntimeException(e); - } - return user; - } -} diff --git a/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java b/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java index b662626..124e017 100644 --- a/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java +++ b/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java @@ -13,6 +13,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -30,25 +31,12 @@ class LicenseControllerTest { @Autowired private ObjectMapper objectMapper; - private String getAuthToken() throws Exception { - var registerRequest = new net.siegeln.cameleer.saas.auth.dto.RegisterRequest( - "license-test-" + System.nanoTime() + "@example.com", "Test User", "password123"); - - var result = mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(registerRequest))) - .andExpect(status().isCreated()) - .andReturn(); - - return objectMapper.readTree(result.getResponse().getContentAsString()).get("token").asText(); - } - - private String createTenantAndGetId(String token) throws Exception { + private String createTenantAndGetId() throws Exception { String slug = "license-tenant-" + System.nanoTime(); var request = new CreateTenantRequest("License Test Org", slug, "MID"); var result = mockMvc.perform(post("/api/tenants") - .header("Authorization", "Bearer " + token) + .with(jwt().jwt(j -> j.claim("sub", "test-user"))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) @@ -59,11 +47,10 @@ class LicenseControllerTest { @Test void generateLicense_returns201WithToken() throws Exception { - String token = getAuthToken(); - String tenantId = createTenantAndGetId(token); + String tenantId = createTenantAndGetId(); mockMvc.perform(post("/api/tenants/" + tenantId + "/license") - .header("Authorization", "Bearer " + token)) + .with(jwt().jwt(j -> j.claim("sub", "test-user")))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.token").isNotEmpty()) .andExpect(jsonPath("$.tier").value("MID")) @@ -72,26 +59,24 @@ class LicenseControllerTest { @Test void getActiveLicense_returnsLicense() throws Exception { - String token = getAuthToken(); - String tenantId = createTenantAndGetId(token); + String tenantId = createTenantAndGetId(); mockMvc.perform(post("/api/tenants/" + tenantId + "/license") - .header("Authorization", "Bearer " + token)) + .with(jwt().jwt(j -> j.claim("sub", "test-user")))) .andExpect(status().isCreated()); mockMvc.perform(get("/api/tenants/" + tenantId + "/license") - .header("Authorization", "Bearer " + token)) + .with(jwt().jwt(j -> j.claim("sub", "test-user")))) .andExpect(status().isOk()) .andExpect(jsonPath("$.tier").value("MID")); } @Test void getActiveLicense_returns404WhenNone() throws Exception { - String token = getAuthToken(); - String tenantId = createTenantAndGetId(token); + String tenantId = createTenantAndGetId(); mockMvc.perform(get("/api/tenants/" + tenantId + "/license") - .header("Authorization", "Bearer " + token)) + .with(jwt().jwt(j -> j.claim("sub", "test-user")))) .andExpect(status().isNotFound()); } } diff --git a/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java b/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java index ad36932..ebdcc58 100644 --- a/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java +++ b/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java @@ -13,6 +13,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -30,26 +31,14 @@ class TenantControllerTest { @Autowired private ObjectMapper objectMapper; - private String getAuthToken() throws Exception { - var registerRequest = new net.siegeln.cameleer.saas.auth.dto.RegisterRequest( - "tenant-test-" + System.nanoTime() + "@example.com", "Test User", "password123"); - - var result = mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(registerRequest))) - .andExpect(status().isCreated()) - .andReturn(); - - return objectMapper.readTree(result.getResponse().getContentAsString()).get("token").asText(); - } - @Test void createTenant_returns201() throws Exception { - String token = getAuthToken(); var request = new CreateTenantRequest("Test Org", "test-org-" + System.nanoTime(), "LOW"); mockMvc.perform(post("/api/tenants") - .header("Authorization", "Bearer " + token) + .with(jwt().jwt(j -> j + .claim("sub", "test-user") + .claim("organization_id", "test-org"))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) @@ -60,18 +49,17 @@ class TenantControllerTest { @Test void createTenant_returns409ForDuplicateSlug() throws Exception { - String token = getAuthToken(); String slug = "duplicate-slug-" + System.nanoTime(); var request = new CreateTenantRequest("First", slug, null); mockMvc.perform(post("/api/tenants") - .header("Authorization", "Bearer " + token) + .with(jwt().jwt(j -> j.claim("sub", "test-user"))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()); mockMvc.perform(post("/api/tenants") - .header("Authorization", "Bearer " + token) + .with(jwt().jwt(j -> j.claim("sub", "test-user"))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isConflict()); @@ -89,12 +77,11 @@ class TenantControllerTest { @Test void getTenant_returnsTenantById() throws Exception { - String token = getAuthToken(); String slug = "get-test-" + System.nanoTime(); var request = new CreateTenantRequest("Get Test", slug, null); var createResult = mockMvc.perform(post("/api/tenants") - .header("Authorization", "Bearer " + token) + .with(jwt().jwt(j -> j.claim("sub", "test-user"))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) @@ -103,7 +90,7 @@ class TenantControllerTest { String id = objectMapper.readTree(createResult.getResponse().getContentAsString()).get("id").asText(); mockMvc.perform(get("/api/tenants/" + id) - .header("Authorization", "Bearer " + token)) + .with(jwt().jwt(j -> j.claim("sub", "test-user")))) .andExpect(status().isOk()) .andExpect(jsonPath("$.slug").value(slug)); }