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/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
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());
+ }
+}