diff --git a/pom.xml b/pom.xml
index 083ac4b6..23354c8e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -86,6 +86,8 @@
s3
2.20.0
+
+
@@ -94,7 +96,39 @@
org.springframework.boot
spring-boot-maven-plugin
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.14
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ report
+ prepare-package
+
+ report
+
+
+
+ post-unit-test
+ test
+
+ report
+
+
+ target/jacoco.exec
+ target/jacoco-ut
+
+
+
+
+
diff --git a/src/test/java/org/example/vet1177/dto/request/comment/CreateCommentRequestValidationTest.java b/src/test/java/org/example/vet1177/dto/request/comment/CreateCommentRequestValidationTest.java
new file mode 100644
index 00000000..5e3bc2ca
--- /dev/null
+++ b/src/test/java/org/example/vet1177/dto/request/comment/CreateCommentRequestValidationTest.java
@@ -0,0 +1,114 @@
+package org.example.vet1177.dto.request.comment;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CreateCommentRequestValidationTest {
+
+ private static ValidatorFactory factory;
+ private static Validator validator;
+
+ @BeforeAll
+ static void setUp() {
+ factory = Validation.buildDefaultValidatorFactory();
+ validator = factory.getValidator();
+ }
+
+ @AfterAll
+ static void tearDown() {
+ factory.close();
+ }
+
+ // --- Happy paths ---
+
+ @Test
+ void validRequest_shouldProduceNoViolations() {
+ var request = new CreateCommentRequest(UUID.randomUUID(), "En giltig kommentar.");
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).isEmpty();
+ }
+
+ @Test
+ void bodyWithExactly5000Characters_shouldProduceNoViolations() {
+ var request = new CreateCommentRequest(UUID.randomUUID(), "x".repeat(5000));
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).isEmpty();
+ }
+
+ // --- Sad paths: recordId ---
+
+ @Test
+ void nullRecordId_shouldProduceViolationWithCorrectMessage() {
+ var request = new CreateCommentRequest(null, "En giltig kommentar.");
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).hasSize(1);
+ assertThat(violations.iterator().next().getMessage()).isEqualTo("Ärende måste anges");
+ }
+
+ // --- Sad paths: body ---
+
+ @Test
+ void blankBody_shouldProduceViolationWithCorrectMessage() {
+ var request = new CreateCommentRequest(UUID.randomUUID(), " ");
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).hasSize(1);
+ assertThat(violations.iterator().next().getMessage()).isEqualTo("Kommentar får inte vara tom");
+ }
+
+ @Test
+ void emptyBody_shouldProduceViolationWithCorrectMessage() {
+ var request = new CreateCommentRequest(UUID.randomUUID(), "");
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).hasSize(1);
+ assertThat(violations.iterator().next().getMessage()).isEqualTo("Kommentar får inte vara tom");
+ }
+
+ @Test
+ void nullBody_shouldProduceViolationWithCorrectMessage() {
+ var request = new CreateCommentRequest(UUID.randomUUID(), null);
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).hasSize(1);
+ assertThat(violations.iterator().next().getMessage()).isEqualTo("Kommentar får inte vara tom");
+ }
+
+ @Test
+ void bodyExceeding5000Characters_shouldProduceViolationWithCorrectMessage() {
+ var request = new CreateCommentRequest(UUID.randomUUID(), "x".repeat(5001));
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).hasSize(1);
+ assertThat(violations.iterator().next().getMessage()).isEqualTo("Kommentar får max vara 5000 tecken");
+ }
+
+ @Test
+ void allFieldsInvalid_shouldProduceTwoViolations() {
+ var request = new CreateCommentRequest(null, "");
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).hasSize(2);
+ }
+}
diff --git a/src/test/java/org/example/vet1177/dto/request/comment/UpdateCommentRequestValidationTest.java b/src/test/java/org/example/vet1177/dto/request/comment/UpdateCommentRequestValidationTest.java
new file mode 100644
index 00000000..6dea760c
--- /dev/null
+++ b/src/test/java/org/example/vet1177/dto/request/comment/UpdateCommentRequestValidationTest.java
@@ -0,0 +1,92 @@
+package org.example.vet1177.dto.request.comment;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class UpdateCommentRequestValidationTest {
+
+ private static ValidatorFactory factory;
+ private static Validator validator;
+
+ @BeforeAll
+ static void setUp() {
+ factory = Validation.buildDefaultValidatorFactory();
+ validator = factory.getValidator();
+ }
+
+ @AfterAll
+ static void tearDown() {
+ factory.close();
+ }
+
+ // --- Happy paths ---
+
+ @Test
+ void validRequest_shouldProduceNoViolations() {
+ var request = new UpdateCommentRequest("Uppdaterad kommentar med ny information.");
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).isEmpty();
+ }
+
+ @Test
+ void bodyWithExactly5000Characters_shouldProduceNoViolations() {
+ var request = new UpdateCommentRequest("x".repeat(5000));
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).isEmpty();
+ }
+
+ // --- Sad paths: body ---
+
+ @Test
+ void blankBody_shouldProduceViolationWithCorrectMessage() {
+ var request = new UpdateCommentRequest(" ");
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).hasSize(1);
+ assertThat(violations.iterator().next().getMessage()).isEqualTo("Kommentar får inte vara tom");
+ }
+
+ @Test
+ void emptyBody_shouldProduceViolationWithCorrectMessage() {
+ var request = new UpdateCommentRequest("");
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).hasSize(1);
+ assertThat(violations.iterator().next().getMessage()).isEqualTo("Kommentar får inte vara tom");
+ }
+
+ @Test
+ void nullBody_shouldProduceViolationWithCorrectMessage() {
+ var request = new UpdateCommentRequest(null);
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).hasSize(1);
+ assertThat(violations.iterator().next().getMessage()).isEqualTo("Kommentar får inte vara tom");
+ }
+
+ @Test
+ void bodyExceeding5000Characters_shouldProduceViolationWithCorrectMessage() {
+ var request = new UpdateCommentRequest("x".repeat(5001));
+
+ Set> violations = validator.validate(request);
+
+ assertThat(violations).hasSize(1);
+ assertThat(violations.iterator().next().getMessage()).isEqualTo("Kommentar får max vara 5000 tecken");
+ }
+}
diff --git a/src/test/java/org/example/vet1177/dto/response/comment/CommentResponseTest.java b/src/test/java/org/example/vet1177/dto/response/comment/CommentResponseTest.java
new file mode 100644
index 00000000..235aa20b
--- /dev/null
+++ b/src/test/java/org/example/vet1177/dto/response/comment/CommentResponseTest.java
@@ -0,0 +1,109 @@
+package org.example.vet1177.dto.response.comment;
+
+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.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.time.Instant;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class CommentResponseTest {
+
+ private Comment comment;
+ private User author;
+ private MedicalRecord medicalRecord;
+ private UUID commentId;
+ private UUID authorId;
+ private UUID recordId;
+
+
+ @BeforeEach
+ void setUp() throws Exception {
+ commentId = UUID.randomUUID();
+ authorId = UUID.randomUUID();
+ recordId = UUID.randomUUID();
+
+ author = new User("Dr. Sara Lindqvist", "sara@vet.se", "hash", Role.VET);
+ setPrivateField(author, "id", authorId);
+
+ medicalRecord = new MedicalRecord();
+ medicalRecord.setId(recordId);
+
+ comment = new Comment();
+ setPrivateField(comment, "id", commentId);
+ comment.setBody("Djuret är friskt och kan skrivas ut.");
+ comment.setAuthor(author);
+ comment.setMedicalRecord(medicalRecord);
+ callProtectedMethod(comment, "onCreate");
+ }
+
+ // --- Happy path ---
+
+ @Test
+ void from_shouldMapAllFieldsCorrectly() {
+ CommentResponse response = CommentResponse.from(comment);
+
+ assertThat(response.id()).isEqualTo(commentId);
+ assertThat(response.recordId()).isEqualTo(recordId);
+ assertThat(response.authorId()).isEqualTo(authorId);
+ assertThat(response.authorName()).isEqualTo("Dr. Sara Lindqvist");
+ assertThat(response.body()).isEqualTo("Djuret är friskt och kan skrivas ut.");
+ assertThat(response.createdAt()).isNotNull();
+ assertThat(response.updatedAt()).isNotNull();
+ }
+
+ @Test
+ void from_createdAtAndUpdatedAtShouldReflectCommentTimestamps() {
+ CommentResponse response = CommentResponse.from(comment);
+
+ assertThat(response.createdAt()).isEqualTo(comment.getCreatedAt());
+ assertThat(response.updatedAt()).isEqualTo(comment.getUpdatedAt());
+ }
+
+ @Test
+ void from_shouldMapAuthorNameFromUserEntity() {
+ CommentResponse response = CommentResponse.from(comment);
+
+ assertThat(response.authorName()).isEqualTo(author.getName());
+ }
+
+ // --- Sad paths ---
+
+ @Test
+ void from_shouldThrowWhenAuthorIsNull() {
+ comment.setAuthor(null);
+
+ assertThatThrownBy(() -> CommentResponse.from(comment))
+ .isInstanceOf(NullPointerException.class);
+ }
+
+ @Test
+ void from_shouldThrowWhenMedicalRecordIsNull() {
+ comment.setMedicalRecord(null);
+
+ assertThatThrownBy(() -> CommentResponse.from(comment))
+ .isInstanceOf(NullPointerException.class);
+ }
+
+ // --- Helpers ---
+
+ private static void setPrivateField(Object target, String fieldName, Object value) throws Exception {
+ Field field = target.getClass().getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(target, value);
+ }
+
+ private static void callProtectedMethod(Object target, String methodName) throws Exception {
+ Method method = target.getClass().getDeclaredMethod(methodName);
+ method.setAccessible(true);
+ method.invoke(target);
+ }
+}
diff --git a/src/test/java/org/example/vet1177/entities/CommentEntityTest.java b/src/test/java/org/example/vet1177/entities/CommentEntityTest.java
new file mode 100644
index 00000000..603f7ea8
--- /dev/null
+++ b/src/test/java/org/example/vet1177/entities/CommentEntityTest.java
@@ -0,0 +1,111 @@
+package org.example.vet1177.entities;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.Instant;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CommentEntityTest {
+
+ private Comment comment;
+ private User author;
+ private MedicalRecord medicalRecord;
+
+ @BeforeEach
+ void setUp() {
+ comment = new Comment();
+ author = new User("Dr. Elin Svensson", "elin@vet.se", "hash123", Role.VET);
+ medicalRecord = new MedicalRecord();
+ }
+
+ // --- Getters & setters ---
+
+ @Test
+ void setBody_shouldStoreAndReturnCorrectValue() {
+ comment.setBody("Patienten visar tecken på förbättring.");
+
+ assertThat(comment.getBody()).isEqualTo("Patienten visar tecken på förbättring.");
+ }
+
+ @Test
+ void setAuthor_shouldStoreAndReturnCorrectUser() {
+ comment.setAuthor(author);
+
+ assertThat(comment.getAuthor()).isSameAs(author);
+ }
+
+ @Test
+ void setMedicalRecord_shouldStoreAndReturnCorrectRecord() {
+ comment.setMedicalRecord(medicalRecord);
+
+ assertThat(comment.getMedicalRecord()).isSameAs(medicalRecord);
+ }
+
+ // --- Lifecycle: onCreate ---
+
+ @Test
+ void onCreate_shouldSetBothTimestampsToNonNull() {
+ comment.onCreate();
+
+ assertThat(comment.getCreatedAt()).isNotNull();
+ assertThat(comment.getUpdatedAt()).isNotNull();
+ }
+
+
+ // --- Lifecycle: onUpdate ---
+
+ @Test
+ void onUpdate_shouldRefreshUpdatedAtWithoutModifyingCreatedAt() {
+ comment.onCreate();
+ Instant createdAt = comment.getCreatedAt();
+
+ comment.onUpdate();
+
+ assertThat(comment.getUpdatedAt()).isNotNull();
+ assertThat(comment.getCreatedAt()).isEqualTo(createdAt);
+ }
+
+ @Test
+ void onUpdate_shouldNotModifyCreatedAt() {
+ comment.onCreate();
+ Instant originalCreatedAt = comment.getCreatedAt();
+
+ comment.onUpdate();
+
+ assertThat(comment.getCreatedAt()).isEqualTo(originalCreatedAt);
+ }
+
+ // --- Sad paths: standardvärden innan persist ---
+
+ @Test
+ void getId_shouldBeNullBeforePersist() {
+ assertThat(comment.getId()).isNull();
+ }
+
+ @Test
+ void getBody_shouldBeNullWhenNotSet() {
+ assertThat(comment.getBody()).isNull();
+ }
+
+ @Test
+ void getAuthor_shouldBeNullWhenNotSet() {
+ assertThat(comment.getAuthor()).isNull();
+ }
+
+ @Test
+ void getMedicalRecord_shouldBeNullWhenNotSet() {
+ assertThat(comment.getMedicalRecord()).isNull();
+ }
+
+ @Test
+ void getCreatedAt_shouldBeNullBeforeOnCreate() {
+ assertThat(comment.getCreatedAt()).isNull();
+ }
+
+ @Test
+ void getUpdatedAt_shouldBeNullBeforeOnCreate() {
+ assertThat(comment.getUpdatedAt()).isNull();
+ }
+}