package se.bilhalsning.controller; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; 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; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; import se.bilhalsning.dto.LoginRequest; import se.bilhalsning.dto.RegisterRequest; import se.bilhalsning.entity.User; import se.bilhalsning.exception.EmailAlreadyExistsException; import se.bilhalsning.exception.InvalidCredentialsException; import se.bilhalsning.security.JwtService; import java.util.Optional; import se.bilhalsning.service.PasswordResetService; import se.bilhalsning.service.UserService; @SpringBootTest @AutoConfigureMockMvc class AuthControllerTest { @Autowired private MockMvc mockMvc; private final ObjectMapper objectMapper = new ObjectMapper(); @MockitoBean private UserService userService; @MockitoBean private PasswordResetService passwordResetService; @MockitoBean private JwtService jwtService; @Test void shouldReturn201AndTokenWhenRegisterSucceeds() throws Exception { when(userService.createUser("new@example.com", "password123")).thenReturn(null); when(jwtService.generateToken("new@example.com", "user")).thenReturn("test-jwt-token"); RegisterRequest request = new RegisterRequest("new@example.com", "password123"); mockMvc.perform(post("/api/auth/register") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.token").value("test-jwt-token")); } @Test void shouldReturn409WhenEmailAlreadyExists() throws Exception { when(userService.createUser("taken@example.com", "password123")) .thenThrow(new EmailAlreadyExistsException("taken@example.com")); RegisterRequest request = new RegisterRequest("taken@example.com", "password123"); mockMvc.perform(post("/api/auth/register") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isConflict()) .andExpect(jsonPath("$.message").value("E-postadressen är redan registrerad")); } @Test void shouldReturn400WhenEmailIsInvalid() throws Exception { RegisterRequest request = new RegisterRequest("not-an-email", "password123"); mockMvc.perform(post("/api/auth/register") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()); } @Test void shouldReturn400WhenPasswordIsTooShort() throws Exception { RegisterRequest request = new RegisterRequest("new@example.com", "1234567"); mockMvc.perform(post("/api/auth/register") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()); } @Test void shouldReturn400WhenEmailIsMissing() throws Exception { RegisterRequest request = new RegisterRequest("", "password123"); mockMvc.perform(post("/api/auth/register") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()); } @Test void shouldReturn200AndTokenWhenLoginSucceeds() throws Exception { User user = new User(); user.setEmail("user@example.com"); user.setRole("user"); when(userService.authenticate("user@example.com", "password123")).thenReturn(user); when(jwtService.generateToken("user@example.com", "user")).thenReturn("login-jwt-token"); LoginRequest request = new LoginRequest("user@example.com", "password123"); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andExpect(jsonPath("$.token").value("login-jwt-token")); } @Test void shouldReturnAdminRoleInTokenWhenAdminLogsIn() throws Exception { User admin = new User(); admin.setEmail("admin@bilhalsning.se"); admin.setRole("admin"); when(userService.authenticate("admin@bilhalsning.se", "admin1234")).thenReturn(admin); when(jwtService.generateToken("admin@bilhalsning.se", "admin")).thenReturn("admin-jwt-token"); LoginRequest request = new LoginRequest("admin@bilhalsning.se", "admin1234"); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andExpect(jsonPath("$.token").value("admin-jwt-token")); } @Test void shouldReturn401WhenCredentialsAreInvalid() throws Exception { when(userService.authenticate("user@example.com", "wrongpassword")) .thenThrow(new InvalidCredentialsException()); LoginRequest request = new LoginRequest("user@example.com", "wrongpassword"); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isUnauthorized()) .andExpect(jsonPath("$.message").value("Felaktig e-post eller lösenord")); } @Test void shouldReturn400WhenLoginEmailIsBlank() throws Exception { LoginRequest request = new LoginRequest("", "password123"); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()); } @Test void shouldReturn400WhenLoginPasswordIsBlank() throws Exception { LoginRequest request = new LoginRequest("user@example.com", ""); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()); } @Test void shouldReturn400WhenLoginEmailIsInvalid() throws Exception { LoginRequest request = new LoginRequest("not-an-email", "password123"); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()); } @Test void shouldReturn200WhenForgotPasswordRequested() throws Exception { when(passwordResetService.requestReset("user@example.com")).thenReturn(Optional.empty()); mockMvc.perform(post("/api/auth/forgot-password") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\":\"user@example.com\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.message") .value("Om e-postadressen finns har vi skickat instruktioner för att återställa lösenordet.")) .andExpect(jsonPath("$.testToken").doesNotExist()); verify(passwordResetService).requestReset("user@example.com"); } @Test void shouldIncludeTestTokenWhenServiceReturnsToken() throws Exception { when(passwordResetService.requestReset("user@example.com")) .thenReturn(Optional.of("e2e-reset-token")); mockMvc.perform(post("/api/auth/forgot-password") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\":\"user@example.com\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.testToken").value("e2e-reset-token")); } @Test void shouldReturn200WhenResetPasswordSucceeds() throws Exception { mockMvc.perform(post("/api/auth/reset-password") .contentType(MediaType.APPLICATION_JSON) .content("{\"token\":\"abc\",\"password\":\"newpassword123\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("Lösenordet har uppdaterats. Du kan nu logga in.")); } @Test @WithMockUser(username = "admin@bilhej.se") void shouldReturn200WhenChangePasswordSucceeds() throws Exception { mockMvc.perform(post("/api/auth/change-password") .contentType(MediaType.APPLICATION_JSON) .content( "{\"currentPassword\":\"test1234\",\"newPassword\":\"newpassword123\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("Lösenordet har uppdaterats.")); } @Test void shouldRejectChangePasswordWithoutAuth() throws Exception { mockMvc.perform(post("/api/auth/change-password") .contentType(MediaType.APPLICATION_JSON) .content( "{\"currentPassword\":\"test1234\",\"newPassword\":\"newpassword123\"}")) .andExpect(status().isForbidden()); } }