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