From 0f00fc60016403def9ee1f35447016f8c36b5d54 Mon Sep 17 00:00:00 2001 From: Hyunjoon <104907396+joonjester@users.noreply.github.com> Date: Fri, 29 Aug 2025 19:55:38 +0000 Subject: [PATCH 01/11] [ASE-215] get exams from lecturer (#1) * feat: implemented the Users * feat: Implemented the Exam Entity and it's dto * feat: Implemented the Basic Controller and Service * feat: DummyData for development * fix: removed semester from Exam Entity * fix: Added Exceptions and removed semester for Exams and Dto * feat: Added controller test and service test * refactor: changed the count to submissions * fix: changed Indentation * refactor: moved files and adjusted exception * fix: linting * fix: formatting * feat: Added Mocks * fix: fixed Attributes * fix: lint * fix: linting and adjusted the checkstyle * fix: linting * fix: magic number * fix:import layout in editor config * fix: added newline and changed controller and service. * fix: go rid of jakarta * fix: change controller and service and imports * fix: imports and config for imports * fix: got rid of the mapping and added uuid. --- .editorconfig | 3 +- checkstyle.xml | 6 +- .../Application.java | 2 +- .../components/.gitkeep | 0 .../config/.gitkeep | 0 .../controllers}/.gitkeep | 0 .../controllers/BaseController.java | 5 + .../controllers/LecturerController.java | 34 +++++ .../controllers/RootController.java | 10 +- .../dtos}/.gitkeep | 0 .../com/ase/lecturerservice/dtos/ExamDto.java | 28 ++++ .../entities}/.gitkeep | 0 .../ase/lecturerservice/entities/Exam.java | 51 +++++++ .../entities/user/Lecturer.java | 15 ++ .../entities/user/Student.java | 14 ++ .../entities/user/UserEntity.java | 31 ++++ .../entities/user/UserType.java | 6 + .../ase/lecturerservice/exception/.gitkeep | 0 .../exception/CustomExceptionHandler.java | 22 +++ .../mockvalues/MockValues.java | 27 ++++ .../ase/lecturerservice/repositories/.gitkeep | 0 .../com/ase/lecturerservice/services/.gitkeep | 0 .../lecturerservice/services/DummyData.java | 104 ++++++++++++++ .../services/LecturerService.java | 40 ++++++ .../ApplicationTests.java | 8 +- .../com/ase/lecturerservice/MockValues.java | 27 ++++ .../controllers/LecturerControllerTest.java | 74 ++++++++++ .../services/LecturerServiceTest.java | 134 ++++++++++++++++++ 28 files changed, 626 insertions(+), 15 deletions(-) rename src/main/java/com/ase/{userservice => lecturerservice}/Application.java (91%) rename src/main/java/com/ase/{userservice => lecturerservice}/components/.gitkeep (100%) rename src/main/java/com/ase/{userservice => lecturerservice}/config/.gitkeep (100%) rename src/main/java/com/ase/{userservice/entities => lecturerservice/controllers}/.gitkeep (100%) create mode 100644 src/main/java/com/ase/lecturerservice/controllers/BaseController.java create mode 100644 src/main/java/com/ase/lecturerservice/controllers/LecturerController.java rename src/main/java/com/ase/{userservice => lecturerservice}/controllers/RootController.java (57%) rename src/main/java/com/ase/{userservice/repositories => lecturerservice/dtos}/.gitkeep (100%) create mode 100644 src/main/java/com/ase/lecturerservice/dtos/ExamDto.java rename src/main/java/com/ase/{userservice/services => lecturerservice/entities}/.gitkeep (100%) create mode 100644 src/main/java/com/ase/lecturerservice/entities/Exam.java create mode 100644 src/main/java/com/ase/lecturerservice/entities/user/Lecturer.java create mode 100644 src/main/java/com/ase/lecturerservice/entities/user/Student.java create mode 100644 src/main/java/com/ase/lecturerservice/entities/user/UserEntity.java create mode 100644 src/main/java/com/ase/lecturerservice/entities/user/UserType.java create mode 100644 src/main/java/com/ase/lecturerservice/exception/.gitkeep create mode 100644 src/main/java/com/ase/lecturerservice/exception/CustomExceptionHandler.java create mode 100644 src/main/java/com/ase/lecturerservice/mockvalues/MockValues.java create mode 100644 src/main/java/com/ase/lecturerservice/repositories/.gitkeep create mode 100644 src/main/java/com/ase/lecturerservice/services/.gitkeep create mode 100644 src/main/java/com/ase/lecturerservice/services/DummyData.java create mode 100644 src/main/java/com/ase/lecturerservice/services/LecturerService.java rename src/test/java/com/ase/{userservice => lecturerservice}/ApplicationTests.java (67%) create mode 100644 src/test/java/com/ase/lecturerservice/MockValues.java create mode 100644 src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java create mode 100644 src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java diff --git a/.editorconfig b/.editorconfig index 01b5b05b..ce472594 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,8 +16,7 @@ ij_java_continuation_indent_size = 4 ij_java_class_count_to_use_import_on_demand = 999 ij_java_names_count_to_use_import_on_demand = 999 # Import order to match Checkstyle groups: java, javax, org, com -ij_java_imports_layout = import java.*; import javax.*; import org.*; import com.*; blank_line; import all other imports; blank_line; import static all other static imports +ij_java_imports_layout = $*,java.**,javax.**,org.**,com.**,* [*.{yml,yaml}] indent_size = 2 - diff --git a/checkstyle.xml b/checkstyle.xml index 0d7ee386..89298d65 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -73,7 +73,7 @@ - + @@ -143,9 +143,9 @@ - + - + diff --git a/src/main/java/com/ase/userservice/Application.java b/src/main/java/com/ase/lecturerservice/Application.java similarity index 91% rename from src/main/java/com/ase/userservice/Application.java rename to src/main/java/com/ase/lecturerservice/Application.java index fecb641b..2bfb7667 100644 --- a/src/main/java/com/ase/userservice/Application.java +++ b/src/main/java/com/ase/lecturerservice/Application.java @@ -1,4 +1,4 @@ -package com.ase.userservice; +package com.ase.lecturerservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/com/ase/userservice/components/.gitkeep b/src/main/java/com/ase/lecturerservice/components/.gitkeep similarity index 100% rename from src/main/java/com/ase/userservice/components/.gitkeep rename to src/main/java/com/ase/lecturerservice/components/.gitkeep diff --git a/src/main/java/com/ase/userservice/config/.gitkeep b/src/main/java/com/ase/lecturerservice/config/.gitkeep similarity index 100% rename from src/main/java/com/ase/userservice/config/.gitkeep rename to src/main/java/com/ase/lecturerservice/config/.gitkeep diff --git a/src/main/java/com/ase/userservice/entities/.gitkeep b/src/main/java/com/ase/lecturerservice/controllers/.gitkeep similarity index 100% rename from src/main/java/com/ase/userservice/entities/.gitkeep rename to src/main/java/com/ase/lecturerservice/controllers/.gitkeep diff --git a/src/main/java/com/ase/lecturerservice/controllers/BaseController.java b/src/main/java/com/ase/lecturerservice/controllers/BaseController.java new file mode 100644 index 00000000..ca0e250e --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/controllers/BaseController.java @@ -0,0 +1,5 @@ +package com.ase.lecturerservice.controllers; + +public interface BaseController { + String BASE_PATH = "/api/v1"; +} diff --git a/src/main/java/com/ase/lecturerservice/controllers/LecturerController.java b/src/main/java/com/ase/lecturerservice/controllers/LecturerController.java new file mode 100644 index 00000000..1df0aabc --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/controllers/LecturerController.java @@ -0,0 +1,34 @@ +package com.ase.lecturerservice.controllers; + +import static com.ase.lecturerservice.controllers.BaseController.BASE_PATH; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import com.ase.lecturerservice.dtos.ExamDto; +import com.ase.lecturerservice.entities.Exam; +import com.ase.lecturerservice.services.LecturerService; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping(BASE_PATH + "/lecturer") +@RequiredArgsConstructor +public class LecturerController { + + private final LecturerService lecturerService; + + @GetMapping("/exams") + public ResponseEntity> getExams(@RequestParam String lecturer) + throws IllegalArgumentException { + List exams = lecturerService.getExamsByLecturer(lecturer); + + List examDtoList = exams.stream() + .map(lecturerService::convertToExamDto) + .collect(Collectors.toList()); + + return ResponseEntity.ok(examDtoList); + } +} diff --git a/src/main/java/com/ase/userservice/controllers/RootController.java b/src/main/java/com/ase/lecturerservice/controllers/RootController.java similarity index 57% rename from src/main/java/com/ase/userservice/controllers/RootController.java rename to src/main/java/com/ase/lecturerservice/controllers/RootController.java index 34e21cb6..db99f155 100644 --- a/src/main/java/com/ase/userservice/controllers/RootController.java +++ b/src/main/java/com/ase/lecturerservice/controllers/RootController.java @@ -1,4 +1,4 @@ -package com.ase.userservice.controllers; +package com.ase.lecturerservice.controllers; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -7,9 +7,9 @@ @RestController public class RootController { - @GetMapping("/") - public ResponseEntity root() { - return ResponseEntity.ok("API Root: /api/v1/"); - } + @GetMapping("/") + public ResponseEntity root() { + return ResponseEntity.ok("API Root: /api/v1/"); + } } diff --git a/src/main/java/com/ase/userservice/repositories/.gitkeep b/src/main/java/com/ase/lecturerservice/dtos/.gitkeep similarity index 100% rename from src/main/java/com/ase/userservice/repositories/.gitkeep rename to src/main/java/com/ase/lecturerservice/dtos/.gitkeep diff --git a/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java b/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java new file mode 100644 index 00000000..e3494680 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java @@ -0,0 +1,28 @@ +package com.ase.lecturerservice.dtos; + +import java.time.LocalDate; +import java.util.UUID; +import com.sun.istack.NotNull; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ExamDto { + @NotNull + private UUID uuid; + + @NotNull + private String name; + + @NotNull + private LocalDate date; + + @NotNull + private String module; + + @NotNull + private int time; + + private int submissionsCount; +} diff --git a/src/main/java/com/ase/userservice/services/.gitkeep b/src/main/java/com/ase/lecturerservice/entities/.gitkeep similarity index 100% rename from src/main/java/com/ase/userservice/services/.gitkeep rename to src/main/java/com/ase/lecturerservice/entities/.gitkeep diff --git a/src/main/java/com/ase/lecturerservice/entities/Exam.java b/src/main/java/com/ase/lecturerservice/entities/Exam.java new file mode 100644 index 00000000..f391f570 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/entities/Exam.java @@ -0,0 +1,51 @@ +package com.ase.lecturerservice.entities; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; +import com.ase.lecturerservice.entities.user.Lecturer; +import com.ase.lecturerservice.entities.user.Student; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Exam { + protected UUID uuid; + + protected String name; + + protected int grade; + + protected int averageGrade; + + protected int totalPoints; + + protected int achievedPoints; + + protected String examType; + + protected LocalDate date; + + protected int time; + + protected String allowedResources; + + protected int attempt; + + protected int etcs; + + protected String room; + + protected String module; + + protected Lecturer lecturer; + + protected List assignedStudents; +} diff --git a/src/main/java/com/ase/lecturerservice/entities/user/Lecturer.java b/src/main/java/com/ase/lecturerservice/entities/user/Lecturer.java new file mode 100644 index 00000000..bdfcdd48 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/entities/user/Lecturer.java @@ -0,0 +1,15 @@ +package com.ase.lecturerservice.entities.user; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +@Getter +@Setter +@NoArgsConstructor +@SuperBuilder +public class Lecturer extends UserEntity { + + protected final UserType type = UserType.LECTURER; +} diff --git a/src/main/java/com/ase/lecturerservice/entities/user/Student.java b/src/main/java/com/ase/lecturerservice/entities/user/Student.java new file mode 100644 index 00000000..83142b83 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/entities/user/Student.java @@ -0,0 +1,14 @@ +package com.ase.lecturerservice.entities.user; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +@Getter +@Setter +@NoArgsConstructor +@SuperBuilder +public class Student extends UserEntity { + protected final UserType type = UserType.STUDENT; +} diff --git a/src/main/java/com/ase/lecturerservice/entities/user/UserEntity.java b/src/main/java/com/ase/lecturerservice/entities/user/UserEntity.java new file mode 100644 index 00000000..c697090c --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/entities/user/UserEntity.java @@ -0,0 +1,31 @@ +package com.ase.lecturerservice.entities.user; + +import java.util.UUID; +import org.hibernate.annotations.UuidGenerator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public abstract class UserEntity { + protected String email; + + @UuidGenerator + private UUID id; + + @JsonProperty("first_name") + private String firstName; + + @JsonProperty("last_name") + private String lastName; + + @JsonProperty("type") + private UserType type; +} diff --git a/src/main/java/com/ase/lecturerservice/entities/user/UserType.java b/src/main/java/com/ase/lecturerservice/entities/user/UserType.java new file mode 100644 index 00000000..ead31a66 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/entities/user/UserType.java @@ -0,0 +1,6 @@ +package com.ase.lecturerservice.entities.user; + +public enum UserType { + LECTURER, + STUDENT +} diff --git a/src/main/java/com/ase/lecturerservice/exception/.gitkeep b/src/main/java/com/ase/lecturerservice/exception/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/ase/lecturerservice/exception/CustomExceptionHandler.java b/src/main/java/com/ase/lecturerservice/exception/CustomExceptionHandler.java new file mode 100644 index 00000000..713ec38e --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/exception/CustomExceptionHandler.java @@ -0,0 +1,22 @@ +package com.ase.lecturerservice.exception; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@ControllerAdvice +public class CustomExceptionHandler { + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException( + IllegalArgumentException illegalArgumentException) { + return ResponseEntity.badRequest().body(illegalArgumentException.getMessage()); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity internalErrorHandler(Exception exception) { + log.error(exception.getMessage()); + return ResponseEntity.internalServerError().body("Internal Server Error"); + } +} diff --git a/src/main/java/com/ase/lecturerservice/mockvalues/MockValues.java b/src/main/java/com/ase/lecturerservice/mockvalues/MockValues.java new file mode 100644 index 00000000..6e30172d --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/mockvalues/MockValues.java @@ -0,0 +1,27 @@ +package com.ase.lecturerservice.mockvalues; + +import lombok.Getter; + +@Getter +public enum MockValues { + GRADE(1), + SUBMISSIONS(1), + AVERAGE_GRADE(2), + TOTAL_POINTS(100), + ACHIEVED_POINTS(95), + TIME_SECONDS(5400), + TIME_MIN(90), + ATTEMPT(1), + ETCS(5), + DATE_YEAR(2015), + DATE_MONTH(10), + DATE_DAY(12); + + private final int value; + + MockValues(int value) { + this.value = value; + } + +} + diff --git a/src/main/java/com/ase/lecturerservice/repositories/.gitkeep b/src/main/java/com/ase/lecturerservice/repositories/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/ase/lecturerservice/services/.gitkeep b/src/main/java/com/ase/lecturerservice/services/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/ase/lecturerservice/services/DummyData.java b/src/main/java/com/ase/lecturerservice/services/DummyData.java new file mode 100644 index 00000000..04ff5070 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/services/DummyData.java @@ -0,0 +1,104 @@ +package com.ase.lecturerservice.services; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; +import com.ase.lecturerservice.entities.Exam; +import com.ase.lecturerservice.entities.user.Lecturer; +import com.ase.lecturerservice.mockvalues.MockValues; + +public class DummyData { + static LocalDate date = LocalDate.of( + MockValues.DATE_YEAR.getValue(), + MockValues.DATE_MONTH.getValue(), + MockValues.DATE_DAY.getValue()); + + public static List EXAMS = List.of( + Exam.builder() + .uuid(UUID.randomUUID()) + .name("Mathematics Final Exam") + .grade(MockValues.GRADE.getValue()) + .averageGrade(MockValues.AVERAGE_GRADE.getValue()) + .totalPoints(MockValues.TOTAL_POINTS.getValue()) + .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) + .examType("Written") + .date(date) + .time(MockValues.TIME_SECONDS.getValue()) + .allowedResources("Calculator, Formula Sheet") + .attempt(MockValues.ATTEMPT.getValue()) + .etcs(MockValues.ETCS.getValue()) + .room("Room A101") + .lecturer(new Lecturer()) + .module("Mathe") + .build(), + Exam.builder() + .uuid(UUID.randomUUID()) + .name("Physics Midterm") + .grade(MockValues.GRADE.getValue()) + .averageGrade(MockValues.AVERAGE_GRADE.getValue()) + .totalPoints(MockValues.TOTAL_POINTS.getValue()) + .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) + .examType("Oral") + .date(date) + .time(MockValues.TIME_SECONDS.getValue()) + .allowedResources("None") + .attempt(MockValues.ATTEMPT.getValue()) + .etcs(MockValues.ETCS.getValue()) + .room("Room B202") + .lecturer(new Lecturer()) + .module("Physics") + .build(), + Exam.builder() + .uuid(UUID.randomUUID()) + .name("Computer Science Project") + .grade(MockValues.GRADE.getValue()) + .averageGrade(MockValues.AVERAGE_GRADE.getValue()) + .totalPoints(MockValues.TOTAL_POINTS.getValue()) + .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) + .examType("Project") + .date(date) + .time(MockValues.TIME_SECONDS.getValue()) + .allowedResources("Laptop, IDE") + .attempt(MockValues.ATTEMPT.getValue()) + .etcs(MockValues.ETCS.getValue()) + .room("Online Submission") + .lecturer(new Lecturer()) + .module("CS I") + .build(), + Exam.builder() + .uuid(UUID.randomUUID()) + .name("Chemistry Lab Exam") + .grade(MockValues.GRADE.getValue()) + .averageGrade(MockValues.AVERAGE_GRADE.getValue()) + .totalPoints(MockValues.TOTAL_POINTS.getValue()) + .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) + .examType("Practical") + .date(date) + .time(MockValues.TIME_SECONDS.getValue()) + .allowedResources("Lab Equipment, Safety Manual") + .attempt(MockValues.ATTEMPT.getValue()) + .etcs(MockValues.ETCS.getValue()) + .room("Lab C303") + .room("Online Submission") + .lecturer(new Lecturer()) + .module("Chemistry") + .build(), + Exam.builder() + .uuid(UUID.randomUUID()) + .name("History Essay Exam") + .grade(MockValues.GRADE.getValue()) + .averageGrade(MockValues.AVERAGE_GRADE.getValue()) + .totalPoints(MockValues.TOTAL_POINTS.getValue()) + .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) + .examType("Essay") + .date(date) + .time(MockValues.TIME_SECONDS.getValue()) + .allowedResources("Notes, Textbook") + .attempt(MockValues.ATTEMPT.getValue()) + .etcs(MockValues.ETCS.getValue()) + .room("Room D404") + .lecturer(new Lecturer()) + .module("History I") + .build() + ); +} diff --git a/src/main/java/com/ase/lecturerservice/services/LecturerService.java b/src/main/java/com/ase/lecturerservice/services/LecturerService.java new file mode 100644 index 00000000..2289c211 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/services/LecturerService.java @@ -0,0 +1,40 @@ +package com.ase.lecturerservice.services; + +import java.util.List; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.server.ResponseStatusException; +import com.ase.lecturerservice.dtos.ExamDto; +import com.ase.lecturerservice.entities.Exam; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LecturerService { + private final ObjectMapper objectMapper; + + private static final int SECONDS_PER_MINUTE = 60; + + public List getExamsByLecturer(String lecturer) + throws HttpStatusCodeException { + if (lecturer == null || lecturer.isBlank()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "Lecturer name is required"); + } + + // TODO: change this to a webclient call, when the API is ready + log.info("The Exam from {} has been requested", lecturer); + return DummyData.EXAMS; + } + + public ExamDto convertToExamDto(Exam exam) { + ExamDto examDto = objectMapper.convertValue(exam, ExamDto.class); + examDto.setTime(examDto.getTime() / SECONDS_PER_MINUTE); + + return (examDto); + } +} diff --git a/src/test/java/com/ase/userservice/ApplicationTests.java b/src/test/java/com/ase/lecturerservice/ApplicationTests.java similarity index 67% rename from src/test/java/com/ase/userservice/ApplicationTests.java rename to src/test/java/com/ase/lecturerservice/ApplicationTests.java index be5b4249..688544d4 100644 --- a/src/test/java/com/ase/userservice/ApplicationTests.java +++ b/src/test/java/com/ase/lecturerservice/ApplicationTests.java @@ -1,4 +1,4 @@ -package com.ase.userservice; +package com.ase.lecturerservice; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -6,8 +6,8 @@ @SpringBootTest class ApplicationTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } diff --git a/src/test/java/com/ase/lecturerservice/MockValues.java b/src/test/java/com/ase/lecturerservice/MockValues.java new file mode 100644 index 00000000..05162279 --- /dev/null +++ b/src/test/java/com/ase/lecturerservice/MockValues.java @@ -0,0 +1,27 @@ +package com.ase.lecturerservice; + +import lombok.Getter; + +@Getter +public enum MockValues { + GRADE(1), + SUBMISSIONS(1), + AVERAGE_GRADE(2), + TOTAL_POINTS(100), + ACHIEVED_POINTS(95), + TIME_SECONDS(5400), + TIME_MIN(90), + ATTEMPT(1), + ETCS(5), + DATE_YEAR(2015), + DATE_MONTH(10), + DATE_DAY(12); + + private final int value; + + MockValues(int value) { + this.value = value; + } + +} + diff --git a/src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java b/src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java new file mode 100644 index 00000000..4508f88f --- /dev/null +++ b/src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java @@ -0,0 +1,74 @@ +package com.ase.lecturerservice.controllers; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import com.ase.lecturerservice.MockValues; +import com.ase.lecturerservice.dtos.ExamDto; +import com.ase.lecturerservice.entities.Exam; +import com.ase.lecturerservice.services.LecturerService; + +@WebMvcTest(LecturerController.class) +public class LecturerControllerTest { + + private static ExamDto examDto; + + @Autowired + private MockMvc mockMvc; + @MockitoBean + private LecturerService lecturerService; + + @BeforeAll + public static void setup() { + LocalDate date = LocalDate.of( + MockValues.DATE_YEAR.getValue(), + MockValues.DATE_MONTH.getValue(), + MockValues.DATE_DAY.getValue()); + + examDto = ExamDto.builder() + .name("Test") + .module("Test") + .date(date) + .time(MockValues.TIME_SECONDS.getValue()) + .build(); + } + + @Test + void fetchExamsShouldReturnExamDtos() throws Exception { + Mockito.when(lecturerService.getExamsByLecturer("john")).thenReturn(List.of()); + Mockito.when(lecturerService.convertToExamDto(new Exam())).thenReturn(examDto); + mockMvc.perform(get("/api/v1/lecturer/exams") + .param("lecturer", "john") + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name").value("Test")) + .andExpect(jsonPath("$[0].date").value("2015-10-12")) + .andExpect(jsonPath("$[0].module").value("Test")) + .andExpect(jsonPath("$[0].time").value(MockValues.TIME_SECONDS.getValue())); + } + + @Test + void fetchExamsShouldThrowException() throws Exception { + Mockito.when(lecturerService.getExamsByLecturer("")) + .thenThrow(new IllegalArgumentException("Lecturer cannot be empty")); + + mockMvc.perform(get("/api/v1/lecturer/exams") + .param("lecturer", "") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(content().string("Lecturer cannot be empty")); + } +} diff --git a/src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java b/src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java new file mode 100644 index 00000000..becf4ba6 --- /dev/null +++ b/src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java @@ -0,0 +1,134 @@ +package com.ase.lecturerservice.services; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import com.ase.lecturerservice.MockValues; +import com.ase.lecturerservice.dtos.ExamDto; +import com.ase.lecturerservice.entities.Exam; +import com.ase.lecturerservice.entities.user.Lecturer; +import com.ase.lecturerservice.entities.user.UserType; + +@SpringBootTest +public class LecturerServiceTest { + @Autowired + private LecturerService lecturerService; + + private Lecturer lecturer; + private LocalDate date; + + @BeforeEach + public void setUpLecturer() { + lecturer = Lecturer.builder() + .id(UUID.randomUUID()) + .email("lecturer@example.com") + .type(UserType.LECTURER) + .firstName("John") + .lastName("Doe") + .build(); + + date = LocalDate.of( + MockValues.DATE_YEAR.getValue(), + MockValues.DATE_MONTH.getValue(), + MockValues.DATE_DAY.getValue()); + } + + @Test + void fetchExamsByLecturerShouldGetExams() { + UUID uuid = UUID.randomUUID(); + DummyData.EXAMS = List.of(Exam.builder() + .uuid(uuid) + .name("Mathematics Final Exam") + .grade(MockValues.GRADE.getValue()) + .averageGrade(MockValues.AVERAGE_GRADE.getValue()) + .totalPoints(MockValues.TOTAL_POINTS.getValue()) + .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) + .examType("Written") + .date(date) + .time(MockValues.TIME_SECONDS.getValue()) + .allowedResources("Calculator, Formula Sheet") + .attempt(MockValues.ATTEMPT.getValue()) + .etcs(MockValues.ETCS.getValue()) + .room("Room A101") + .lecturer(lecturer) + .module("Mathe") + .build()); + + List exams = lecturerService.getExamsByLecturer("Test"); + Exam exam = exams.getFirst(); + + Assertions.assertThat(exams).isNotEmpty(); + Assertions.assertThat(exam.getUuid()) + .isEqualTo(uuid); + Assertions.assertThat(exam.getName()) + .isEqualTo("Mathematics Final Exam"); + Assertions.assertThat(exam.getGrade()) + .isEqualTo(MockValues.GRADE.getValue()); + Assertions.assertThat(exam.getAverageGrade()) + .isEqualTo(MockValues.AVERAGE_GRADE.getValue()); + Assertions.assertThat(exam.getTotalPoints()) + .isEqualTo(MockValues.TOTAL_POINTS.getValue()); + Assertions.assertThat(exam.getAchievedPoints()) + .isEqualTo(MockValues.ACHIEVED_POINTS.getValue()); + Assertions.assertThat(exam.getExamType()).isEqualTo("Written"); + Assertions.assertThat(exam.getDate()).isEqualTo(date); + Assertions.assertThat(exam.getTime()) + .isEqualTo(MockValues.TIME_SECONDS.getValue()); + Assertions.assertThat(exam.getAllowedResources()) + .isEqualTo("Calculator, Formula Sheet"); + Assertions.assertThat(exam.getAttempt()) + .isEqualTo(MockValues.ATTEMPT.getValue()); + Assertions.assertThat(exam.getEtcs()) + .isEqualTo(MockValues.ETCS.getValue()); + Assertions.assertThat(exam.getRoom()).isEqualTo("Room A101"); + Assertions.assertThat(exam.getLecturer()).isEqualTo(lecturer); + Assertions.assertThat(exam.getModule()).isEqualTo("Mathe"); + } + + @Test + void fetchExamsByLecturerShouldNotGetExams() { + DummyData.EXAMS = List.of(); + + List exams = lecturerService.getExamsByLecturer("Test"); + + Assertions.assertThat(exams).isEmpty(); + } + + @Test + void convertToExamDtoShouldConvertExamsToDto() { + Exam exam = Exam.builder() + .name("Mathematics Final Exam") + .grade(MockValues.GRADE.getValue()) + .averageGrade(MockValues.AVERAGE_GRADE.getValue()) + .totalPoints(MockValues.TOTAL_POINTS.getValue()) + .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) + .examType("Written") + .date(date) + .time(MockValues.TIME_SECONDS.getValue()) + .allowedResources("Calculator, Formula Sheet") + .attempt(MockValues.ATTEMPT.getValue()) + .etcs(MockValues.ETCS.getValue()) + .room("Room A101") + .lecturer(lecturer) + .module("Mathe") + .build(); + + ExamDto examDto = lecturerService.convertToExamDto(exam); + + Assertions.assertThat(examDto.getName()). + isEqualTo("Mathematics Final Exam"); + Assertions.assertThat(examDto.getModule()). + isEqualTo("Mathe"); + Assertions.assertThat(examDto.getDate()). + isEqualTo(date); + Assertions.assertThat(examDto.getTime()). + isEqualTo(MockValues.TIME_MIN.getValue()); + Assertions.assertThat(examDto.getSubmissionsCount()). + isEqualTo(MockValues.SUBMISSIONS.getValue()); + } +} From d23fe13613c64dbe0f4e3ee85918236c41a0165a Mon Sep 17 00:00:00 2001 From: Janne Date: Sat, 30 Aug 2025 01:07:20 +0200 Subject: [PATCH 02/11] add deployment --- .github/workflows/deploy-application.yaml | 20 ++++ dockerfile | 8 ++ k8s/deployment.yaml | 108 ++++++++++++++++++++++ k8s/ingress.yaml | 23 +++++ k8s/kustomization.yaml | 7 ++ k8s/service.yaml | 12 +++ 6 files changed, 178 insertions(+) create mode 100644 .github/workflows/deploy-application.yaml create mode 100644 dockerfile create mode 100644 k8s/deployment.yaml create mode 100644 k8s/ingress.yaml create mode 100644 k8s/kustomization.yaml create mode 100644 k8s/service.yaml diff --git a/.github/workflows/deploy-application.yaml b/.github/workflows/deploy-application.yaml new file mode 100644 index 00000000..7c410bec --- /dev/null +++ b/.github/workflows/deploy-application.yaml @@ -0,0 +1,20 @@ +name: deploy-to-k8s + +on: + push: + +jobs: + test-action: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v5 + + - name: Upload image + uses: Agile-Software-Engineering-25/build-and-publish-image@v1 + + - name: Deploy to Namespace + uses: Agile-Software-Engineering-25/deploy-to-k8s@v1 + with: + kubeconfig: ${{ secrets.KUBECONFIG }} + namespace: ${{ variables.KUBECONFIG }} diff --git a/dockerfile b/dockerfile new file mode 100644 index 00000000..bd904579 --- /dev/null +++ b/dockerfile @@ -0,0 +1,8 @@ +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app +# Copy fat jar +COPY build/libs/*.jar app.jar +# Run as non-root (matches k8s securityContext) +USER 1000:1000 +EXPOSE 8080 +ENTRYPOINT ["java","-jar","/app/app.jar"] \ No newline at end of file diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 00000000..0d9e127c --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,108 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grading-service + namespace: ase-12 + labels: + app.kubernetes.io/name: grading-service + app.kubernetes.io/instance: grading-service +spec: + replicas: 2 + revisionHistoryLimit: 2 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + app.kubernetes.io/name: grading-service + app.kubernetes.io/instance: grading-service + template: + metadata: + labels: + app.kubernetes.io/name: grading-service + app.kubernetes.io/instance: grading-service + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 30 + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + imagePullSecrets: + - name: # e.g. ghrc-secret for GHCR + containers: + - name: app + image: ghcr.io/agile-software-engineering-25/grading-service:latest + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 8080 + env: + - name: SERVER_SERVLET_CONTEXT_PATH + value: "/grading-service" # App will live under / + + # Tell Spring to respect X-Forwarded-* from Traefik + - name: SERVER_FORWARD_HEADERS_STRATEGY + value: "framework" + + # Reasonable JVM sizing for containers (tune per app) + - name: JAVA_TOOL_OPTIONS + value: "-XX:MaxRAMPercentage=75 -XX:InitialRAMPercentage=50 -Djava.security.egd=file:/dev/./urandom" + + # Expose only necessary actuator endpoints; prometheus requires micrometer-registry-prometheus + - name: MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE + value: "health,prometheus" + - name: MANAGEMENT_ENDPOINT_HEALTH_PROBES_ENABLED + value: "true" # Adds /actuator/health/liveness & /readiness + + # Enable graceful shutdown at app level (align with terminationGracePeriodSeconds) + - name: SERVER_SHUTDOWN + value: "graceful" + + readinessProbe: + httpGet: + path: /ase-12/actuator/health/readiness + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 6 + timeoutSeconds: 1 + livenessProbe: + httpGet: + path: /ase-12/actuator/health/liveness + port: 8080 + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 3 + timeoutSeconds: 1 + startupProbe: + httpGet: + path: /ase-12/actuator/health + port: 8080 + periodSeconds: 5 + failureThreshold: 30 + + resources: + requests: + cpu: "250m" + memory: "512Mi" + limits: + cpu: "1" + memory: "1024Mi" + + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true # Immutable root FS → mount /tmp as writable + capabilities: + drop: ["ALL"] + + volumeMounts: + - name: tmp + mountPath: /tmp + + volumes: + - name: tmp + emptyDir: {} diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 00000000..bf118611 --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: grading-service + namespace: ase-12 + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure # send traffic over HTTPS +spec: + ingressClassName: traefik + tls: + - hosts: ["sau-portal.de"] + secretName: tls-to-come + rules: + - host: sau-portal.de + http: + paths: + - path: /ex-grad/grading-service # Public URL prefix for this service + pathType: Prefix + backend: + service: + name: grading-service + port: + number: 80 diff --git a/k8s/kustomization.yaml b/k8s/kustomization.yaml new file mode 100644 index 00000000..a67b402e --- /dev/null +++ b/k8s/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: ase-12 #e.g ase-1 +resources: + - deployment.yaml + - service.yaml + - ingress.yaml diff --git a/k8s/service.yaml b/k8s/service.yaml new file mode 100644 index 00000000..45b819cf --- /dev/null +++ b/k8s/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: grading-service + namespace: ase-12 +spec: + selector: + app.kubernetes.io/name: grading-service # Must match Deployment template labels + ports: + - name: http + port: 80 + targetPort: 8080 From 39a720293b2f3996930eed7cf4f9a2993aedbb9d Mon Sep 17 00:00:00 2001 From: Janne Date: Sat, 30 Aug 2025 01:10:57 +0200 Subject: [PATCH 03/11] fix deploy --- .github/workflows/deploy-application.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-application.yaml b/.github/workflows/deploy-application.yaml index 7c410bec..62a86cf6 100644 --- a/.github/workflows/deploy-application.yaml +++ b/.github/workflows/deploy-application.yaml @@ -2,9 +2,13 @@ name: deploy-to-k8s on: push: + branches: + - main + - deployment + workflow_dispatch: jobs: - test-action: + deploy: runs-on: ubuntu-latest steps: - name: Checkout repo @@ -17,4 +21,4 @@ jobs: uses: Agile-Software-Engineering-25/deploy-to-k8s@v1 with: kubeconfig: ${{ secrets.KUBECONFIG }} - namespace: ${{ variables.KUBECONFIG }} + namespace: ${{ variables.NAMESPACE }} From b49f98881c13762960e258c0da71f25e4d83b684 Mon Sep 17 00:00:00 2001 From: Janne Date: Sat, 30 Aug 2025 01:13:22 +0200 Subject: [PATCH 04/11] change to run on every branch --- .github/workflows/deploy-application.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy-application.yaml b/.github/workflows/deploy-application.yaml index 62a86cf6..5cfb705e 100644 --- a/.github/workflows/deploy-application.yaml +++ b/.github/workflows/deploy-application.yaml @@ -3,8 +3,7 @@ name: deploy-to-k8s on: push: branches: - - main - - deployment + - "**" workflow_dispatch: jobs: From 3eddd9d4b167257f5b4fc7d5b870acdd52b7e785 Mon Sep 17 00:00:00 2001 From: Janne Date: Sat, 30 Aug 2025 01:14:41 +0200 Subject: [PATCH 05/11] change from variables to secrets --- .github/workflows/deploy-application.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-application.yaml b/.github/workflows/deploy-application.yaml index 5cfb705e..43ccd02e 100644 --- a/.github/workflows/deploy-application.yaml +++ b/.github/workflows/deploy-application.yaml @@ -14,10 +14,10 @@ jobs: uses: actions/checkout@v5 - name: Upload image - uses: Agile-Software-Engineering-25/build-and-publish-image@v1 + uses: Agile-Software-Engineering-25/build-and-publish-image@v1.0.0 - name: Deploy to Namespace - uses: Agile-Software-Engineering-25/deploy-to-k8s@v1 + uses: Agile-Software-Engineering-25/deploy-to-k8s@v1.0.0 with: kubeconfig: ${{ secrets.KUBECONFIG }} - namespace: ${{ variables.NAMESPACE }} + namespace: ${{ secrets.NAMESPACE }} From 76688f71368632ba8edd16a46f8644a4102d2ef0 Mon Sep 17 00:00:00 2001 From: janne6565 Date: Sat, 30 Aug 2025 15:18:31 +0200 Subject: [PATCH 06/11] fix tests --- .github/workflows/deploy-application.yaml | 13 +++- .github/workflows/test.yml | 65 +++++++++++++++++++ .gitignore | 4 +- Dockerfile | 22 +++++++ dockerfile | 8 --- k8s/deployment.yaml | 37 ++++------- pom.xml | 25 +++++-- .../controllers/LecturerController.java | 3 +- .../com/ase/lecturerservice/dtos/ExamDto.java | 11 ++-- .../exception/CustomExceptionHandler.java | 11 +++- src/main/resources/application.yaml | 10 +++ .../controllers/LecturerControllerTest.java | 24 +++++-- .../services/LecturerServiceTest.java | 3 +- 13 files changed, 178 insertions(+), 58 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 Dockerfile delete mode 100644 dockerfile diff --git a/.github/workflows/deploy-application.yaml b/.github/workflows/deploy-application.yaml index 43ccd02e..f247b1cf 100644 --- a/.github/workflows/deploy-application.yaml +++ b/.github/workflows/deploy-application.yaml @@ -13,11 +13,18 @@ jobs: - name: Checkout repo uses: actions/checkout@v5 - - name: Upload image + - name: Build & Publish Docker image + id: build uses: Agile-Software-Engineering-25/build-and-publish-image@v1.0.0 + with: + # optional: set extra tags if needed + extra_tags: ${{ github.sha }} - name: Deploy to Namespace - uses: Agile-Software-Engineering-25/deploy-to-k8s@v1.0.0 + uses: Agile-Software-Engineering-25/deploy-to-k8s@feature/make-image-tag-adjustable with: kubeconfig: ${{ secrets.KUBECONFIG }} - namespace: ${{ secrets.NAMESPACE }} + namespace: ${{ vars.K8S_NAMESPACE }} + deployment: grading-service + image: ghcr.io/agile-software-engineering-25/team-12-backend-exagrad-lecturer-service + tag: ${{ github.sha }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..98af8c8a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,65 @@ +name: CI - Test + +on: + push: + branches: + - '**' + pull_request: + branches: + - '**' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-test-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Test (Java ${{ matrix.java }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + java: [ '21', '22' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Temurin JDK ${{ matrix.java }} + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: ${{ matrix.java }} + cache: maven + + - name: Show Java and Maven versions + run: | + java -version + mvn -v + + - name: Build and run tests + run: mvn -B -ntp clean verify + + - name: Upload test reports (Surefire) + if: always() + uses: actions/upload-artifact@v4 + with: + name: surefire-reports-java-${{ matrix.java }} + path: | + **/target/surefire-reports/** + if-no-files-found: ignore + retention-days: 14 + + - name: Upload integration test reports (Failsafe) + if: always() + uses: actions/upload-artifact@v4 + with: + name: failsafe-reports-java-${{ matrix.java }} + path: | + **/target/failsafe-reports/** + if-no-files-found: ignore + retention-days: 14 diff --git a/.gitignore b/.gitignore index 7a6a576f..76011b95 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,6 @@ build/ ### MacOs ### -*/.DS_STORE \ No newline at end of file +*/.DS_STORE + +/data diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..78dd770e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# Stage 1: build the jar +FROM maven:3.9.9-eclipse-temurin-21-alpine AS builder +WORKDIR /app + +COPY pom.xml . +RUN mvn dependency:go-offline -B + +COPY src src +RUN mvn clean package -DskipTests -B + +# Stage 2: run the jar +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app + +# Copy the fat jar (Spring Boot creates a runnable jar under target/) +COPY --from=builder /app/target/*.jar app.jar +RUN chown -R 1000:1000 /app + +# Run as non-root +USER 1000:1000 +EXPOSE 8080 +ENTRYPOINT ["java","-jar","/app/app.jar"] diff --git a/dockerfile b/dockerfile deleted file mode 100644 index bd904579..00000000 --- a/dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM eclipse-temurin:21-jre-alpine -WORKDIR /app -# Copy fat jar -COPY build/libs/*.jar app.jar -# Run as non-root (matches k8s securityContext) -USER 1000:1000 -EXPOSE 8080 -ENTRYPOINT ["java","-jar","/app/app.jar"] \ No newline at end of file diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 0d9e127c..aeb2cccc 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -31,40 +31,23 @@ spec: runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 - imagePullSecrets: - - name: # e.g. ghrc-secret for GHCR containers: - name: app - image: ghcr.io/agile-software-engineering-25/grading-service:latest + image: ghcr.io/agile-software-engineering-25/team-12-backend-exagrad-lecturer-service:latest imagePullPolicy: IfNotPresent ports: - name: http containerPort: 8080 env: - - name: SERVER_SERVLET_CONTEXT_PATH - value: "/grading-service" # App will live under / - - # Tell Spring to respect X-Forwarded-* from Traefik - - name: SERVER_FORWARD_HEADERS_STRATEGY - value: "framework" - - # Reasonable JVM sizing for containers (tune per app) - - name: JAVA_TOOL_OPTIONS - value: "-XX:MaxRAMPercentage=75 -XX:InitialRAMPercentage=50 -Djava.security.egd=file:/dev/./urandom" - - # Expose only necessary actuator endpoints; prometheus requires micrometer-registry-prometheus - - name: MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE - value: "health,prometheus" - - name: MANAGEMENT_ENDPOINT_HEALTH_PROBES_ENABLED - value: "true" # Adds /actuator/health/liveness & /readiness - # Enable graceful shutdown at app level (align with terminationGracePeriodSeconds) + - name: SERVER_SERVLET_CONTEXT_PATH + value: "/ex-grad/grading-service" - name: SERVER_SHUTDOWN value: "graceful" readinessProbe: httpGet: - path: /ase-12/actuator/health/readiness + path: /ex-grad/grading-service/actuator/health/readiness port: 8080 initialDelaySeconds: 10 periodSeconds: 5 @@ -72,7 +55,7 @@ spec: timeoutSeconds: 1 livenessProbe: httpGet: - path: /ase-12/actuator/health/liveness + path: /ex-grad/grading-service/actuator/health/liveness port: 8080 initialDelaySeconds: 20 periodSeconds: 10 @@ -80,7 +63,7 @@ spec: timeoutSeconds: 1 startupProbe: httpGet: - path: /ase-12/actuator/health + path: /ex-grad/grading-service/actuator/health port: 8080 periodSeconds: 5 failureThreshold: 30 @@ -97,12 +80,16 @@ spec: allowPrivilegeEscalation: false readOnlyRootFilesystem: true # Immutable root FS → mount /tmp as writable capabilities: - drop: ["ALL"] + drop: [ "ALL" ] volumeMounts: + - name: data + mountPath: /app/data - name: tmp mountPath: /tmp volumes: - name: tmp - emptyDir: {} + emptyDir: { } + - name: data + emptyDir: { } diff --git a/pom.xml b/pom.xml index e26b2f8a..e1073c4c 100644 --- a/pom.xml +++ b/pom.xml @@ -31,16 +31,13 @@ - ch.bildspur - artnet4j - 0.6.2 + org.springframework.boot + spring-boot-starter-actuator - org.springframework.boot spring-boot-starter-web - org.projectlombok lombok @@ -65,4 +62,22 @@ runtime + + + + org.springframework.boot + spring-boot-maven-plugin + + com.ase.lecturerservice.Application + + + + + repackage + + + + + + diff --git a/src/main/java/com/ase/lecturerservice/controllers/LecturerController.java b/src/main/java/com/ase/lecturerservice/controllers/LecturerController.java index 1df0aabc..ce106c3f 100644 --- a/src/main/java/com/ase/lecturerservice/controllers/LecturerController.java +++ b/src/main/java/com/ase/lecturerservice/controllers/LecturerController.java @@ -12,7 +12,9 @@ import com.ase.lecturerservice.entities.Exam; import com.ase.lecturerservice.services.LecturerService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @RestController @RequestMapping(BASE_PATH + "/lecturer") @RequiredArgsConstructor @@ -24,7 +26,6 @@ public class LecturerController { public ResponseEntity> getExams(@RequestParam String lecturer) throws IllegalArgumentException { List exams = lecturerService.getExamsByLecturer(lecturer); - List examDtoList = exams.stream() .map(lecturerService::convertToExamDto) .collect(Collectors.toList()); diff --git a/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java b/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java index e3494680..325051c9 100644 --- a/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java +++ b/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java @@ -2,26 +2,25 @@ import java.time.LocalDate; import java.util.UUID; -import com.sun.istack.NotNull; import lombok.Builder; import lombok.Data; +import lombok.NonNull; @Data @Builder public class ExamDto { - @NotNull + @NonNull private UUID uuid; - @NotNull + @NonNull private String name; - @NotNull + @NonNull private LocalDate date; - @NotNull + @NonNull private String module; - @NotNull private int time; private int submissionsCount; diff --git a/src/main/java/com/ase/lecturerservice/exception/CustomExceptionHandler.java b/src/main/java/com/ase/lecturerservice/exception/CustomExceptionHandler.java index 713ec38e..bd82db89 100644 --- a/src/main/java/com/ase/lecturerservice/exception/CustomExceptionHandler.java +++ b/src/main/java/com/ase/lecturerservice/exception/CustomExceptionHandler.java @@ -1,8 +1,10 @@ package com.ase.lecturerservice.exception; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.resource.NoResourceFoundException; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -14,9 +16,16 @@ public ResponseEntity handleIllegalArgumentException( return ResponseEntity.badRequest().body(illegalArgumentException.getMessage()); } + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity handleNoResourceFoundException( + NoResourceFoundException noResourceFoundException + ) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("This page could not be found"); + } + @ExceptionHandler(Exception.class) public ResponseEntity internalErrorHandler(Exception exception) { - log.error(exception.getMessage()); + log.error(exception.getMessage(), exception); return ResponseEntity.internalServerError().body("Internal Server Error"); } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4b5711b9..41d8cfd0 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -17,3 +17,13 @@ spring: server: error: include-message: always +management: + endpoints: + web: + exposure: + include: health,readiness,liveness,prometheus + base-path: /actuator + endpoint: + health: + probes: + enabled: true diff --git a/src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java b/src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java index 4508f88f..202d120b 100644 --- a/src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java +++ b/src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java @@ -7,7 +7,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.time.LocalDate; import java.util.List; -import org.junit.jupiter.api.BeforeAll; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; @@ -19,25 +20,36 @@ import com.ase.lecturerservice.dtos.ExamDto; import com.ase.lecturerservice.entities.Exam; import com.ase.lecturerservice.services.LecturerService; +import lombok.extern.slf4j.Slf4j; +@Slf4j @WebMvcTest(LecturerController.class) public class LecturerControllerTest { - private static ExamDto examDto; + private static ExamDto examDto = ExamDto.builder() + .uuid(UUID.randomUUID()) + .name("Test") + .module("Test") + .date(LocalDate.of(MockValues.DATE_YEAR.getValue(), + MockValues.DATE_MONTH.getValue(), + MockValues.DATE_DAY.getValue())) + .time(MockValues.TIME_SECONDS.getValue()) + .build(); @Autowired private MockMvc mockMvc; @MockitoBean private LecturerService lecturerService; - @BeforeAll - public static void setup() { + @BeforeEach + public void setup() { LocalDate date = LocalDate.of( MockValues.DATE_YEAR.getValue(), MockValues.DATE_MONTH.getValue(), MockValues.DATE_DAY.getValue()); examDto = ExamDto.builder() + .uuid(UUID.randomUUID()) .name("Test") .module("Test") .date(date) @@ -47,8 +59,8 @@ public static void setup() { @Test void fetchExamsShouldReturnExamDtos() throws Exception { - Mockito.when(lecturerService.getExamsByLecturer("john")).thenReturn(List.of()); - Mockito.when(lecturerService.convertToExamDto(new Exam())).thenReturn(examDto); + Mockito.when(lecturerService.getExamsByLecturer("john")).thenReturn(List.of(new Exam())); + Mockito.when(lecturerService.convertToExamDto(Mockito.any())).thenReturn(examDto); mockMvc.perform(get("/api/v1/lecturer/exams") .param("lecturer", "john") .contentType(MediaType.APPLICATION_JSON)) diff --git a/src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java b/src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java index becf4ba6..40d14e63 100644 --- a/src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java +++ b/src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java @@ -102,6 +102,7 @@ void fetchExamsByLecturerShouldNotGetExams() { @Test void convertToExamDtoShouldConvertExamsToDto() { Exam exam = Exam.builder() + .uuid(UUID.randomUUID()) .name("Mathematics Final Exam") .grade(MockValues.GRADE.getValue()) .averageGrade(MockValues.AVERAGE_GRADE.getValue()) @@ -128,7 +129,5 @@ void convertToExamDtoShouldConvertExamsToDto() { isEqualTo(date); Assertions.assertThat(examDto.getTime()). isEqualTo(MockValues.TIME_MIN.getValue()); - Assertions.assertThat(examDto.getSubmissionsCount()). - isEqualTo(MockValues.SUBMISSIONS.getValue()); } } From 203462cf951e0f79b92f6a070a800b97775baa26 Mon Sep 17 00:00:00 2001 From: Janne <60547730+Janne6565@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:05:42 +0200 Subject: [PATCH 07/11] Update src/main/java/com/ase/lecturerservice/services/DummyData.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/com/ase/lecturerservice/services/DummyData.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/ase/lecturerservice/services/DummyData.java b/src/main/java/com/ase/lecturerservice/services/DummyData.java index 04ff5070..6f5ea0a2 100644 --- a/src/main/java/com/ase/lecturerservice/services/DummyData.java +++ b/src/main/java/com/ase/lecturerservice/services/DummyData.java @@ -79,7 +79,6 @@ public class DummyData { .attempt(MockValues.ATTEMPT.getValue()) .etcs(MockValues.ETCS.getValue()) .room("Lab C303") - .room("Online Submission") .lecturer(new Lecturer()) .module("Chemistry") .build(), From 8650d29d5296750c75012a40a176c16809c712b3 Mon Sep 17 00:00:00 2001 From: Janne Date: Sat, 30 Aug 2025 20:14:44 +0200 Subject: [PATCH 08/11] remove old deployment action branch --- .github/workflows/deploy-application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-application.yaml b/.github/workflows/deploy-application.yaml index f247b1cf..fad452c4 100644 --- a/.github/workflows/deploy-application.yaml +++ b/.github/workflows/deploy-application.yaml @@ -21,7 +21,7 @@ jobs: extra_tags: ${{ github.sha }} - name: Deploy to Namespace - uses: Agile-Software-Engineering-25/deploy-to-k8s@feature/make-image-tag-adjustable + uses: Agile-Software-Engineering-25/deploy-to-k8s@v1.0.0 with: kubeconfig: ${{ secrets.KUBECONFIG }} namespace: ${{ vars.K8S_NAMESPACE }} From 3319493a5a8e3a5546918b09180d15e2e54dcb88 Mon Sep 17 00:00:00 2001 From: Janne Date: Sat, 30 Aug 2025 20:15:54 +0200 Subject: [PATCH 09/11] change deployment url --- k8s/deployment.yaml | 8 ++++---- k8s/ingress.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index aeb2cccc..9fe698cb 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -41,13 +41,13 @@ spec: env: # Enable graceful shutdown at app level (align with terminationGracePeriodSeconds) - name: SERVER_SERVLET_CONTEXT_PATH - value: "/ex-grad/grading-service" + value: "/exa-grad/grading-service" - name: SERVER_SHUTDOWN value: "graceful" readinessProbe: httpGet: - path: /ex-grad/grading-service/actuator/health/readiness + path: /exa-grad/grading-service/actuator/health/readiness port: 8080 initialDelaySeconds: 10 periodSeconds: 5 @@ -55,7 +55,7 @@ spec: timeoutSeconds: 1 livenessProbe: httpGet: - path: /ex-grad/grading-service/actuator/health/liveness + path: /exa-grad/grading-service/actuator/health/liveness port: 8080 initialDelaySeconds: 20 periodSeconds: 10 @@ -63,7 +63,7 @@ spec: timeoutSeconds: 1 startupProbe: httpGet: - path: /ex-grad/grading-service/actuator/health + path: /exa-grad/grading-service/actuator/health port: 8080 periodSeconds: 5 failureThreshold: 30 diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml index bf118611..1dfa9064 100644 --- a/k8s/ingress.yaml +++ b/k8s/ingress.yaml @@ -14,7 +14,7 @@ spec: - host: sau-portal.de http: paths: - - path: /ex-grad/grading-service # Public URL prefix for this service + - path: /exa-grad/grading-service # Public URL prefix for this service pathType: Prefix backend: service: From 224f4f80e664e91d7d11c6a29980e743b5ec1aaf Mon Sep 17 00:00:00 2001 From: Janne Date: Sat, 30 Aug 2025 20:18:52 +0200 Subject: [PATCH 10/11] update action tag --- .github/workflows/deploy-application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-application.yaml b/.github/workflows/deploy-application.yaml index fad452c4..fe65cddc 100644 --- a/.github/workflows/deploy-application.yaml +++ b/.github/workflows/deploy-application.yaml @@ -21,7 +21,7 @@ jobs: extra_tags: ${{ github.sha }} - name: Deploy to Namespace - uses: Agile-Software-Engineering-25/deploy-to-k8s@v1.0.0 + uses: Agile-Software-Engineering-25/deploy-to-k8s@v1.0.1 with: kubeconfig: ${{ secrets.KUBECONFIG }} namespace: ${{ vars.K8S_NAMESPACE }} From e03f42f15dcba1b0034cb696aeb211094a2258a5 Mon Sep 17 00:00:00 2001 From: Hyunjoon <104907396+joonjester@users.noreply.github.com> Date: Mon, 15 Sep 2025 09:41:35 +0200 Subject: [PATCH 11/11] [ASE-582] exams without submissions (#2) * implement cors config * refactor: added ExamType * feat: get Grade and Exam Data * fix: simplified the service and controller * test: implemented * fix: Got rid of the gitkeep * fix: Adjusted the mocks to be more clear * fix: testing * fix: changed day of the date * refactor: changed the locations of the funktions * refactor: split lecturer service,controller and test * chore: added more types and clean up * fix: got rid of achievedPoints * fix: added Grade * refactor: renaming * adjust gitignore * implement submissions and feedback retrieval for lecturers * filter exams by lecturerUuid, adjust API endpoints and update test cases * add submission controller and service tests; refactor lecturer field to lecturerUuid in exam entity and update related logic * refactor services and test methods for improved readability; adjust formatting in controller and related classes * fix: linting --------- Co-authored-by: Janne --- .gitignore | 4 +- .../com/ase/lecturerservice/config/.gitkeep | 0 .../lecturerservice/config/CorsConfig.java | 47 +++ .../ase/lecturerservice/controllers/.gitkeep | 0 ...rerController.java => ExamController.java} | 19 +- .../controllers/FeedbackController.java | 32 ++ .../controllers/RootController.java | 15 - .../controllers/SubmissionController.java | 29 ++ .../com/ase/lecturerservice/dtos/.gitkeep | 0 .../com/ase/lecturerservice/dtos/ExamDto.java | 15 +- .../com/ase/lecturerservice/entities/.gitkeep | 0 .../ase/lecturerservice/entities/Exam.java | 34 +-- .../lecturerservice/entities/ExamType.java | 9 + .../lecturerservice/entities/Feedback.java | 69 +++++ .../entities/FileReference.java | 21 ++ .../lecturerservice/entities/Submission.java | 14 + .../entities/user/Lecturer.java | 2 +- .../entities/user/Student.java | 7 +- .../entities/user/UserEntity.java | 3 +- .../mockvalues/MockValues.java | 89 ++++-- .../com/ase/lecturerservice/services/.gitkeep | 0 .../lecturerservice/services/DummyData.java | 274 ++++++++++++++---- .../lecturerservice/services/ExamService.java | 28 ++ .../services/FeedbackService.java | 38 +++ .../services/LecturerService.java | 40 --- .../services/SubmissionService.java | 40 +++ src/main/resources/application.yaml | 4 + .../com/ase/lecturerservice/MockValues.java | 84 ++++-- .../controllers/ExamControllerTest.java | 100 +++++++ .../controllers/FeedbackControllerTest.java | 71 +++++ .../controllers/LecturerControllerTest.java | 74 ----- .../controllers/SubmissionControllerTest.java | 105 +++++++ .../services/ExamServiceTest.java | 90 ++++++ .../services/FeedbackServiceTest.java | 8 + .../services/LecturerServiceTest.java | 134 --------- .../services/SubmissionServiceTest.java | 125 ++++++++ 36 files changed, 1224 insertions(+), 400 deletions(-) delete mode 100644 src/main/java/com/ase/lecturerservice/config/.gitkeep create mode 100644 src/main/java/com/ase/lecturerservice/config/CorsConfig.java delete mode 100644 src/main/java/com/ase/lecturerservice/controllers/.gitkeep rename src/main/java/com/ase/lecturerservice/controllers/{LecturerController.java => ExamController.java} (67%) create mode 100644 src/main/java/com/ase/lecturerservice/controllers/FeedbackController.java delete mode 100644 src/main/java/com/ase/lecturerservice/controllers/RootController.java create mode 100644 src/main/java/com/ase/lecturerservice/controllers/SubmissionController.java delete mode 100644 src/main/java/com/ase/lecturerservice/dtos/.gitkeep delete mode 100644 src/main/java/com/ase/lecturerservice/entities/.gitkeep create mode 100644 src/main/java/com/ase/lecturerservice/entities/ExamType.java create mode 100644 src/main/java/com/ase/lecturerservice/entities/Feedback.java create mode 100644 src/main/java/com/ase/lecturerservice/entities/FileReference.java create mode 100644 src/main/java/com/ase/lecturerservice/entities/Submission.java delete mode 100644 src/main/java/com/ase/lecturerservice/services/.gitkeep create mode 100644 src/main/java/com/ase/lecturerservice/services/ExamService.java create mode 100644 src/main/java/com/ase/lecturerservice/services/FeedbackService.java delete mode 100644 src/main/java/com/ase/lecturerservice/services/LecturerService.java create mode 100644 src/main/java/com/ase/lecturerservice/services/SubmissionService.java create mode 100644 src/test/java/com/ase/lecturerservice/controllers/ExamControllerTest.java create mode 100644 src/test/java/com/ase/lecturerservice/controllers/FeedbackControllerTest.java delete mode 100644 src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java create mode 100644 src/test/java/com/ase/lecturerservice/controllers/SubmissionControllerTest.java create mode 100644 src/test/java/com/ase/lecturerservice/services/ExamServiceTest.java create mode 100644 src/test/java/com/ase/lecturerservice/services/FeedbackServiceTest.java delete mode 100644 src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java create mode 100644 src/test/java/com/ase/lecturerservice/services/SubmissionServiceTest.java diff --git a/.gitignore b/.gitignore index 7a6a576f..28564dcb 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ build/ ### VS Code ### .vscode/ - +/data/ ### MacOs ### -*/.DS_STORE \ No newline at end of file +*/.DS_STORE diff --git a/src/main/java/com/ase/lecturerservice/config/.gitkeep b/src/main/java/com/ase/lecturerservice/config/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/com/ase/lecturerservice/config/CorsConfig.java b/src/main/java/com/ase/lecturerservice/config/CorsConfig.java new file mode 100644 index 00000000..9bc320e6 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/config/CorsConfig.java @@ -0,0 +1,47 @@ +package com.ase.lecturerservice.config; + +import java.util.Arrays; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Configuration +@RequiredArgsConstructor +@EnableConfigurationProperties(CorsConfig.CorsConfigurationProperties.class) +public class CorsConfig { + + private final CorsConfigurationProperties corsConfigurationProperties; + + @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsConfiguration.addAllowedHeader("*"); + corsConfiguration.addAllowedMethod("*"); + corsConfiguration.setAllowCredentials(true); + Arrays.stream(corsConfigurationProperties.allowedOrigins) + .forEach(corsConfiguration::addAllowedOrigin); + log.info("Allowed origins:"); + Arrays.stream(corsConfigurationProperties.allowedOrigins).forEach(System.out::println); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", corsConfiguration); + return source; + } + + @Bean + CorsFilter corsFilter() { + return new CorsFilter(corsConfigurationSource()); + } + + @ConfigurationProperties(prefix = "app.cors") + record CorsConfigurationProperties(String[] allowedOrigins) { + + } +} diff --git a/src/main/java/com/ase/lecturerservice/controllers/.gitkeep b/src/main/java/com/ase/lecturerservice/controllers/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/com/ase/lecturerservice/controllers/LecturerController.java b/src/main/java/com/ase/lecturerservice/controllers/ExamController.java similarity index 67% rename from src/main/java/com/ase/lecturerservice/controllers/LecturerController.java rename to src/main/java/com/ase/lecturerservice/controllers/ExamController.java index 1df0aabc..5bebfe52 100644 --- a/src/main/java/com/ase/lecturerservice/controllers/LecturerController.java +++ b/src/main/java/com/ase/lecturerservice/controllers/ExamController.java @@ -10,23 +10,24 @@ import org.springframework.web.bind.annotation.RestController; import com.ase.lecturerservice.dtos.ExamDto; import com.ase.lecturerservice.entities.Exam; -import com.ase.lecturerservice.services.LecturerService; +import com.ase.lecturerservice.services.ExamService; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; @RestController -@RequestMapping(BASE_PATH + "/lecturer") +@RequestMapping(BASE_PATH + "/exams") @RequiredArgsConstructor -public class LecturerController { +public class ExamController { + private final ExamService examService; + private final ObjectMapper objectMapper; - private final LecturerService lecturerService; - - @GetMapping("/exams") - public ResponseEntity> getExams(@RequestParam String lecturer) + @GetMapping + public ResponseEntity> getExams(@RequestParam String lecturerUuid) throws IllegalArgumentException { - List exams = lecturerService.getExamsByLecturer(lecturer); + List exams = examService.getExamsByLecturer(lecturerUuid); List examDtoList = exams.stream() - .map(lecturerService::convertToExamDto) + .map(exam -> objectMapper.convertValue(exam, ExamDto.class)) .collect(Collectors.toList()); return ResponseEntity.ok(examDtoList); diff --git a/src/main/java/com/ase/lecturerservice/controllers/FeedbackController.java b/src/main/java/com/ase/lecturerservice/controllers/FeedbackController.java new file mode 100644 index 00000000..1e74eec2 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/controllers/FeedbackController.java @@ -0,0 +1,32 @@ +package com.ase.lecturerservice.controllers; + +import static com.ase.lecturerservice.controllers.BaseController.BASE_PATH; +import java.util.List; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import com.ase.lecturerservice.entities.Feedback; +import com.ase.lecturerservice.services.FeedbackService; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping(BASE_PATH + "/feedback") +@RequiredArgsConstructor +public class FeedbackController { + private final FeedbackService feedbackService; + + @GetMapping + public ResponseEntity getFeedbackFromExam( + @RequestParam String examUuid, @RequestParam String studentUuid) { + return ResponseEntity.ok(feedbackService.getFeedbackExam(studentUuid, examUuid)); + } + + @GetMapping("/for-lecturer/{lecturerUuid}") + public ResponseEntity> getFeedbacksForLecturer(@PathVariable String lecturerUuid) { + return ResponseEntity.ok(feedbackService.getFeedbackForLecturer(lecturerUuid)); + } + +} diff --git a/src/main/java/com/ase/lecturerservice/controllers/RootController.java b/src/main/java/com/ase/lecturerservice/controllers/RootController.java deleted file mode 100644 index db99f155..00000000 --- a/src/main/java/com/ase/lecturerservice/controllers/RootController.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.ase.lecturerservice.controllers; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class RootController { - - @GetMapping("/") - public ResponseEntity root() { - return ResponseEntity.ok("API Root: /api/v1/"); - } - -} diff --git a/src/main/java/com/ase/lecturerservice/controllers/SubmissionController.java b/src/main/java/com/ase/lecturerservice/controllers/SubmissionController.java new file mode 100644 index 00000000..df688e95 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/controllers/SubmissionController.java @@ -0,0 +1,29 @@ +package com.ase.lecturerservice.controllers; + +import static com.ase.lecturerservice.controllers.BaseController.BASE_PATH; +import java.util.List; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ase.lecturerservice.entities.Submission; +import com.ase.lecturerservice.services.SubmissionService; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping(BASE_PATH + "/submissions") +@RequiredArgsConstructor +public class SubmissionController { + + private final SubmissionService submissionService; + + @GetMapping("/for-lecturer/{lecturerUuid}") + public ResponseEntity> getRelevantSubmissions( + @PathVariable String lecturerUuid + ) { + return ResponseEntity.ok( + submissionService.getAllAccessibleSubmissionsForLecturer(lecturerUuid) + ); + } +} diff --git a/src/main/java/com/ase/lecturerservice/dtos/.gitkeep b/src/main/java/com/ase/lecturerservice/dtos/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java b/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java index e3494680..35f21285 100644 --- a/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java +++ b/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java @@ -1,7 +1,9 @@ package com.ase.lecturerservice.dtos; import java.time.LocalDate; -import java.util.UUID; +import java.util.List; +import com.ase.lecturerservice.entities.ExamType; +import com.ase.lecturerservice.entities.user.Student; import com.sun.istack.NotNull; import lombok.Builder; import lombok.Data; @@ -10,7 +12,7 @@ @Builder public class ExamDto { @NotNull - private UUID uuid; + private String uuid; @NotNull private String name; @@ -21,8 +23,15 @@ public class ExamDto { @NotNull private String module; + @NotNull + private ExamType examType; + + @NotNull + private List assignedStudents; + @NotNull private int time; - private int submissionsCount; + @NotNull + private int totalPoints; } diff --git a/src/main/java/com/ase/lecturerservice/entities/.gitkeep b/src/main/java/com/ase/lecturerservice/entities/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/com/ase/lecturerservice/entities/Exam.java b/src/main/java/com/ase/lecturerservice/entities/Exam.java index f391f570..55ebf302 100644 --- a/src/main/java/com/ase/lecturerservice/entities/Exam.java +++ b/src/main/java/com/ase/lecturerservice/entities/Exam.java @@ -2,8 +2,6 @@ import java.time.LocalDate; import java.util.List; -import java.util.UUID; -import com.ase.lecturerservice.entities.user.Lecturer; import com.ase.lecturerservice.entities.user.Student; import lombok.AllArgsConstructor; import lombok.Builder; @@ -17,35 +15,29 @@ @NoArgsConstructor @AllArgsConstructor public class Exam { - protected UUID uuid; + private String uuid; - protected String name; + private String name; - protected int grade; + private int totalPoints; - protected int averageGrade; + private ExamType examType; - protected int totalPoints; + private LocalDate date; - protected int achievedPoints; + private int time; - protected String examType; + private String allowedResources; - protected LocalDate date; + private int attempt; - protected int time; + private int etcs; - protected String allowedResources; + private String room; - protected int attempt; + private String module; - protected int etcs; + private String lecturerUuid; - protected String room; - - protected String module; - - protected Lecturer lecturer; - - protected List assignedStudents; + private List assignedStudents; } diff --git a/src/main/java/com/ase/lecturerservice/entities/ExamType.java b/src/main/java/com/ase/lecturerservice/entities/ExamType.java new file mode 100644 index 00000000..2f441523 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/entities/ExamType.java @@ -0,0 +1,9 @@ +package com.ase.lecturerservice.entities; + +public enum ExamType { + EXAM, + PRESENTATION, + ORAL, + PROJECT, + OTHERS +} diff --git a/src/main/java/com/ase/lecturerservice/entities/Feedback.java b/src/main/java/com/ase/lecturerservice/entities/Feedback.java new file mode 100644 index 00000000..c3d710a8 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/entities/Feedback.java @@ -0,0 +1,69 @@ +package com.ase.lecturerservice.entities; + +import java.time.LocalDate; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Builder +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +public class Feedback { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private String uuid; + + @Column(name = "graded_at") + private LocalDate gradedAt; + + @Column(name = "graded_exam") + @JsonProperty("examUuid") + private String examUuid; + + @Column(name = "graded_by") + @JsonProperty("lecturerUuid") + private String lecturerUuid; + + @Column(name = "grade_for") + @JsonProperty("studentUuid") + private String studentUuid; + + @Column(name = "graded_submission") + @JsonProperty("submissionUuid") + private String submissionUuid; + + @Column(name = "comment") + @JsonProperty("comment") + private String comment; + + @ElementCollection + @JsonProperty("fileReference") + @CollectionTable( + name = "grade_file_references", + joinColumns = @JoinColumn(name = "grade_id") + ) + private List fileReference; + + @Column(name = "points") + @JsonProperty("points") + private int points; + + @Column(name = "grade") + @JsonProperty("grade") + private float grade; +} diff --git a/src/main/java/com/ase/lecturerservice/entities/FileReference.java b/src/main/java/com/ase/lecturerservice/entities/FileReference.java new file mode 100644 index 00000000..db1544c3 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/entities/FileReference.java @@ -0,0 +1,21 @@ +package com.ase.lecturerservice.entities; + +import jakarta.persistence.Embeddable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +@Getter +@Setter +@NoArgsConstructor +@SuperBuilder +@Embeddable +public class FileReference { + private String fileUuid; + + private String filename; + + private String downloadLink; + +} diff --git a/src/main/java/com/ase/lecturerservice/entities/Submission.java b/src/main/java/com/ase/lecturerservice/entities/Submission.java new file mode 100644 index 00000000..ecb9cd1c --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/entities/Submission.java @@ -0,0 +1,14 @@ +package com.ase.lecturerservice.entities; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Submission { + private String id; + private String examId; + private String studentId; + private String submissionDate; + private FileReference fileUpload; +} diff --git a/src/main/java/com/ase/lecturerservice/entities/user/Lecturer.java b/src/main/java/com/ase/lecturerservice/entities/user/Lecturer.java index bdfcdd48..df34aeea 100644 --- a/src/main/java/com/ase/lecturerservice/entities/user/Lecturer.java +++ b/src/main/java/com/ase/lecturerservice/entities/user/Lecturer.java @@ -11,5 +11,5 @@ @SuperBuilder public class Lecturer extends UserEntity { - protected final UserType type = UserType.LECTURER; + private final UserType type = UserType.LECTURER; } diff --git a/src/main/java/com/ase/lecturerservice/entities/user/Student.java b/src/main/java/com/ase/lecturerservice/entities/user/Student.java index 83142b83..844f5728 100644 --- a/src/main/java/com/ase/lecturerservice/entities/user/Student.java +++ b/src/main/java/com/ase/lecturerservice/entities/user/Student.java @@ -1,5 +1,6 @@ package com.ase.lecturerservice.entities.user; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -7,8 +8,12 @@ @Getter @Setter +@AllArgsConstructor @NoArgsConstructor @SuperBuilder public class Student extends UserEntity { - protected final UserType type = UserType.STUDENT; + + private String matriculationNumber; + + private UserType type = UserType.STUDENT; } diff --git a/src/main/java/com/ase/lecturerservice/entities/user/UserEntity.java b/src/main/java/com/ase/lecturerservice/entities/user/UserEntity.java index c697090c..3a4e05b4 100644 --- a/src/main/java/com/ase/lecturerservice/entities/user/UserEntity.java +++ b/src/main/java/com/ase/lecturerservice/entities/user/UserEntity.java @@ -1,6 +1,5 @@ package com.ase.lecturerservice.entities.user; -import java.util.UUID; import org.hibernate.annotations.UuidGenerator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; @@ -18,7 +17,7 @@ public abstract class UserEntity { protected String email; @UuidGenerator - private UUID id; + private String uuid; @JsonProperty("first_name") private String firstName; diff --git a/src/main/java/com/ase/lecturerservice/mockvalues/MockValues.java b/src/main/java/com/ase/lecturerservice/mockvalues/MockValues.java index 6e30172d..5bd35854 100644 --- a/src/main/java/com/ase/lecturerservice/mockvalues/MockValues.java +++ b/src/main/java/com/ase/lecturerservice/mockvalues/MockValues.java @@ -2,26 +2,75 @@ import lombok.Getter; -@Getter -public enum MockValues { - GRADE(1), - SUBMISSIONS(1), - AVERAGE_GRADE(2), - TOTAL_POINTS(100), - ACHIEVED_POINTS(95), - TIME_SECONDS(5400), - TIME_MIN(90), - ATTEMPT(1), - ETCS(5), - DATE_YEAR(2015), - DATE_MONTH(10), - DATE_DAY(12); - - private final int value; - - MockValues(int value) { - this.value = value; +public class MockValues { + + @Getter + public enum UuidMocks { + EXAM_UUID("550e8400-e29b-41d4-a716-446655440000"), + EXAM_UUID2("550e8400-e29b-41d4-a716-446655440001"), + EXAM_UUID3("550e8400-e29b-41d4-a716-446655440002"), + EXAM_UUID4("550e8400-e29b-41d4-a716-446655440003"), + EXAM_UUID5("550e8400-e29b-41d4-a716-446655440004"), + EXAM_UUID6("550e8400-e29b-41d4-a716-446655440005"), + + STUDENT_UUID("d1c27c4f-e7d7-45b8-bc4e-6f634e7c5e8f"), + STUDENT_UUID2("f2a26e3f-3b50-44ac-a7f9-02fe3b41cf6a"), + STUDENT_UUID3("7283a092-2b64-4bfa-bf92-4242448b740a"), + STUDENT_UUID4("a9f5d8b5-2632-42b5-8520-1db4010fc80d"), + STUDENT_UUID5("be7f4234-cd28-4b29-9b09-5d1a38d3c67a"), + + GRADE_UUID("ea3f2b67-5ed0-4d89-bc2c-28533a210ae2"), + GRADE_UUID2("27d211f8-e45e-4f5a-b264-e7b4f51e8f95"), + GRADE_UUID3("cc28b1f6-3b5b-44e1-963f-0793b742a6d4"), + GRADE_UUID4("23428b1f6-3b5b-44e1-963f-0793b742a53"), + GRADE_UUID5("5318b1fs6-3b5b-46e1-963f-079dw742a53"), + GRADE_UUID6("5318b1fs6-a6ds-46e1-963f-079dw742a53"), + GRADE_UUID7("ac28b1f6-3b5b-44e1-963f-9033b742a6d4"), + + LECTURER_UUID("12345678-62hj-jhj2-h23j-901234567890"), + LECTURER_UUID2("3f8a9c12-7b4e-4d21-9c8a-2e6b7d9f1a23"), + LECTURER_UUID3("c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"), + LECTURER_UUID4("a0b1c2d3-e4f5-4a67-8b9c-d0e1f2a3b4c5"), + LECTURER_UUID5("9d8c7b6a-5e4f-4a3b-9c8d-7e6f5a4b3c2d"), + LECTURER_UUID6("f0e1d2c3-b4a5-49c6-8d7e-6f5a4b3c2d1e"); + + + private final String value; + + UuidMocks(String value) { + this.value = value; + } } -} + @Getter + public enum FloatMocks { + GRADE(1.3f), + AVERAGE_GRADE(2.0f); + private final float value; + + FloatMocks(float value) { + this.value = value; + } + } + + @Getter + public enum IntMocks { + SUBMISSIONS(0), + TOTAL_POINTS(100), + ACHIEVED_POINTS(95), + TIME_SECONDS(5400), + TIME_MIN(90), + ATTEMPT(1), + ETCS(5), + DATE_YEAR(2015), + DATE_MONTH(10), + DATE_DAY(25); + + private final int value; + + IntMocks(int value) { + this.value = value; + } + } +} diff --git a/src/main/java/com/ase/lecturerservice/services/.gitkeep b/src/main/java/com/ase/lecturerservice/services/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/com/ase/lecturerservice/services/DummyData.java b/src/main/java/com/ase/lecturerservice/services/DummyData.java index 04ff5070..2820173c 100644 --- a/src/main/java/com/ase/lecturerservice/services/DummyData.java +++ b/src/main/java/com/ase/lecturerservice/services/DummyData.java @@ -4,101 +4,259 @@ import java.util.List; import java.util.UUID; import com.ase.lecturerservice.entities.Exam; -import com.ase.lecturerservice.entities.user.Lecturer; +import com.ase.lecturerservice.entities.ExamType; +import com.ase.lecturerservice.entities.Feedback; +import com.ase.lecturerservice.entities.FileReference; +import com.ase.lecturerservice.entities.Submission; +import com.ase.lecturerservice.entities.user.Student; import com.ase.lecturerservice.mockvalues.MockValues; public class DummyData { + public static List SUBMISSIONS = List.of( + Submission.builder() + .studentId(MockValues.UuidMocks.STUDENT_UUID.getValue()) + .examId(MockValues.UuidMocks.EXAM_UUID.getValue()) + .submissionDate("2025-09-01T10:30:00Z") + .fileUpload(FileReference.builder() + .fileUuid(UUID.randomUUID().toString()) + .filename("submission1.pdf") + .downloadLink("https://example.com/submission1.pdf") + .build()) + .build(), + Submission.builder() + .studentId(MockValues.UuidMocks.STUDENT_UUID2.getValue()) + .examId(MockValues.UuidMocks.EXAM_UUID.getValue()) + .submissionDate("2025-09-01T11:10:00Z") + .fileUpload(FileReference.builder() + .fileUuid(UUID.randomUUID().toString()) + .filename("submission2.pdf") + .downloadLink("https://example.com/submission2.pdf") + .build()) + .build(), + Submission.builder() + .studentId(MockValues.UuidMocks.STUDENT_UUID3.getValue()) + .examId(MockValues.UuidMocks.EXAM_UUID2.getValue()) + .submissionDate("2025-09-02T09:15:00Z") + .fileUpload(FileReference.builder() + .fileUuid(UUID.randomUUID().toString()) + .filename("physics_midterm_attempt1.zip") + .downloadLink("https://example.com/physics_midterm_attempt1.zip") + .build()) + .build(), + Submission.builder() + .studentId(MockValues.UuidMocks.STUDENT_UUID.getValue()) + .examId(MockValues.UuidMocks.EXAM_UUID6.getValue()) + .submissionDate("2025-09-03T14:45:00Z") + .fileUpload(FileReference.builder() + .fileUuid(UUID.randomUUID().toString()) + .filename("software_project_demo.mp4") + .downloadLink("https://example.com/software_project_demo.mp4") + .build()) + .build(), + Submission.builder() + .studentId(MockValues.UuidMocks.STUDENT_UUID2.getValue()) + .examId(MockValues.UuidMocks.EXAM_UUID4.getValue()) + .submissionDate("2025-09-04T08:05:00Z") + .fileUpload(FileReference.builder() + .fileUuid(UUID.randomUUID().toString()) + .filename("chemistry_lab_report.docx") + .downloadLink("https://example.com/chemistry_lab_report.docx") + .build()) + .build() + ); static LocalDate date = LocalDate.of( - MockValues.DATE_YEAR.getValue(), - MockValues.DATE_MONTH.getValue(), - MockValues.DATE_DAY.getValue()); - + MockValues.IntMocks.DATE_YEAR.getValue(), + MockValues.IntMocks.DATE_MONTH.getValue(), + MockValues.IntMocks.DATE_DAY.getValue()); + static List studentList = List.of( + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID.getValue()).matriculationNumber("D725").build(), + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID2.getValue()).matriculationNumber("D755").build(), + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID3.getValue()).matriculationNumber("D735").build(), + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID4.getValue()).matriculationNumber("D729").build(), + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID5.getValue()).matriculationNumber("D726").build() + ); + static List studentList2 = List.of( + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID.getValue()).matriculationNumber("D725").build(), + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID2.getValue()).matriculationNumber("D755").build() + ); public static List EXAMS = List.of( Exam.builder() - .uuid(UUID.randomUUID()) + .uuid(MockValues.UuidMocks.EXAM_UUID.getValue()) .name("Mathematics Final Exam") - .grade(MockValues.GRADE.getValue()) - .averageGrade(MockValues.AVERAGE_GRADE.getValue()) - .totalPoints(MockValues.TOTAL_POINTS.getValue()) - .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) - .examType("Written") + .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue()) + .examType(ExamType.PRESENTATION) .date(date) - .time(MockValues.TIME_SECONDS.getValue()) + .time(MockValues.IntMocks.TIME_SECONDS.getValue()) .allowedResources("Calculator, Formula Sheet") - .attempt(MockValues.ATTEMPT.getValue()) - .etcs(MockValues.ETCS.getValue()) + .attempt(MockValues.IntMocks.ATTEMPT.getValue()) + .etcs(MockValues.IntMocks.ETCS.getValue()) .room("Room A101") - .lecturer(new Lecturer()) + .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID.getValue()) .module("Mathe") + .assignedStudents(studentList) .build(), Exam.builder() - .uuid(UUID.randomUUID()) + .uuid(MockValues.UuidMocks.EXAM_UUID2.getValue()) .name("Physics Midterm") - .grade(MockValues.GRADE.getValue()) - .averageGrade(MockValues.AVERAGE_GRADE.getValue()) - .totalPoints(MockValues.TOTAL_POINTS.getValue()) - .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) - .examType("Oral") + .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue()) + .examType(ExamType.EXAM) .date(date) - .time(MockValues.TIME_SECONDS.getValue()) + .time(MockValues.IntMocks.TIME_SECONDS.getValue()) .allowedResources("None") - .attempt(MockValues.ATTEMPT.getValue()) - .etcs(MockValues.ETCS.getValue()) + .attempt(MockValues.IntMocks.ATTEMPT.getValue()) + .etcs(MockValues.IntMocks.ETCS.getValue()) .room("Room B202") - .lecturer(new Lecturer()) + .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID.getValue()) .module("Physics") + .assignedStudents(studentList2) .build(), Exam.builder() - .uuid(UUID.randomUUID()) + .uuid(MockValues.UuidMocks.EXAM_UUID3.getValue()) .name("Computer Science Project") - .grade(MockValues.GRADE.getValue()) - .averageGrade(MockValues.AVERAGE_GRADE.getValue()) - .totalPoints(MockValues.TOTAL_POINTS.getValue()) - .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) - .examType("Project") + .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue()) + .examType(ExamType.OTHERS) .date(date) - .time(MockValues.TIME_SECONDS.getValue()) + .time(MockValues.IntMocks.TIME_SECONDS.getValue()) .allowedResources("Laptop, IDE") - .attempt(MockValues.ATTEMPT.getValue()) - .etcs(MockValues.ETCS.getValue()) + .attempt(MockValues.IntMocks.ATTEMPT.getValue()) + .etcs(MockValues.IntMocks.ETCS.getValue()) .room("Online Submission") - .lecturer(new Lecturer()) + .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID2.getValue()) .module("CS I") + .assignedStudents(studentList) .build(), Exam.builder() - .uuid(UUID.randomUUID()) + .uuid(MockValues.UuidMocks.EXAM_UUID4.getValue()) .name("Chemistry Lab Exam") - .grade(MockValues.GRADE.getValue()) - .averageGrade(MockValues.AVERAGE_GRADE.getValue()) - .totalPoints(MockValues.TOTAL_POINTS.getValue()) - .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) - .examType("Practical") + .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue()) + .examType(ExamType.ORAL) .date(date) - .time(MockValues.TIME_SECONDS.getValue()) + .time(MockValues.IntMocks.TIME_SECONDS.getValue()) .allowedResources("Lab Equipment, Safety Manual") - .attempt(MockValues.ATTEMPT.getValue()) - .etcs(MockValues.ETCS.getValue()) - .room("Lab C303") + .attempt(MockValues.IntMocks.ATTEMPT.getValue()) + .etcs(MockValues.IntMocks.ETCS.getValue()) .room("Online Submission") - .lecturer(new Lecturer()) + .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID2.getValue()) .module("Chemistry") + .assignedStudents(studentList2) .build(), Exam.builder() - .uuid(UUID.randomUUID()) - .name("History Essay Exam") - .grade(MockValues.GRADE.getValue()) - .averageGrade(MockValues.AVERAGE_GRADE.getValue()) - .totalPoints(MockValues.TOTAL_POINTS.getValue()) - .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) - .examType("Essay") + .uuid(MockValues.UuidMocks.EXAM_UUID6.getValue()) + .name("Software Development Project") + .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue()) + .examType(ExamType.PRESENTATION) .date(date) - .time(MockValues.TIME_SECONDS.getValue()) + .time(MockValues.IntMocks.TIME_SECONDS.getValue()) .allowedResources("Notes, Textbook") - .attempt(MockValues.ATTEMPT.getValue()) - .etcs(MockValues.ETCS.getValue()) - .room("Room D404") - .lecturer(new Lecturer()) + .attempt(MockValues.IntMocks.ATTEMPT.getValue()) + .etcs(MockValues.IntMocks.ETCS.getValue()) + .room("Room C303") + .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID2.getValue()) + .assignedStudents(studentList2) .module("History I") .build() ); + static List fileReferencesList = List.of( + FileReference.builder() + .fileUuid(UUID.randomUUID().toString()) + .filename("dummy_file") + .build(), + FileReference.builder() + .fileUuid(UUID.randomUUID().toString()) + .filename("dummy_file2") + .build() + ); + public static List Feedbacks = List.of( + Feedback.builder() + .uuid(MockValues.UuidMocks.GRADE_UUID.getValue()) + .gradedAt(date) + .lecturerUuid(UUID.randomUUID().toString()) + .studentUuid(MockValues.UuidMocks.STUDENT_UUID.getValue()) + .submissionUuid(UUID.randomUUID().toString()) + .examUuid(MockValues.UuidMocks.EXAM_UUID.getValue()) + .comment("Excellent work on the assignment.") + .fileReference(fileReferencesList) + .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue()) + .grade(MockValues.FloatMocks.GRADE.getValue()) + .build(), + Feedback.builder() + .uuid(MockValues.UuidMocks.GRADE_UUID2.getValue()) + .gradedAt(date) + .lecturerUuid(UUID.randomUUID().toString()) + .studentUuid(MockValues.UuidMocks.STUDENT_UUID2.getValue()) + .submissionUuid(UUID.randomUUID().toString()) + .examUuid(MockValues.UuidMocks.EXAM_UUID.getValue()) + .comment("Great effort! Check feedback in files.") + .fileReference(fileReferencesList) + .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue()) + .grade(MockValues.FloatMocks.GRADE.getValue()) + .build(), + Feedback.builder() + .uuid(MockValues.UuidMocks.GRADE_UUID3.getValue()) + .gradedAt(date) + .lecturerUuid(UUID.randomUUID().toString()) + .studentUuid(MockValues.UuidMocks.STUDENT_UUID3.getValue()) + .submissionUuid(UUID.randomUUID().toString()) + .examUuid(MockValues.UuidMocks.EXAM_UUID.getValue()) + .comment("Incomplete submission. Please review guidelines.") + .fileReference(fileReferencesList) + .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue()) + .grade(MockValues.FloatMocks.GRADE.getValue()) + .build(), + Feedback.builder() + .uuid(MockValues.UuidMocks.GRADE_UUID4.getValue()) + .gradedAt(date) + .lecturerUuid(UUID.randomUUID().toString()) + .studentUuid(MockValues.UuidMocks.STUDENT_UUID.getValue()) + .submissionUuid(UUID.randomUUID().toString()) + .examUuid(MockValues.UuidMocks.EXAM_UUID6.getValue()) + .comment("Great effort! Check feedback in files.") + .fileReference(fileReferencesList) + .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue()) + .grade(MockValues.FloatMocks.GRADE.getValue()) + .build(), + Feedback.builder() + .uuid(MockValues.UuidMocks.GRADE_UUID5.getValue()) + .gradedAt(date) + .lecturerUuid(UUID.randomUUID().toString()) + .studentUuid(MockValues.UuidMocks.STUDENT_UUID2.getValue()) + .submissionUuid(UUID.randomUUID().toString()) + .examUuid(MockValues.UuidMocks.EXAM_UUID6.getValue()) + .comment("Great effort! Check feedback in files.") + .fileReference(fileReferencesList) + .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue()) + .grade(MockValues.FloatMocks.GRADE.getValue()) + .build(), + Feedback.builder() + .uuid(MockValues.UuidMocks.GRADE_UUID6.getValue()) + .gradedAt(date) + .lecturerUuid(UUID.randomUUID().toString()) + .studentUuid(MockValues.UuidMocks.STUDENT_UUID2.getValue()) + .submissionUuid(UUID.randomUUID().toString()) + .examUuid(MockValues.UuidMocks.EXAM_UUID2.getValue()) + .comment("Great effort! Check feedback in files.") + .fileReference(fileReferencesList) + .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue()) + .grade(MockValues.FloatMocks.GRADE.getValue()) + .build(), + Feedback.builder() + .uuid(MockValues.UuidMocks.GRADE_UUID7.getValue()) + .gradedAt(date) + .lecturerUuid(UUID.randomUUID().toString()) + .studentUuid(MockValues.UuidMocks.STUDENT_UUID.getValue()) + .submissionUuid(UUID.randomUUID().toString()) + .examUuid(MockValues.UuidMocks.EXAM_UUID4.getValue()) + .comment("Great effort! Check feedback in files.") + .fileReference(fileReferencesList) + .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue()) + .grade(MockValues.FloatMocks.GRADE.getValue()) + .build() + ); } diff --git a/src/main/java/com/ase/lecturerservice/services/ExamService.java b/src/main/java/com/ase/lecturerservice/services/ExamService.java new file mode 100644 index 00000000..e371f398 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/services/ExamService.java @@ -0,0 +1,28 @@ +package com.ase.lecturerservice.services; + +import java.util.List; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; +import com.ase.lecturerservice.entities.Exam; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ExamService { + + public List getExamsByLecturer(String lecturerUuid) { + if (lecturerUuid == null || lecturerUuid.isBlank()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "Lecturer name is required"); + } + + // TODO: change this to a webclient call, when the API is ready + log.info("The Exams from {} has been requested", lecturerUuid); + return DummyData.EXAMS.stream() + .filter(exam -> exam.getLecturerUuid().equals(lecturerUuid)) + .toList(); + } +} diff --git a/src/main/java/com/ase/lecturerservice/services/FeedbackService.java b/src/main/java/com/ase/lecturerservice/services/FeedbackService.java new file mode 100644 index 00000000..fc6efdbe --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/services/FeedbackService.java @@ -0,0 +1,38 @@ +package com.ase.lecturerservice.services; + +import java.util.List; +import java.util.Optional; +import org.springframework.stereotype.Service; +import com.ase.lecturerservice.entities.Exam; +import com.ase.lecturerservice.entities.Feedback; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FeedbackService { + + // TODO: Adjust this when saving data is implemented + public Feedback getFeedbackExam(String studentUuid, String examUuid) { + return DummyData.Feedbacks.stream() + .filter(feedback -> feedback.getStudentUuid().equals(studentUuid) + && feedback.getExamUuid().equals(examUuid)) + .findFirst() + .orElse(null); + } + + public List getFeedbackForLecturer(String lecturerUuid) { + return DummyData.Feedbacks.stream().filter(feedback -> + Optional.ofNullable(getExam(feedback.getExamUuid())) + .map(exam -> exam.getLecturerUuid().equals(lecturerUuid)) + .orElse(false) + ).toList(); + } + + public Exam getExam(String uuid) { + return DummyData.EXAMS.stream() + .filter(exam -> exam.getUuid().equals(uuid)) + .findFirst().orElse(null); + } +} diff --git a/src/main/java/com/ase/lecturerservice/services/LecturerService.java b/src/main/java/com/ase/lecturerservice/services/LecturerService.java deleted file mode 100644 index 2289c211..00000000 --- a/src/main/java/com/ase/lecturerservice/services/LecturerService.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.ase.lecturerservice.services; - -import java.util.List; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.server.ResponseStatusException; -import com.ase.lecturerservice.dtos.ExamDto; -import com.ase.lecturerservice.entities.Exam; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Service -@RequiredArgsConstructor -public class LecturerService { - private final ObjectMapper objectMapper; - - private static final int SECONDS_PER_MINUTE = 60; - - public List getExamsByLecturer(String lecturer) - throws HttpStatusCodeException { - if (lecturer == null || lecturer.isBlank()) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - "Lecturer name is required"); - } - - // TODO: change this to a webclient call, when the API is ready - log.info("The Exam from {} has been requested", lecturer); - return DummyData.EXAMS; - } - - public ExamDto convertToExamDto(Exam exam) { - ExamDto examDto = objectMapper.convertValue(exam, ExamDto.class); - examDto.setTime(examDto.getTime() / SECONDS_PER_MINUTE); - - return (examDto); - } -} diff --git a/src/main/java/com/ase/lecturerservice/services/SubmissionService.java b/src/main/java/com/ase/lecturerservice/services/SubmissionService.java new file mode 100644 index 00000000..68985294 --- /dev/null +++ b/src/main/java/com/ase/lecturerservice/services/SubmissionService.java @@ -0,0 +1,40 @@ +package com.ase.lecturerservice.services; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.springframework.stereotype.Service; +import com.ase.lecturerservice.entities.Exam; +import com.ase.lecturerservice.entities.Submission; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class SubmissionService { + + private final ExamService examService; + + public List getSubmissionsForExam(String examId) { + return DummyData.SUBMISSIONS.stream() + .filter(submission -> submission.getExamId().equals(examId)) + .toList(); + } + + public List getSubmissionsForStudent(String studentId) { + return DummyData.SUBMISSIONS.stream() + .filter(submission -> submission.getStudentId().equals(studentId)) + .toList(); + } + + public List getAllAccessibleSubmissionsForLecturer(String lecturerUuid) { + Set examsOfLecturer = examService.getExamsByLecturer(lecturerUuid) + .stream() + .map(Exam::getUuid) + .collect(Collectors.toSet()); + + return DummyData.SUBMISSIONS.stream() + .filter(submission -> examsOfLecturer.contains(submission.getExamId())) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4b5711b9..de34f3cd 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -17,3 +17,7 @@ spring: server: error: include-message: always +app: + cors: + allowed-origins: + - http://localhost:5173 diff --git a/src/test/java/com/ase/lecturerservice/MockValues.java b/src/test/java/com/ase/lecturerservice/MockValues.java index 05162279..aeb7364e 100644 --- a/src/test/java/com/ase/lecturerservice/MockValues.java +++ b/src/test/java/com/ase/lecturerservice/MockValues.java @@ -2,26 +2,70 @@ import lombok.Getter; -@Getter -public enum MockValues { - GRADE(1), - SUBMISSIONS(1), - AVERAGE_GRADE(2), - TOTAL_POINTS(100), - ACHIEVED_POINTS(95), - TIME_SECONDS(5400), - TIME_MIN(90), - ATTEMPT(1), - ETCS(5), - DATE_YEAR(2015), - DATE_MONTH(10), - DATE_DAY(12); - - private final int value; - - MockValues(int value) { - this.value = value; +public class MockValues { + + @Getter + public enum UuidMocks { + EXAM_UUID("550e8400-e29b-41d4-a716-446655440000"), + EXAM_UUID2("550e8400-e29b-41d4-a716-446655440001"), + EXAM_UUID3("550e8400-e29b-41d4-a716-446655440002"), + EXAM_UUID4("550e8400-e29b-41d4-a716-446655440003"), + EXAM_UUID5("550e8400-e29b-41d4-a716-446655440004"), + + STUDENT_UUID("d1c27c4f-e7d7-45b8-bc4e-6f634e7c5e8f"), + STUDENT_UUID2("f2a26e3f-3b50-44ac-a7f9-02fe3b41cf6a"), + STUDENT_UUID3("7283a092-2b64-4bfa-bf92-4242448b740a"), + STUDENT_UUID4("a9f5d8b5-2632-42b5-8520-1db4010fc80d"), + STUDENT_UUID5("be7f4234-cd28-4b29-9b09-5d1a38d3c67a"), + + GRADE_UUID("ea3f2b67-5ed0-4d89-bc2c-28533a210ae2"), + GRADE_UUID2("27d211f8-e45e-4f5a-b264-e7b4f51e8f95"), + GRADE_UUID3("cc28b1f6-3b5b-44e1-963f-0793b742a6d4"), + + LECTURER_UUID("12345678-62hj-jhj2-h23j-901234567890"), + LECTURER_UUID2("3f8a9c12-7b4e-4d21-9c8a-2e6b7d9f1a23"), + LECTURER_UUID3("c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"), + LECTURER_UUID4("a0b1c2d3-e4f5-4a67-8b9c-d0e1f2a3b4c5"), + LECTURER_UUID5("9d8c7b6a-5e4f-4a3b-9c8d-7e6f5a4b3c2d"), + LECTURER_UUID6("f0e1d2c3-b4a5-49c6-8d7e-6f5a4b3c2d1e"); + + + private final String value; + + UuidMocks(String value) { + this.value = value; + } } -} + @Getter + public enum FloatMocks { + GRADE(1.3f), + AVERAGE_GRADE(2.0f); + private final float value; + + FloatMocks(float value) { + this.value = value; + } + } + + @Getter + public enum IntMocks { + SUBMISSIONS(0), + TOTAL_POINTS(100), + ACHIEVED_POINTS(95), + TIME_SECONDS(5400), + TIME_MIN(90), + ATTEMPT(1), + ETCS(5), + DATE_YEAR(2015), + DATE_MONTH(10), + DATE_DAY(25); + + private final int value; + + IntMocks(int value) { + this.value = value; + } + } +} diff --git a/src/test/java/com/ase/lecturerservice/controllers/ExamControllerTest.java b/src/test/java/com/ase/lecturerservice/controllers/ExamControllerTest.java new file mode 100644 index 00000000..7a7dfbc7 --- /dev/null +++ b/src/test/java/com/ase/lecturerservice/controllers/ExamControllerTest.java @@ -0,0 +1,100 @@ +package com.ase.lecturerservice.controllers; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import com.ase.lecturerservice.MockValues; +import com.ase.lecturerservice.entities.Exam; +import com.ase.lecturerservice.entities.ExamType; +import com.ase.lecturerservice.entities.user.Student; +import com.ase.lecturerservice.services.ExamService; + +@ExtendWith(SpringExtension.class) +@WebMvcTest(ExamController.class) +public class ExamControllerTest { + private static final List STUDENT_LIST = List.of( + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID.getValue()).matriculationNumber("D725").build(), + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID2.getValue()).matriculationNumber("D755").build(), + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID3.getValue()).matriculationNumber("D735").build(), + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID4.getValue()).matriculationNumber("D729").build(), + Student.builder().uuid( + MockValues.UuidMocks.STUDENT_UUID5.getValue()).matriculationNumber("D726").build() + ); + private static Exam exam; + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private ExamService examService; + + @BeforeAll + public static void setup() { + LocalDate date = LocalDate.of( + MockValues.IntMocks.DATE_YEAR.getValue(), + MockValues.IntMocks.DATE_MONTH.getValue(), + MockValues.IntMocks.DATE_DAY.getValue()); + + exam = Exam.builder() + .uuid(MockValues.UuidMocks.EXAM_UUID.getValue()) + .name("Test") + .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue()) + .examType(ExamType.EXAM) + .date(date) + .time(MockValues.IntMocks.TIME_SECONDS.getValue()) + .allowedResources("Calculator, Formula Sheet") + .attempt(MockValues.IntMocks.ATTEMPT.getValue()) + .etcs(MockValues.IntMocks.ETCS.getValue()) + .room("Room A101") + .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID.getValue()) + .module("Test") + .assignedStudents(STUDENT_LIST) + .build(); + } + + @Test + void fetchExamsShouldReturnExamDtos() throws Exception { + when(examService.getExamsByLecturer("Tom")).thenReturn(List.of(exam)); + + mockMvc.perform(get("/api/v1/exams?lecturerUuid={lecturerUuid}", "Tom") + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].uuid") + .value(MockValues.UuidMocks.EXAM_UUID.getValue())) + .andExpect(jsonPath("$[0].name").value("Test")) + .andExpect(jsonPath("$[0].date").value("2015-10-25")) + .andExpect(jsonPath("$[0].module").value("Test")) + .andExpect(jsonPath("$[0].time").value(MockValues.IntMocks.TIME_SECONDS.getValue())) + .andExpect(jsonPath("$[0].examType").value(ExamType.EXAM.toString())) + .andExpect(jsonPath("$[0].assignedStudents").isNotEmpty()); + } + + @Test + void fetchExamsShouldThrowException() throws Exception { + when(examService.getExamsByLecturer(" ")) + .thenThrow(new IllegalArgumentException("Lecturer cannot be empty")); + + mockMvc.perform(get("/api/v1/exams?lecturerUuid={lecturerUuid}", " ") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(content().string("Lecturer cannot be empty")); + } +} diff --git a/src/test/java/com/ase/lecturerservice/controllers/FeedbackControllerTest.java b/src/test/java/com/ase/lecturerservice/controllers/FeedbackControllerTest.java new file mode 100644 index 00000000..c2863771 --- /dev/null +++ b/src/test/java/com/ase/lecturerservice/controllers/FeedbackControllerTest.java @@ -0,0 +1,71 @@ +package com.ase.lecturerservice.controllers; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.time.LocalDate; +import java.util.UUID; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import com.ase.lecturerservice.MockValues; +import com.ase.lecturerservice.entities.Feedback; +import com.ase.lecturerservice.services.FeedbackService; + +@ExtendWith(SpringExtension.class) +@WebMvcTest(FeedbackController.class) +public class FeedbackControllerTest { + private static final LocalDate DATE = LocalDate.of( + MockValues.IntMocks.DATE_YEAR.getValue(), + MockValues.IntMocks.DATE_MONTH.getValue(), + MockValues.IntMocks.DATE_DAY.getValue()); + private static Feedback feedback; + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private FeedbackService feedbackService; + + @BeforeAll + public static void setup() { + feedback = Feedback.builder() + .uuid(MockValues.UuidMocks.GRADE_UUID.getValue()) + .gradedAt(DATE) + .lecturerUuid(UUID.randomUUID().toString()) + .studentUuid(MockValues.UuidMocks.STUDENT_UUID.getValue()) + .submissionUuid(UUID.randomUUID().toString()) + .examUuid(MockValues.UuidMocks.EXAM_UUID.getValue()) + .comment("Excellent work on the assignment.") + .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue()) + .grade(MockValues.FloatMocks.GRADE.getValue()) + .build(); + } + + @Test + void getGradedExamShouldReturnGradeWhenValidUuids() throws Exception { + String studentUuid = MockValues.UuidMocks.STUDENT_UUID.getValue(); + String examUuid = MockValues.UuidMocks.EXAM_UUID.getValue(); + + when(feedbackService.getFeedbackExam(studentUuid, examUuid)) + .thenReturn(feedback); + + mockMvc.perform(get("/api/v1/feedback") + .param("studentUuid", studentUuid) + .param("examUuid", examUuid) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.gradedAt").value(DATE.toString())) + .andExpect(jsonPath("$.studentUuid").value(studentUuid)) + .andExpect(jsonPath("$.examUuid").value(examUuid)) + .andExpect(jsonPath("$.comment").value("Excellent work on the assignment.")) + .andExpect(jsonPath("$.grade").value(feedback.getGrade())) + .andExpect(jsonPath("$.points").value(feedback.getPoints())); + } +} diff --git a/src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java b/src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java deleted file mode 100644 index 4508f88f..00000000 --- a/src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.ase.lecturerservice.controllers; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.time.LocalDate; -import java.util.List; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.http.MediaType; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -import org.springframework.test.web.servlet.MockMvc; -import com.ase.lecturerservice.MockValues; -import com.ase.lecturerservice.dtos.ExamDto; -import com.ase.lecturerservice.entities.Exam; -import com.ase.lecturerservice.services.LecturerService; - -@WebMvcTest(LecturerController.class) -public class LecturerControllerTest { - - private static ExamDto examDto; - - @Autowired - private MockMvc mockMvc; - @MockitoBean - private LecturerService lecturerService; - - @BeforeAll - public static void setup() { - LocalDate date = LocalDate.of( - MockValues.DATE_YEAR.getValue(), - MockValues.DATE_MONTH.getValue(), - MockValues.DATE_DAY.getValue()); - - examDto = ExamDto.builder() - .name("Test") - .module("Test") - .date(date) - .time(MockValues.TIME_SECONDS.getValue()) - .build(); - } - - @Test - void fetchExamsShouldReturnExamDtos() throws Exception { - Mockito.when(lecturerService.getExamsByLecturer("john")).thenReturn(List.of()); - Mockito.when(lecturerService.convertToExamDto(new Exam())).thenReturn(examDto); - mockMvc.perform(get("/api/v1/lecturer/exams") - .param("lecturer", "john") - .contentType(MediaType.APPLICATION_JSON)) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].name").value("Test")) - .andExpect(jsonPath("$[0].date").value("2015-10-12")) - .andExpect(jsonPath("$[0].module").value("Test")) - .andExpect(jsonPath("$[0].time").value(MockValues.TIME_SECONDS.getValue())); - } - - @Test - void fetchExamsShouldThrowException() throws Exception { - Mockito.when(lecturerService.getExamsByLecturer("")) - .thenThrow(new IllegalArgumentException("Lecturer cannot be empty")); - - mockMvc.perform(get("/api/v1/lecturer/exams") - .param("lecturer", "") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andExpect(content().string("Lecturer cannot be empty")); - } -} diff --git a/src/test/java/com/ase/lecturerservice/controllers/SubmissionControllerTest.java b/src/test/java/com/ase/lecturerservice/controllers/SubmissionControllerTest.java new file mode 100644 index 00000000..08fc8114 --- /dev/null +++ b/src/test/java/com/ase/lecturerservice/controllers/SubmissionControllerTest.java @@ -0,0 +1,105 @@ +package com.ase.lecturerservice.controllers; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import com.ase.lecturerservice.MockValues; +import com.ase.lecturerservice.entities.FileReference; +import com.ase.lecturerservice.entities.Submission; +import com.ase.lecturerservice.services.SubmissionService; + +@ExtendWith(SpringExtension.class) +@WebMvcTest(SubmissionController.class) +public class SubmissionControllerTest { + private static final String LECTURER_UUID = "lecturer-uuid-123"; + private static final String STUDENT_UUID = MockValues.UuidMocks.STUDENT_UUID.getValue(); + private static final String EXAM_UUID = MockValues.UuidMocks.EXAM_UUID.getValue(); + private static final String SUBMISSION_DATE = "2025-09-01T10:30:00Z"; + private static final String FILENAME = "submission1.pdf"; + private static final String DOWNLOAD_LINK = "https://example.com/submission1.pdf"; + + private static Submission submission; + private static List submissionList; + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private SubmissionService submissionService; + + @BeforeAll + public static void setup() { + FileReference fileReference = FileReference.builder() + .fileUuid(UUID.randomUUID().toString()) + .filename(FILENAME) + .downloadLink(DOWNLOAD_LINK) + .build(); + + submission = Submission.builder() + .id(UUID.randomUUID().toString()) + .examId(EXAM_UUID) + .studentId(STUDENT_UUID) + .submissionDate(SUBMISSION_DATE) + .fileUpload(fileReference) + .build(); + + submissionList = List.of(submission); + } + + @Test + void getRelevantSubmissionsShouldReturnSubmissionsForLecturer() throws Exception { + when(submissionService.getAllAccessibleSubmissionsForLecturer(LECTURER_UUID)) + .thenReturn(submissionList); + + mockMvc.perform(get("/api/v1/submissions/for-lecturer/{lecturerUuid}", LECTURER_UUID) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].examId").value(EXAM_UUID)) + .andExpect(jsonPath("$[0].studentId").value(STUDENT_UUID)) + .andExpect(jsonPath("$[0].submissionDate").value(SUBMISSION_DATE)) + .andExpect(jsonPath("$[0].fileUpload.filename").value(FILENAME)) + .andExpect(jsonPath("$[0].fileUpload.downloadLink").value(DOWNLOAD_LINK)); + } + + @Test + void getRelevantSubmissionsShouldReturnEmptyListWhenNoSubmissions() throws Exception { + when(submissionService.getAllAccessibleSubmissionsForLecturer(LECTURER_UUID)) + .thenReturn(List.of()); + + mockMvc.perform(get("/api/v1/submissions/for-lecturer/{lecturerUuid}", LECTURER_UUID) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + } + + @Test + void getRelevantSubmissionsShouldHandleInvalidLecturerUuid() throws Exception { + String invalidLecturerUuid = "invalid-uuid"; + when(submissionService.getAllAccessibleSubmissionsForLecturer(invalidLecturerUuid)) + .thenReturn(List.of()); + + mockMvc.perform(get("/api/v1/submissions/for-lecturer/{lecturerUuid}", invalidLecturerUuid) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + } +} diff --git a/src/test/java/com/ase/lecturerservice/services/ExamServiceTest.java b/src/test/java/com/ase/lecturerservice/services/ExamServiceTest.java new file mode 100644 index 00000000..1487fe64 --- /dev/null +++ b/src/test/java/com/ase/lecturerservice/services/ExamServiceTest.java @@ -0,0 +1,90 @@ +package com.ase.lecturerservice.services; + +import java.time.LocalDate; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import com.ase.lecturerservice.MockValues; +import com.ase.lecturerservice.entities.Exam; +import com.ase.lecturerservice.entities.ExamType; +import com.ase.lecturerservice.entities.user.Lecturer; +import com.ase.lecturerservice.entities.user.UserType; + +@SpringBootTest +public class ExamServiceTest { + @Autowired + private ExamService examService; + + private Lecturer lecturer; + private LocalDate date; + + @BeforeEach + public void setUpLecturer() { + lecturer = Lecturer.builder() + .uuid(MockValues.UuidMocks.LECTURER_UUID.getValue()) + .email("lecturer@example.com") + .type(UserType.LECTURER) + .firstName("John") + .lastName("Doe") + .build(); + + date = LocalDate.of( + MockValues.IntMocks.DATE_YEAR.getValue(), + MockValues.IntMocks.DATE_MONTH.getValue(), + MockValues.IntMocks.DATE_DAY.getValue()); + } + + @Test + void fetchExamsByLecturerShouldGetExams() { + DummyData.EXAMS = List.of(Exam.builder() + .uuid(MockValues.UuidMocks.EXAM_UUID.getValue()) + .name("Mathematics Final Exam") + .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue()) + .examType(ExamType.PRESENTATION) + .date(date) + .time(MockValues.IntMocks.TIME_SECONDS.getValue()) + .allowedResources("Calculator, Formula Sheet") + .attempt(MockValues.IntMocks.ATTEMPT.getValue()) + .etcs(MockValues.IntMocks.ETCS.getValue()) + .room("Room A101") + .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID.getValue()) + .module("Mathe") + .build()); + + List exams = examService.getExamsByLecturer(lecturer.getUuid()); + Exam exam = exams.getFirst(); + + Assertions.assertThat(exams).isNotEmpty(); + Assertions.assertThat(exam.getUuid()) + .isEqualTo(MockValues.UuidMocks.EXAM_UUID.getValue()); + Assertions.assertThat(exam.getName()) + .isEqualTo("Mathematics Final Exam"); + Assertions.assertThat(exam.getTotalPoints()) + .isEqualTo(MockValues.IntMocks.TOTAL_POINTS.getValue()); + Assertions.assertThat(exam.getExamType()).isEqualTo(ExamType.PRESENTATION); + Assertions.assertThat(exam.getDate()).isEqualTo(date); + Assertions.assertThat(exam.getTime()) + .isEqualTo(MockValues.IntMocks.TIME_SECONDS.getValue()); + Assertions.assertThat(exam.getAllowedResources()) + .isEqualTo("Calculator, Formula Sheet"); + Assertions.assertThat(exam.getAttempt()) + .isEqualTo(MockValues.IntMocks.ATTEMPT.getValue()); + Assertions.assertThat(exam.getEtcs()) + .isEqualTo(MockValues.IntMocks.ETCS.getValue()); + Assertions.assertThat(exam.getRoom()).isEqualTo("Room A101"); + Assertions.assertThat(exam.getLecturerUuid()).isEqualTo(lecturer.getUuid()); + Assertions.assertThat(exam.getModule()).isEqualTo("Mathe"); + } + + @Test + void fetchExamsByLecturerShouldNotGetExams() { + DummyData.EXAMS = List.of(); + + List exams = examService.getExamsByLecturer("Test"); + + Assertions.assertThat(exams).isEmpty(); + } +} diff --git a/src/test/java/com/ase/lecturerservice/services/FeedbackServiceTest.java b/src/test/java/com/ase/lecturerservice/services/FeedbackServiceTest.java new file mode 100644 index 00000000..d19e41c5 --- /dev/null +++ b/src/test/java/com/ase/lecturerservice/services/FeedbackServiceTest.java @@ -0,0 +1,8 @@ +package com.ase.lecturerservice.services; + +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class FeedbackServiceTest { + +} diff --git a/src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java b/src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java deleted file mode 100644 index becf4ba6..00000000 --- a/src/test/java/com/ase/lecturerservice/services/LecturerServiceTest.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.ase.lecturerservice.services; - -import java.time.LocalDate; -import java.util.List; -import java.util.UUID; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import com.ase.lecturerservice.MockValues; -import com.ase.lecturerservice.dtos.ExamDto; -import com.ase.lecturerservice.entities.Exam; -import com.ase.lecturerservice.entities.user.Lecturer; -import com.ase.lecturerservice.entities.user.UserType; - -@SpringBootTest -public class LecturerServiceTest { - @Autowired - private LecturerService lecturerService; - - private Lecturer lecturer; - private LocalDate date; - - @BeforeEach - public void setUpLecturer() { - lecturer = Lecturer.builder() - .id(UUID.randomUUID()) - .email("lecturer@example.com") - .type(UserType.LECTURER) - .firstName("John") - .lastName("Doe") - .build(); - - date = LocalDate.of( - MockValues.DATE_YEAR.getValue(), - MockValues.DATE_MONTH.getValue(), - MockValues.DATE_DAY.getValue()); - } - - @Test - void fetchExamsByLecturerShouldGetExams() { - UUID uuid = UUID.randomUUID(); - DummyData.EXAMS = List.of(Exam.builder() - .uuid(uuid) - .name("Mathematics Final Exam") - .grade(MockValues.GRADE.getValue()) - .averageGrade(MockValues.AVERAGE_GRADE.getValue()) - .totalPoints(MockValues.TOTAL_POINTS.getValue()) - .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) - .examType("Written") - .date(date) - .time(MockValues.TIME_SECONDS.getValue()) - .allowedResources("Calculator, Formula Sheet") - .attempt(MockValues.ATTEMPT.getValue()) - .etcs(MockValues.ETCS.getValue()) - .room("Room A101") - .lecturer(lecturer) - .module("Mathe") - .build()); - - List exams = lecturerService.getExamsByLecturer("Test"); - Exam exam = exams.getFirst(); - - Assertions.assertThat(exams).isNotEmpty(); - Assertions.assertThat(exam.getUuid()) - .isEqualTo(uuid); - Assertions.assertThat(exam.getName()) - .isEqualTo("Mathematics Final Exam"); - Assertions.assertThat(exam.getGrade()) - .isEqualTo(MockValues.GRADE.getValue()); - Assertions.assertThat(exam.getAverageGrade()) - .isEqualTo(MockValues.AVERAGE_GRADE.getValue()); - Assertions.assertThat(exam.getTotalPoints()) - .isEqualTo(MockValues.TOTAL_POINTS.getValue()); - Assertions.assertThat(exam.getAchievedPoints()) - .isEqualTo(MockValues.ACHIEVED_POINTS.getValue()); - Assertions.assertThat(exam.getExamType()).isEqualTo("Written"); - Assertions.assertThat(exam.getDate()).isEqualTo(date); - Assertions.assertThat(exam.getTime()) - .isEqualTo(MockValues.TIME_SECONDS.getValue()); - Assertions.assertThat(exam.getAllowedResources()) - .isEqualTo("Calculator, Formula Sheet"); - Assertions.assertThat(exam.getAttempt()) - .isEqualTo(MockValues.ATTEMPT.getValue()); - Assertions.assertThat(exam.getEtcs()) - .isEqualTo(MockValues.ETCS.getValue()); - Assertions.assertThat(exam.getRoom()).isEqualTo("Room A101"); - Assertions.assertThat(exam.getLecturer()).isEqualTo(lecturer); - Assertions.assertThat(exam.getModule()).isEqualTo("Mathe"); - } - - @Test - void fetchExamsByLecturerShouldNotGetExams() { - DummyData.EXAMS = List.of(); - - List exams = lecturerService.getExamsByLecturer("Test"); - - Assertions.assertThat(exams).isEmpty(); - } - - @Test - void convertToExamDtoShouldConvertExamsToDto() { - Exam exam = Exam.builder() - .name("Mathematics Final Exam") - .grade(MockValues.GRADE.getValue()) - .averageGrade(MockValues.AVERAGE_GRADE.getValue()) - .totalPoints(MockValues.TOTAL_POINTS.getValue()) - .achievedPoints(MockValues.ACHIEVED_POINTS.getValue()) - .examType("Written") - .date(date) - .time(MockValues.TIME_SECONDS.getValue()) - .allowedResources("Calculator, Formula Sheet") - .attempt(MockValues.ATTEMPT.getValue()) - .etcs(MockValues.ETCS.getValue()) - .room("Room A101") - .lecturer(lecturer) - .module("Mathe") - .build(); - - ExamDto examDto = lecturerService.convertToExamDto(exam); - - Assertions.assertThat(examDto.getName()). - isEqualTo("Mathematics Final Exam"); - Assertions.assertThat(examDto.getModule()). - isEqualTo("Mathe"); - Assertions.assertThat(examDto.getDate()). - isEqualTo(date); - Assertions.assertThat(examDto.getTime()). - isEqualTo(MockValues.TIME_MIN.getValue()); - Assertions.assertThat(examDto.getSubmissionsCount()). - isEqualTo(MockValues.SUBMISSIONS.getValue()); - } -} diff --git a/src/test/java/com/ase/lecturerservice/services/SubmissionServiceTest.java b/src/test/java/com/ase/lecturerservice/services/SubmissionServiceTest.java new file mode 100644 index 00000000..0d484d8b --- /dev/null +++ b/src/test/java/com/ase/lecturerservice/services/SubmissionServiceTest.java @@ -0,0 +1,125 @@ +package com.ase.lecturerservice.services; + +import java.time.LocalDate; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import com.ase.lecturerservice.MockValues; +import com.ase.lecturerservice.entities.Exam; +import com.ase.lecturerservice.entities.ExamType; +import com.ase.lecturerservice.entities.Submission; +import com.ase.lecturerservice.entities.user.Lecturer; +import com.ase.lecturerservice.entities.user.UserType; + +@SpringBootTest +public class SubmissionServiceTest { + @Autowired + private SubmissionService submissionService; + + @Autowired + private ExamService examService; + + private Lecturer lecturer; + private LocalDate date; + + @BeforeEach + public void setUpLecturer() { + lecturer = Lecturer.builder() + .uuid(MockValues.UuidMocks.LECTURER_UUID.getValue()) + .email("lecturer@example.com") + .type(UserType.LECTURER) + .firstName("John") + .lastName("Doe") + .build(); + + date = LocalDate.of( + MockValues.IntMocks.DATE_YEAR.getValue(), + MockValues.IntMocks.DATE_MONTH.getValue(), + MockValues.IntMocks.DATE_DAY.getValue()); + } + + @Test + void getSubmissionsForExamShouldReturnSubmissionsForSpecificExam() { + String examId = MockValues.UuidMocks.EXAM_UUID.getValue(); + + List submissions = submissionService.getSubmissionsForExam(examId); + + Assertions.assertThat(submissions).isNotEmpty(); + Assertions.assertThat(submissions) + .allMatch(submission -> submission.getExamId().equals(examId)); + } + + @Test + void getSubmissionsForStudentShouldReturnSubmissionsForSpecificStudent() { + String studentId = MockValues.UuidMocks.STUDENT_UUID.getValue(); + + List submissions = submissionService.getSubmissionsForStudent(studentId); + + Assertions.assertThat(submissions).isNotEmpty(); + Assertions.assertThat(submissions) + .allMatch(submission -> submission.getStudentId().equals(studentId)); + } + + @Test + void getAllAccessibleSubmissionsForLecturerShouldReturnOnlyLecturerSubmissions() { + // Setup exam for lecturer + Exam exam = Exam.builder() + .uuid(MockValues.UuidMocks.EXAM_UUID.getValue()) + .name("Test Exam") + .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue()) + .examType(ExamType.EXAM) + .date(date) + .time(MockValues.IntMocks.TIME_SECONDS.getValue()) + .allowedResources("Calculator") + .attempt(MockValues.IntMocks.ATTEMPT.getValue()) + .etcs(MockValues.IntMocks.ETCS.getValue()) + .room("Room A101") + .lecturerUuid(lecturer.getUuid()) + .module("Test Module") + .build(); + + DummyData.EXAMS = List.of(exam); + + List submissions = submissionService + .getAllAccessibleSubmissionsForLecturer(lecturer.getUuid()); + + Assertions.assertThat(submissions).isNotEmpty(); + // All submissions should be for exams belonging to this lecturer + Assertions.assertThat(submissions).allMatch(submission -> + DummyData.EXAMS.stream() + .anyMatch(ex -> ex.getUuid().equals(submission.getExamId()) + && ex.getLecturerUuid().equals(lecturer.getUuid())) + ); + } + + @Test + void getAllAccessibleSubmissionsForLecturerShouldReturnEmptyListForUnknownLecturer() { + String unknownLecturerUuid = "unknown-lecturer-uuid"; + + List submissions = submissionService + .getAllAccessibleSubmissionsForLecturer(unknownLecturerUuid); + + Assertions.assertThat(submissions).isEmpty(); + } + + @Test + void getSubmissionsForExamShouldReturnEmptyListForUnknownExam() { + String unknownExamId = "unknown-exam-id"; + + List submissions = submissionService.getSubmissionsForExam(unknownExamId); + + Assertions.assertThat(submissions).isEmpty(); + } + + @Test + void getSubmissionsForStudentShouldReturnEmptyListForUnknownStudent() { + String unknownStudentId = "unknown-student-id"; + + List submissions = submissionService.getSubmissionsForStudent(unknownStudentId); + + Assertions.assertThat(submissions).isEmpty(); + } +}