From df7c18e4af31d82499259b4a2e699320fede9238 Mon Sep 17 00:00:00 2001 From: Annika Holmqvist Date: Sun, 5 Apr 2026 18:54:20 +0200 Subject: [PATCH 1/2] refactor: remove `insuranceNumber` field from Pet-related components - Removed `insuranceNumber` from `PetRequest`, `PetResponse`, and relevant methods in `PetController` and `PetService`. - Updated logic to align with the simplified data model. --- .../org/example/vet1177/controller/PetController.java | 1 - .../example/vet1177/dto/request/pet/PetRequest.java | 10 ---------- .../example/vet1177/dto/response/pet/PetResponse.java | 8 +------- .../java/org/example/vet1177/services/PetService.java | 11 +++++------ 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/example/vet1177/controller/PetController.java b/src/main/java/org/example/vet1177/controller/PetController.java index 4b18d6c..191088e 100644 --- a/src/main/java/org/example/vet1177/controller/PetController.java +++ b/src/main/java/org/example/vet1177/controller/PetController.java @@ -36,7 +36,6 @@ public PetResponse createPet( saved.getBreed(), saved.getDateOfBirth(), saved.getWeightKg(), - saved.getInsuranceNumber(), saved.getCreatedAt(), saved.getUpdatedAt() ); diff --git a/src/main/java/org/example/vet1177/dto/request/pet/PetRequest.java b/src/main/java/org/example/vet1177/dto/request/pet/PetRequest.java index 4069392..d1e2d21 100644 --- a/src/main/java/org/example/vet1177/dto/request/pet/PetRequest.java +++ b/src/main/java/org/example/vet1177/dto/request/pet/PetRequest.java @@ -22,9 +22,6 @@ public class PetRequest { @Positive private BigDecimal weightKg; - @Size(max = 100) - private String insuranceNumber; - public PetRequest() { } @@ -68,11 +65,4 @@ public void setWeightKg(BigDecimal weightKg) { this.weightKg = weightKg; } - public String getInsuranceNumber() { - return insuranceNumber; - } - - public void setInsuranceNumber(String insuranceNumber) { - this.insuranceNumber = insuranceNumber; - } } \ No newline at end of file diff --git a/src/main/java/org/example/vet1177/dto/response/pet/PetResponse.java b/src/main/java/org/example/vet1177/dto/response/pet/PetResponse.java index 1c77742..91f593e 100644 --- a/src/main/java/org/example/vet1177/dto/response/pet/PetResponse.java +++ b/src/main/java/org/example/vet1177/dto/response/pet/PetResponse.java @@ -14,7 +14,6 @@ public class PetResponse { private String breed; private LocalDate dateOfBirth; private BigDecimal weightKg; - private String insuranceNumber; private Instant createdAt; private Instant updatedAt; @@ -23,7 +22,7 @@ public PetResponse() { public PetResponse(UUID id, UUID ownerId, String name, String species, String breed, LocalDate dateOfBirth, BigDecimal weightKg, - String insuranceNumber, Instant createdAt, Instant updatedAt) { + Instant createdAt, Instant updatedAt) { this.id = id; this.ownerId = ownerId; this.name = name; @@ -31,7 +30,6 @@ public PetResponse(UUID id, UUID ownerId, String name, String species, this.breed = breed; this.dateOfBirth = dateOfBirth; this.weightKg = weightKg; - this.insuranceNumber = insuranceNumber; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -64,10 +62,6 @@ public BigDecimal getWeightKg() { return weightKg; } - public String getInsuranceNumber() { - return insuranceNumber; - } - public Instant getCreatedAt() { return createdAt; } diff --git a/src/main/java/org/example/vet1177/services/PetService.java b/src/main/java/org/example/vet1177/services/PetService.java index a788c7c..d086a01 100644 --- a/src/main/java/org/example/vet1177/services/PetService.java +++ b/src/main/java/org/example/vet1177/services/PetService.java @@ -107,11 +107,11 @@ public Pet updatePet(UUID currentUserId, UUID petId, PetRequest request) { throw new RuntimeException("Du saknar behörighet för att uppdatera info om djuret"); } - existingPet.setName(updatedPet.getName()); - existingPet.setSpecies(updatedPet.getSpecies()); - existingPet.setBreed(updatedPet.getBreed()); - existingPet.setDateOfBirth(updatedPet.getDateOfBirth()); - existingPet.setWeightKg(updatedPet.getWeightKg()); + existingPet.setName(request.getName()); + existingPet.setSpecies(request.getSpecies()); + existingPet.setBreed(request.getBreed()); + existingPet.setDateOfBirth(request.getDateOfBirth()); + existingPet.setWeightKg(request.getWeightKg()); return petRepository.save(existingPet); @@ -152,7 +152,6 @@ private void applyPetRequest(Pet target, PetRequest request) { target.setBreed(request.getBreed()); target.setDateOfBirth(request.getDateOfBirth()); target.setWeightKg(request.getWeightKg()); - target.setInsuranceNumber(request.getInsuranceNumber()); } } \ No newline at end of file From 474bbbc7b0ee719ccd380e9f7fbc50b530b9a8e3 Mon Sep 17 00:00:00 2001 From: Annika Holmqvist Date: Sun, 5 Apr 2026 18:57:34 +0200 Subject: [PATCH 2/2] test: add integration tests for CommentController - Added test coverage for endpoints: `create`, `getByRecord`, `update`, `delete`, and `countByRecord`. - Ensured proper exception handling for validation errors, forbidden access, and missing resources. - Updated `pom.xml` to include test dependencies for Spring Security and WebMVC. Closes #115 Closes #98 --- pom.xml | 10 + .../controller/CommentControllerTest.java | 318 ++++++++++++++++++ 2 files changed, 328 insertions(+) create mode 100644 src/test/java/org/example/vet1177/controller/CommentControllerTest.java diff --git a/pom.xml b/pom.xml index ae81e84..837de1b 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,16 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-security-test + test + + + org.springframework.boot + spring-boot-starter-webmvc-test + test + org.postgresql postgresql diff --git a/src/test/java/org/example/vet1177/controller/CommentControllerTest.java b/src/test/java/org/example/vet1177/controller/CommentControllerTest.java new file mode 100644 index 0000000..37ccdf8 --- /dev/null +++ b/src/test/java/org/example/vet1177/controller/CommentControllerTest.java @@ -0,0 +1,318 @@ +package org.example.vet1177.controller; + +import tools.jackson.databind.ObjectMapper; +import org.example.vet1177.dto.request.comment.CreateCommentRequest; +import org.example.vet1177.dto.request.comment.UpdateCommentRequest; +import org.example.vet1177.entities.Comment; +import org.example.vet1177.entities.MedicalRecord; +import org.example.vet1177.entities.Role; +import org.example.vet1177.entities.User; +import org.example.vet1177.exception.ForbiddenException; +import org.example.vet1177.exception.ResourceNotFoundException; +import org.example.vet1177.services.CommentService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.example.vet1177.security.SecurityConfig; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.RequestPostProcessor; + +import java.util.List; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(CommentController.class) +@Import(SecurityConfig.class) +class CommentControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private CommentService commentService; + + private User currentUser; + private MedicalRecord record; + private Comment comment; + private UUID recordId; + private UUID commentId; + + @BeforeEach + void setUp() { + recordId = UUID.randomUUID(); + commentId = UUID.randomUUID(); + + currentUser = new User("Dr. Sara Lindqvist", "sara@vet.se", "hash", Role.VET); + + record = new MedicalRecord(); + record.setId(recordId); + + comment = new Comment(); + comment.setBody("En kommentar."); + comment.setAuthor(currentUser); + comment.setMedicalRecord(record); + } + + private RequestPostProcessor authenticatedAs(User user) { + return authentication(new UsernamePasswordAuthenticationToken( + user, null, user.getAuthorities() + )); + } + + // ------------------------------------------------------------------------- + // POST /api/comments — create + // ------------------------------------------------------------------------- + + @Test + void create_shouldReturn200WithCommentResponse() throws Exception { + when(commentService.create(eq(recordId), eq("En kommentar."), any(User.class))) + .thenReturn(comment); + + var request = new CreateCommentRequest(recordId, "En kommentar."); + + mockMvc.perform(post("/api/comments") + .with(authenticatedAs(currentUser)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.body").value("En kommentar.")) + .andExpect(jsonPath("$.authorName").value("Dr. Sara Lindqvist")); + } + + @Test + void create_whenBodyIsBlank_shouldReturn400() throws Exception { + var request = new CreateCommentRequest(recordId, " "); + + mockMvc.perform(post("/api/comments") + .with(authenticatedAs(currentUser)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void create_whenRecordIdIsNull_shouldReturn400() throws Exception { + var request = new CreateCommentRequest(null, "En kommentar."); + + mockMvc.perform(post("/api/comments") + .with(authenticatedAs(currentUser)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void create_whenRecordNotFound_shouldReturn404() throws Exception { + when(commentService.create(any(), any(), any())) + .thenThrow(new ResourceNotFoundException("MedicalRecord", recordId)); + + var request = new CreateCommentRequest(recordId, "En kommentar."); + + mockMvc.perform(post("/api/comments") + .with(authenticatedAs(currentUser)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()); + } + + @Test + void create_whenForbidden_shouldReturn403() throws Exception { + when(commentService.create(any(), any(), any())) + .thenThrow(new ForbiddenException("Du saknar behörighet")); + + var request = new CreateCommentRequest(recordId, "En kommentar."); + + mockMvc.perform(post("/api/comments") + .with(authenticatedAs(currentUser)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()); + } + + // ------------------------------------------------------------------------- + // GET /api/comments/record/{recordId} — getByRecord + // ------------------------------------------------------------------------- + + @Test + void getByRecord_shouldReturn200WithListOfComments() throws Exception { + when(commentService.getByRecord(eq(recordId), any(User.class))) + .thenReturn(List.of(comment)); + + mockMvc.perform(get("/api/comments/record/{recordId}", recordId) + .with(authenticatedAs(currentUser))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].body").value("En kommentar.")); + } + + @Test + void getByRecord_whenNoComments_shouldReturnEmptyList() throws Exception { + when(commentService.getByRecord(eq(recordId), any(User.class))) + .thenReturn(List.of()); + + mockMvc.perform(get("/api/comments/record/{recordId}", recordId) + .with(authenticatedAs(currentUser))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isEmpty()); + } + + @Test + void getByRecord_whenRecordNotFound_shouldReturn404() throws Exception { + when(commentService.getByRecord(any(), any())) + .thenThrow(new ResourceNotFoundException("MedicalRecord", recordId)); + + mockMvc.perform(get("/api/comments/record/{recordId}", recordId) + .with(authenticatedAs(currentUser))) + .andExpect(status().isNotFound()); + } + + @Test + void getByRecord_whenForbidden_shouldReturn403() throws Exception { + when(commentService.getByRecord(any(), any())) + .thenThrow(new ForbiddenException("Du saknar behörighet")); + + mockMvc.perform(get("/api/comments/record/{recordId}", recordId) + .with(authenticatedAs(currentUser))) + .andExpect(status().isForbidden()); + } + + // ------------------------------------------------------------------------- + // PUT /api/comments/{id} — update + // ------------------------------------------------------------------------- + + @Test + void update_shouldReturn200WithUpdatedComment() throws Exception { + comment.setBody("Uppdaterad text."); + when(commentService.update(eq(commentId), eq("Uppdaterad text."), any(User.class))) + .thenReturn(comment); + + var request = new UpdateCommentRequest("Uppdaterad text."); + + mockMvc.perform(put("/api/comments/{id}", commentId) + .with(authenticatedAs(currentUser)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.body").value("Uppdaterad text.")); + } + + @Test + void update_whenBodyIsBlank_shouldReturn400() throws Exception { + var request = new UpdateCommentRequest(" "); + + mockMvc.perform(put("/api/comments/{id}", commentId) + .with(authenticatedAs(currentUser)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void update_whenCommentNotFound_shouldReturn404() throws Exception { + when(commentService.update(any(), any(), any())) + .thenThrow(new ResourceNotFoundException("Comment", commentId)); + + var request = new UpdateCommentRequest("Uppdaterad text."); + + mockMvc.perform(put("/api/comments/{id}", commentId) + .with(authenticatedAs(currentUser)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()); + } + + @Test + void update_whenForbidden_shouldReturn403() throws Exception { + when(commentService.update(any(), any(), any())) + .thenThrow(new ForbiddenException("Du saknar behörighet")); + + var request = new UpdateCommentRequest("Uppdaterad text."); + + mockMvc.perform(put("/api/comments/{id}", commentId) + .with(authenticatedAs(currentUser)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()); + } + + // ------------------------------------------------------------------------- + // DELETE /api/comments/{id} — delete + // ------------------------------------------------------------------------- + + @Test + void delete_shouldReturn204() throws Exception { + doNothing().when(commentService).delete(eq(commentId), any(User.class)); + + mockMvc.perform(delete("/api/comments/{id}", commentId) + .with(authenticatedAs(currentUser))) + .andExpect(status().isNoContent()); + } + + @Test + void delete_whenCommentNotFound_shouldReturn404() throws Exception { + doThrow(new ResourceNotFoundException("Comment", commentId)) + .when(commentService).delete(any(), any()); + + mockMvc.perform(delete("/api/comments/{id}", commentId) + .with(authenticatedAs(currentUser))) + .andExpect(status().isNotFound()); + } + + @Test + void delete_whenForbidden_shouldReturn403() throws Exception { + doThrow(new ForbiddenException("Du saknar behörighet")) + .when(commentService).delete(any(), any()); + + mockMvc.perform(delete("/api/comments/{id}", commentId) + .with(authenticatedAs(currentUser))) + .andExpect(status().isForbidden()); + } + + // ------------------------------------------------------------------------- + // GET /api/comments/record/{recordId}/count — countByRecord + // ------------------------------------------------------------------------- + + @Test + void countByRecord_shouldReturn200WithCount() throws Exception { + when(commentService.countByRecord(eq(recordId), any(User.class))) + .thenReturn(3L); + + mockMvc.perform(get("/api/comments/record/{recordId}/count", recordId) + .with(authenticatedAs(currentUser))) + .andExpect(status().isOk()) + .andExpect(content().string("3")); + } + + @Test + void countByRecord_whenRecordNotFound_shouldReturn404() throws Exception { + when(commentService.countByRecord(any(), any())) + .thenThrow(new ResourceNotFoundException("MedicalRecord", recordId)); + + mockMvc.perform(get("/api/comments/record/{recordId}/count", recordId) + .with(authenticatedAs(currentUser))) + .andExpect(status().isNotFound()); + } + + @Test + void countByRecord_whenForbidden_shouldReturn403() throws Exception { + when(commentService.countByRecord(any(), any())) + .thenThrow(new ForbiddenException("Du saknar behörighet")); + + mockMvc.perform(get("/api/comments/record/{recordId}/count", recordId) + .with(authenticatedAs(currentUser))) + .andExpect(status().isForbidden()); + } +}