From 4b82d148cf26dff32942b5b94b6f828299d45e25 Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Mon, 22 Jun 2026 13:47:52 +0300 Subject: [PATCH 01/15] Student db connection, most likely needs more work --- .../uniplan/student/StudentController.java | 38 ++++++++++++++++--- .../unilab/uniplan/student/StudentMapper.java | 13 ++++++- .../student/dto/StudentResponseDto.java | 5 ++- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/unilab/uniplan/student/StudentController.java b/src/main/java/org/unilab/uniplan/student/StudentController.java index b21f45bb..f10c340d 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentController.java +++ b/src/main/java/org/unilab/uniplan/student/StudentController.java @@ -18,6 +18,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.unilab.uniplan.course.Course; +import org.unilab.uniplan.course.CourseService; +import org.unilab.uniplan.course.dto.CourseDto; +import org.unilab.uniplan.major.MajorService; +import org.unilab.uniplan.major.dto.MajorDto; import org.unilab.uniplan.student.dto.StudentCourseMajorDto; import org.unilab.uniplan.student.dto.StudentDto; import org.unilab.uniplan.student.dto.StudentRequestDto; @@ -30,29 +35,48 @@ public class StudentController { private final StudentService studentService; + private final CourseService courseService; private final StudentMapper studentMapper; + private final MajorService majorService; @PostMapping public ResponseEntity createStudent(@RequestBody @NotNull @Valid final StudentRequestDto studentRequestDTO) { final StudentDto studentDTO = studentMapper.toInternalDto(studentRequestDTO); studentService.createStudent(studentDTO); + + CourseDto courseDto = courseService.findCourseById(studentDTO.courseId()); + MajorDto majorDto = majorService.findMajorById(courseDto.majorId()); + StudentResponseDto student = studentMapper.toResponseDto(studentDTO, courseDto, majorDto); + return ResponseEntity.status(HttpStatus.CREATED) - .body(studentMapper.toResponseDto(studentDTO)); + .body(student); } @GetMapping("/{id}") public ResponseEntity getStudent(@PathVariable @NotNull final UUID id) { - final StudentResponseDto studentResponseDTO = studentMapper.toResponseDto(studentService.findStudentById( - id)); + + StudentDto studentDTO = studentService.findStudentById(id); + CourseDto courseDto = courseService.findCourseById(studentDTO.courseId()); + MajorDto majorDto = majorService.findMajorById(courseDto.majorId()); + final StudentResponseDto studentResponseDTO = studentMapper.toResponseDto(studentDTO, courseDto, majorDto); return ResponseEntity.ok(studentResponseDTO); } @GetMapping public List getAllStudents() { - return studentMapper.toResponseDtoList(studentService.findAll()); + List students = studentService.findAll(); + List responseList = students.stream() + .map(studentDto -> { + CourseDto courseDto = courseService.findCourseById(studentDto.courseId()); + MajorDto majorDto = majorService.findMajorById(courseDto.majorId()); + return studentMapper.toResponseDto(studentDto, courseDto, majorDto); + }) + .toList(); + + return responseList; } @GetMapping("/student-course-major/getStudentCourseMajorInfo") @@ -69,8 +93,10 @@ public ResponseEntity updateStudent(@PathVariable @RequestBody @NotNull @Valid final StudentRequestDto studentRequestDTO) { final StudentDto studentDTO = studentMapper.toInternalDto(studentRequestDTO); - studentService.updateStudent(id, studentDTO); - return ResponseEntity.ok(studentMapper.toResponseDto(studentDTO)); + StudentDto studentDto = studentService.updateStudent(id, studentDTO); + CourseDto courseDto = courseService.findCourseById(studentDto.courseId()); + MajorDto majorDto = majorService.findMajorById(courseDto.majorId()); + return ResponseEntity.ok(studentMapper.toResponseDto(studentDTO, courseDto, majorDto)); } @DeleteMapping("/{id}") diff --git a/src/main/java/org/unilab/uniplan/student/StudentMapper.java b/src/main/java/org/unilab/uniplan/student/StudentMapper.java index 440e5043..94681962 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentMapper.java +++ b/src/main/java/org/unilab/uniplan/student/StudentMapper.java @@ -4,6 +4,8 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; +import org.unilab.uniplan.course.dto.CourseDto; +import org.unilab.uniplan.major.dto.MajorDto; import org.unilab.uniplan.student.dto.StudentDto; import org.unilab.uniplan.student.dto.StudentRequestDto; import org.unilab.uniplan.student.dto.StudentResponseDto; @@ -24,8 +26,15 @@ public interface StudentMapper { @Mapping(target = "id", ignore = true) StudentDto toInternalDto(StudentRequestDto student); - @Mapping(source = "courseId", target = "courseId") - StudentResponseDto toResponseDto(StudentDto studentDto); + @Mapping(target = "majorName", source = "majorDto.majorName") + @Mapping(target = "courseType", source = "courseDto.courseType") + @Mapping(target = "courseSubtype", source = "courseDto.courseSubtype") + @Mapping(target = "courseYear", source = "courseDto.courseYear") + @Mapping(target = "id", source = "studentDto.id") + @Mapping(target = "firstName", source = "studentDto.firstName") + @Mapping(target = "lastName", source = "studentDto.lastName") + @Mapping(target = "facultyNumber", source = "studentDto.facultyNumber") + StudentResponseDto toResponseDto(StudentDto studentDto, CourseDto courseDto, MajorDto majorDto); List toResponseDtoList(List students); diff --git a/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java b/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java index 8ed48fec..dc16780a 100644 --- a/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java +++ b/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java @@ -13,7 +13,10 @@ public record StudentResponseDto( @Size(max = 100) String lastName, String facultyNumber, - UUID courseId + String majorName, + String courseType, + String courseSubtype, + int courseYear ) { } From f31a53e4f130485ef199a373d6eb82755d758cc7 Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Tue, 23 Jun 2026 14:10:19 +0300 Subject: [PATCH 02/15] fixes for Student --- .../uniplan/student/StudentController.java | 38 +++++-------------- .../unilab/uniplan/student/StudentMapper.java | 27 ++++++------- .../uniplan/student/StudentService.java | 17 ++++++--- .../student/dto/StudentResponseDto.java | 9 +---- 4 files changed, 37 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/unilab/uniplan/student/StudentController.java b/src/main/java/org/unilab/uniplan/student/StudentController.java index f10c340d..b383f1a4 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentController.java +++ b/src/main/java/org/unilab/uniplan/student/StudentController.java @@ -18,11 +18,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.unilab.uniplan.course.Course; -import org.unilab.uniplan.course.CourseService; -import org.unilab.uniplan.course.dto.CourseDto; -import org.unilab.uniplan.major.MajorService; -import org.unilab.uniplan.major.dto.MajorDto; import org.unilab.uniplan.student.dto.StudentCourseMajorDto; import org.unilab.uniplan.student.dto.StudentDto; import org.unilab.uniplan.student.dto.StudentRequestDto; @@ -35,9 +30,7 @@ public class StudentController { private final StudentService studentService; - private final CourseService courseService; private final StudentMapper studentMapper; - private final MajorService majorService; @PostMapping public ResponseEntity createStudent(@RequestBody @NotNull @@ -45,12 +38,12 @@ public ResponseEntity createStudent(@RequestBody @NotNull final StudentDto studentDTO = studentMapper.toInternalDto(studentRequestDTO); studentService.createStudent(studentDTO); - CourseDto courseDto = courseService.findCourseById(studentDTO.courseId()); - MajorDto majorDto = majorService.findMajorById(courseDto.majorId()); - StudentResponseDto student = studentMapper.toResponseDto(studentDTO, courseDto, majorDto); + + +// StudentResponseDto student = studentMapper.toResponseDto(new StudentCourseMajorDto()); return ResponseEntity.status(HttpStatus.CREATED) - .body(student); + .body(null); //student } @GetMapping("/{id}") @@ -58,25 +51,14 @@ public ResponseEntity getStudent(@PathVariable @NotNull final UUID id) { StudentDto studentDTO = studentService.findStudentById(id); - CourseDto courseDto = courseService.findCourseById(studentDTO.courseId()); - MajorDto majorDto = majorService.findMajorById(courseDto.majorId()); - final StudentResponseDto studentResponseDTO = studentMapper.toResponseDto(studentDTO, courseDto, majorDto); +// final StudentResponseDto studentResponseDTO = studentMapper.toResponseDto(); - return ResponseEntity.ok(studentResponseDTO); + return ResponseEntity.ok(null); //studentResponseDTO } @GetMapping public List getAllStudents() { - List students = studentService.findAll(); - List responseList = students.stream() - .map(studentDto -> { - CourseDto courseDto = courseService.findCourseById(studentDto.courseId()); - MajorDto majorDto = majorService.findMajorById(courseDto.majorId()); - return studentMapper.toResponseDto(studentDto, courseDto, majorDto); - }) - .toList(); - - return responseList; + return studentService.findAllStudentsWithDetails(); } @GetMapping("/student-course-major/getStudentCourseMajorInfo") @@ -93,10 +75,10 @@ public ResponseEntity updateStudent(@PathVariable @RequestBody @NotNull @Valid final StudentRequestDto studentRequestDTO) { final StudentDto studentDTO = studentMapper.toInternalDto(studentRequestDTO); + StudentDto studentDto = studentService.updateStudent(id, studentDTO); - CourseDto courseDto = courseService.findCourseById(studentDto.courseId()); - MajorDto majorDto = majorService.findMajorById(courseDto.majorId()); - return ResponseEntity.ok(studentMapper.toResponseDto(studentDTO, courseDto, majorDto)); + + return ResponseEntity.ok(null); //studentMapper.toResponseDto() } @DeleteMapping("/{id}") diff --git a/src/main/java/org/unilab/uniplan/student/StudentMapper.java b/src/main/java/org/unilab/uniplan/student/StudentMapper.java index 94681962..778b99b0 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentMapper.java +++ b/src/main/java/org/unilab/uniplan/student/StudentMapper.java @@ -4,8 +4,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; -import org.unilab.uniplan.course.dto.CourseDto; -import org.unilab.uniplan.major.dto.MajorDto; +import org.unilab.uniplan.student.dto.StudentCourseMajorDto; import org.unilab.uniplan.student.dto.StudentDto; import org.unilab.uniplan.student.dto.StudentRequestDto; import org.unilab.uniplan.student.dto.StudentResponseDto; @@ -26,16 +25,18 @@ public interface StudentMapper { @Mapping(target = "id", ignore = true) StudentDto toInternalDto(StudentRequestDto student); - @Mapping(target = "majorName", source = "majorDto.majorName") - @Mapping(target = "courseType", source = "courseDto.courseType") - @Mapping(target = "courseSubtype", source = "courseDto.courseSubtype") - @Mapping(target = "courseYear", source = "courseDto.courseYear") - @Mapping(target = "id", source = "studentDto.id") - @Mapping(target = "firstName", source = "studentDto.firstName") - @Mapping(target = "lastName", source = "studentDto.lastName") - @Mapping(target = "facultyNumber", source = "studentDto.facultyNumber") - StudentResponseDto toResponseDto(StudentDto studentDto, CourseDto courseDto, MajorDto majorDto); - - List toResponseDtoList(List students); + @Mapping(target = "name", expression = "java(toFullName(dto.firstName(), dto.lastName()))") + @Mapping(target = "facultyNumber", source = "facultyNumber") + @Mapping(target = "majorName", source = "majorName") + @Mapping(target = "courseType", source = "courseType") + @Mapping(target = "courseSubtype", source = "courseSubType") + @Mapping(target = "courseYear", source = "courseYear") + StudentResponseDto toResponseDto(StudentCourseMajorDto dto); + + List toResponseDtoList(List students); + + default String toFullName(String firstName, String lastName){ + return firstName +" "+lastName; + } } \ No newline at end of file diff --git a/src/main/java/org/unilab/uniplan/student/StudentService.java b/src/main/java/org/unilab/uniplan/student/StudentService.java index c616dbd3..c26d10ca 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentService.java +++ b/src/main/java/org/unilab/uniplan/student/StudentService.java @@ -10,6 +10,7 @@ import org.unilab.uniplan.exception.ResourceNotFoundException; import org.unilab.uniplan.student.dto.StudentCourseMajorDto; import org.unilab.uniplan.student.dto.StudentDto; +import org.unilab.uniplan.student.dto.StudentResponseDto; @Service @RequiredArgsConstructor @@ -36,6 +37,17 @@ public List findAll() { .stream().map(studentMapper::toDto).toList(); } + public List findAllStudentsWithDetails() { + return studentMapper.toResponseDtoList( + studentRepository.searchStudents("", "", "", "") + ); + } + + public List findStudentCourseMajorInfo(final String firstName, final String lastName, + final String facultyNumber, final String majorName){ + return studentRepository.searchStudents(firstName, lastName, facultyNumber, majorName); + } + @Transactional public StudentDto updateStudent(final UUID id, final StudentDto studentDTO) { return studentRepository.findById(id) @@ -54,11 +66,6 @@ public void deleteStudent(final UUID id) { studentRepository.delete(student); } - public List findStudentCourseMajorInfo(final String firstName, final String lastName, - final String facultyNumber, final String majorName){ - return studentRepository.searchStudents(firstName, lastName, facultyNumber, majorName); - } - private StudentDto updateEntityAndConvertToDto(final StudentDto dto, final Student entity) { studentMapper.updateEntityFromDto(dto, entity); diff --git a/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java b/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java index dc16780a..ad2d99e1 100644 --- a/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java +++ b/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java @@ -1,17 +1,10 @@ package org.unilab.uniplan.student.dto; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; import java.util.UUID; public record StudentResponseDto( UUID id, - @NotNull - @Size(max = 100) - String firstName, - @NotNull - @Size(max = 100) - String lastName, + String name, String facultyNumber, String majorName, String courseType, From 2c79f7f2a94d43016e007799dca6bff4d8a2e01f Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Wed, 24 Jun 2026 10:40:39 +0300 Subject: [PATCH 03/15] fixes for Student --- .../unilab/uniplan/student/StudentMapper.java | 7 +---- .../uniplan/student/StudentRepository.java | 14 +++++++++ .../uniplan/student/StudentService.java | 11 +++++-- .../student/dto/StudentCourseMajorDto.java | 2 +- .../student/dto/StudentRequestDto.java | 7 +++-- .../uniplan/student/StudentServiceTest.java | 29 +++++++++++++++---- 6 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/unilab/uniplan/student/StudentMapper.java b/src/main/java/org/unilab/uniplan/student/StudentMapper.java index 778b99b0..8e62f4d5 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentMapper.java +++ b/src/main/java/org/unilab/uniplan/student/StudentMapper.java @@ -25,12 +25,7 @@ public interface StudentMapper { @Mapping(target = "id", ignore = true) StudentDto toInternalDto(StudentRequestDto student); - @Mapping(target = "name", expression = "java(toFullName(dto.firstName(), dto.lastName()))") - @Mapping(target = "facultyNumber", source = "facultyNumber") - @Mapping(target = "majorName", source = "majorName") - @Mapping(target = "courseType", source = "courseType") - @Mapping(target = "courseSubtype", source = "courseSubType") - @Mapping(target = "courseYear", source = "courseYear") + @Mapping(target = "name", expression = "java(toFullName(dto.firstName(), dto.lastName()))") StudentResponseDto toResponseDto(StudentCourseMajorDto dto); List toResponseDtoList(List students); diff --git a/src/main/java/org/unilab/uniplan/student/StudentRepository.java b/src/main/java/org/unilab/uniplan/student/StudentRepository.java index 85904938..eaa0650f 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentRepository.java +++ b/src/main/java/org/unilab/uniplan/student/StudentRepository.java @@ -1,6 +1,7 @@ package org.unilab.uniplan.student; import java.util.List; +import java.util.Optional; import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -30,4 +31,17 @@ List searchStudents( @Param("facultyNumber") String facultyNumber, @Param("majorName") String majorName ); + + @Query(""" + select new org.unilab.uniplan.student.dto.StudentCourseMajorDto( + s.id, s.firstName, s.lastName, s.facultyNumber, + c.id, c.courseType, c.courseSubtype, c.courseYear, + m.id, m.majorName + ) + from Student s + join s.course c + join c.major m + where s.id = :id + """) + Optional findStudentWithDetailsById(@Param("id") UUID id); } diff --git a/src/main/java/org/unilab/uniplan/student/StudentService.java b/src/main/java/org/unilab/uniplan/student/StudentService.java index c26d10ca..71268394 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentService.java +++ b/src/main/java/org/unilab/uniplan/student/StudentService.java @@ -10,6 +10,7 @@ import org.unilab.uniplan.exception.ResourceNotFoundException; import org.unilab.uniplan.student.dto.StudentCourseMajorDto; import org.unilab.uniplan.student.dto.StudentDto; +import org.unilab.uniplan.student.dto.StudentRequestDto; import org.unilab.uniplan.student.dto.StudentResponseDto; @Service @@ -20,9 +21,13 @@ public class StudentService { private final StudentMapper studentMapper; @Transactional - public StudentDto createStudent(final StudentDto studentDTO) { - final Student student = studentMapper.toEntity(studentDTO); - return saveEntityAndConvertToDto(student); + public StudentResponseDto createStudent(StudentRequestDto request) { + StudentDto studentDto = studentMapper.toInternalDto(request); + Student saved = studentRepository.save(studentMapper.toEntity(studentDto)); + return studentRepository.findStudentWithDetailsById(saved.getId()) + .map(studentMapper::toResponseDto) + .orElseThrow(() -> new ResourceNotFoundException( + STUDENT_NOT_FOUND.getMessage(String.valueOf(saved.getId())))); } public StudentDto findStudentById(final UUID id) { diff --git a/src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java b/src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java index 9dc5a51d..96be4c73 100644 --- a/src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java +++ b/src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java @@ -20,7 +20,7 @@ public record StudentCourseMajorDto( @NotNull String courseType, @NotNull - String courseSubType, + String courseSubtype, @NotNull Byte courseYear, @NotNull diff --git a/src/main/java/org/unilab/uniplan/student/dto/StudentRequestDto.java b/src/main/java/org/unilab/uniplan/student/dto/StudentRequestDto.java index 2f3ee4ae..3f715455 100644 --- a/src/main/java/org/unilab/uniplan/student/dto/StudentRequestDto.java +++ b/src/main/java/org/unilab/uniplan/student/dto/StudentRequestDto.java @@ -1,17 +1,20 @@ package org.unilab.uniplan.student.dto; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import java.util.UUID; public record StudentRequestDto( - @NotNull + @NotBlank @Size(max = 100) String firstName, - @NotNull + @NotBlank @Size(max = 100) String lastName, + @Size(max = 40) String facultyNumber, + @NotNull UUID courseId ) { diff --git a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java index f20e52c6..84a67e01 100644 --- a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java +++ b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java @@ -19,7 +19,10 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.unilab.uniplan.exception.ResourceNotFoundException; +import org.unilab.uniplan.student.dto.StudentCourseMajorDto; import org.unilab.uniplan.student.dto.StudentDto; +import org.unilab.uniplan.student.dto.StudentRequestDto; +import org.unilab.uniplan.student.dto.StudentResponseDto; @ExtendWith(MockitoExtension.class) class StudentServiceTest { @@ -36,25 +39,41 @@ class StudentServiceTest { private UUID studentId; private StudentDto studentDTO; private Student student; + private StudentRequestDto studentRequestDto; + private StudentCourseMajorDto studentCourseMajorDto; + private StudentResponseDto studentResponseDto; @BeforeEach - void beforeAll() { + void beforeEach() { studentId = UUID.randomUUID(); UUID courseId = UUID.randomUUID(); studentDTO = new StudentDto(studentId, "Petar", "Petrov", "2301261005", courseId); + studentRequestDto = new StudentRequestDto("Petar", "Petrov", "2301261005", courseId); + studentCourseMajorDto = new StudentCourseMajorDto( + studentId, "Petar", "Petrov", "2301261005", + courseId, "bachelor", "regular", (byte) 2, + UUID.randomUUID(), "Informatics" + ); + studentResponseDto = new StudentResponseDto( + studentId, "Petar Petrov", "2301261005", "Informatics", "bachelor", "regular", 2 + ); student = new Student(); + student.setId(studentId); } @Test - void createStudentShouldReturnSaveAndReturnStudentDTO() { + void createStudentShouldSaveAndReturnStudentResponseDto() { + when(studentMapper.toInternalDto(studentRequestDto)).thenReturn(studentDTO); when(studentMapper.toEntity(studentDTO)).thenReturn(student); when(studentRepository.save(student)).thenReturn(student); - when(studentMapper.toDto(student)).thenReturn(studentDTO); + when(studentRepository.findStudentWithDetailsById(studentId)).thenReturn(Optional.of(studentCourseMajorDto)); + when(studentMapper.toResponseDto(studentCourseMajorDto)).thenReturn(studentResponseDto); - StudentDto result = studentService.createStudent(studentDTO); + StudentResponseDto result = studentService.createStudent(studentRequestDto); - assertEquals(studentDTO, result); + assertEquals(studentResponseDto, result); verify(studentRepository).save(student); + verify(studentRepository).findStudentWithDetailsById(studentId); } @Test From d16ea9887eef589149da1dca3319eb8fa827713e Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Wed, 24 Jun 2026 11:22:31 +0300 Subject: [PATCH 04/15] adjusted getStudentById and corresponding test --- .../org/unilab/uniplan/student/StudentController.java | 7 +++---- .../org/unilab/uniplan/student/StudentService.java | 6 +++--- .../org/unilab/uniplan/student/StudentServiceTest.java | 10 +++++----- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/unilab/uniplan/student/StudentController.java b/src/main/java/org/unilab/uniplan/student/StudentController.java index b383f1a4..f5501e15 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentController.java +++ b/src/main/java/org/unilab/uniplan/student/StudentController.java @@ -36,7 +36,7 @@ public class StudentController { public ResponseEntity createStudent(@RequestBody @NotNull @Valid final StudentRequestDto studentRequestDTO) { final StudentDto studentDTO = studentMapper.toInternalDto(studentRequestDTO); - studentService.createStudent(studentDTO); +// studentService.createStudent(studentDTO); @@ -50,10 +50,9 @@ public ResponseEntity createStudent(@RequestBody @NotNull public ResponseEntity getStudent(@PathVariable @NotNull final UUID id) { - StudentDto studentDTO = studentService.findStudentById(id); -// final StudentResponseDto studentResponseDTO = studentMapper.toResponseDto(); + StudentResponseDto studentResponseDTO = studentService.findStudentById(id); - return ResponseEntity.ok(null); //studentResponseDTO + return ResponseEntity.ok(studentResponseDTO); } @GetMapping diff --git a/src/main/java/org/unilab/uniplan/student/StudentService.java b/src/main/java/org/unilab/uniplan/student/StudentService.java index 71268394..2a1e44e0 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentService.java +++ b/src/main/java/org/unilab/uniplan/student/StudentService.java @@ -30,9 +30,9 @@ public StudentResponseDto createStudent(StudentRequestDto request) { STUDENT_NOT_FOUND.getMessage(String.valueOf(saved.getId())))); } - public StudentDto findStudentById(final UUID id) { - return studentRepository.findById(id) - .map(studentMapper::toDto) + public StudentResponseDto findStudentById(final UUID id) { + return studentRepository.findStudentWithDetailsById(id) + .map(studentMapper::toResponseDto) .orElseThrow(() -> new ResourceNotFoundException(STUDENT_NOT_FOUND.getMessage( String.valueOf(id)))); } diff --git a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java index 84a67e01..872916b8 100644 --- a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java +++ b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java @@ -77,13 +77,13 @@ void createStudentShouldSaveAndReturnStudentResponseDto() { } @Test - void findStudentByIdShouldReturnStudentDTOIfExists() { - when(studentRepository.findById(studentId)).thenReturn(Optional.of(student)); - when(studentMapper.toDto(student)).thenReturn(studentDTO); + void findStudentByIdShouldReturnStudentResponseDtoIfExists() { + when(studentRepository.findStudentWithDetailsById(studentId)).thenReturn(Optional.of(studentCourseMajorDto)); + when(studentMapper.toResponseDto(studentCourseMajorDto)).thenReturn(studentResponseDto); - StudentDto result = studentService.findStudentById(studentId); + StudentResponseDto result = studentService.findStudentById(studentId); - assertEquals(studentDTO, result); + assertEquals(studentResponseDto, result); } @Test From 90ef9818a2f2ae77e1aacf5bcdfa94d5fdc30901 Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Wed, 24 Jun 2026 22:51:29 +0300 Subject: [PATCH 05/15] changes to DTOs and Mapper to prepare for Edit --- .../unilab/uniplan/student/StudentMapper.java | 3 ++- .../student/dto/StudentCourseMajorDto.java | 2 +- .../student/dto/StudentResponseDto.java | 3 ++- .../uniplan/student/StudentServiceTest.java | 22 ++++++++++++------- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/unilab/uniplan/student/StudentMapper.java b/src/main/java/org/unilab/uniplan/student/StudentMapper.java index 8e62f4d5..ab407c74 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentMapper.java +++ b/src/main/java/org/unilab/uniplan/student/StudentMapper.java @@ -19,12 +19,13 @@ public interface StudentMapper { StudentDto toDto(Student student); @Mapping(target = "id", ignore = true) - @Mapping(source = "courseId", target = "course.id") + @Mapping(target = "course", ignore = true) void updateEntityFromDto(StudentDto studentDto, @MappingTarget Student student); @Mapping(target = "id", ignore = true) StudentDto toInternalDto(StudentRequestDto student); + @Mapping(target = "id", source = "id") @Mapping(target = "name", expression = "java(toFullName(dto.firstName(), dto.lastName()))") StudentResponseDto toResponseDto(StudentCourseMajorDto dto); diff --git a/src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java b/src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java index 96be4c73..300a2059 100644 --- a/src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java +++ b/src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java @@ -6,7 +6,7 @@ import java.util.UUID; public record StudentCourseMajorDto( - UUID studentId, + UUID id, @NotBlank @Size(max = 100) String firstName, diff --git a/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java b/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java index ad2d99e1..ee4f1377 100644 --- a/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java +++ b/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java @@ -6,10 +6,11 @@ public record StudentResponseDto( UUID id, String name, String facultyNumber, + UUID majorId, String majorName, String courseType, String courseSubtype, - int courseYear + Byte courseYear ) { } diff --git a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java index 872916b8..2d3d8d89 100644 --- a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java +++ b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java @@ -47,15 +47,17 @@ class StudentServiceTest { void beforeEach() { studentId = UUID.randomUUID(); UUID courseId = UUID.randomUUID(); + UUID majorId = UUID.randomUUID(); studentDTO = new StudentDto(studentId, "Petar", "Petrov", "2301261005", courseId); studentRequestDto = new StudentRequestDto("Petar", "Petrov", "2301261005", courseId); studentCourseMajorDto = new StudentCourseMajorDto( studentId, "Petar", "Petrov", "2301261005", courseId, "bachelor", "regular", (byte) 2, - UUID.randomUUID(), "Informatics" + majorId, "Informatics" ); studentResponseDto = new StudentResponseDto( - studentId, "Petar Petrov", "2301261005", "Informatics", "bachelor", "regular", 2 + studentId, "Petar Petrov", "2301261005", majorId, "Informatics", "bachelor", "regular", + (byte) 2 ); student = new Student(); student.setId(studentId); @@ -109,23 +111,27 @@ void findAllShouldReturnMappedStudentDTOList() { } @Test - void updateStudentShouldReturnUpdatedStudentDTOIfExists() { + void updateStudentShouldReturnUpdatedStudentResponseDtoIfExists() { when(studentRepository.findById(studentId)).thenReturn(Optional.of(student)); + when(studentMapper.toInternalDto(studentRequestDto)).thenReturn(studentDTO); doNothing().when(studentMapper).updateEntityFromDto(studentDTO, student); when(studentRepository.save(student)).thenReturn(student); - when(studentMapper.toDto(student)).thenReturn(studentDTO); + when(studentRepository.findStudentWithDetailsById(studentId)).thenReturn(Optional.of(studentCourseMajorDto)); + when(studentMapper.toResponseDto(studentCourseMajorDto)).thenReturn(studentResponseDto); - StudentDto result = studentService.updateStudent(studentId, studentDTO); + StudentResponseDto result = studentService.updateStudent(studentId, studentRequestDto); - assertEquals(studentDTO, result); + assertEquals(studentResponseDto, result); verify(studentRepository).save(student); + verify(studentRepository).findStudentWithDetailsById(studentId); } @Test - void updateStudentShouldReturnEmptyIfNotFound() { + void updateStudentShouldThrowIfNotFound() { when(studentRepository.findById(studentId)).thenReturn(Optional.empty()); - ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, () -> studentService.updateStudent(studentId, studentDTO)); + ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, + () -> studentService.updateStudent(studentId, studentRequestDto)); assertTrue(exception.getMessage().contains(String.valueOf(studentId))); verify(studentRepository, never()).save(any()); From 57be802e8f6de5c24c1611a1fc1a1ecf2cbad697 Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Thu, 25 Jun 2026 12:38:02 +0300 Subject: [PATCH 06/15] added soft delete logic to Student --- src/main/java/org/unilab/uniplan/student/Student.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/unilab/uniplan/student/Student.java b/src/main/java/org/unilab/uniplan/student/Student.java index 428d2cdd..8117dbae 100644 --- a/src/main/java/org/unilab/uniplan/student/Student.java +++ b/src/main/java/org/unilab/uniplan/student/Student.java @@ -10,11 +10,14 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SoftDelete; +import org.hibernate.annotations.SoftDeleteType; import org.unilab.uniplan.common.model.Person; import org.unilab.uniplan.course.Course; @Entity @Table(name = "student") +@SoftDelete(strategy = SoftDeleteType.DELETED, columnName = "is_deleted") @Getter @Setter @NoArgsConstructor From 8bbfef04d1684e127f9489b9d9feae2dd6c7c353 Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Mon, 29 Jun 2026 13:15:15 +0300 Subject: [PATCH 07/15] added Validator interface, created StudentValidator and StudentWebFacade, made changes to StudentMapper, updated StudentServiceTest and created tests for new classes --- .../uniplan/common/model/Validator.java | 5 + .../uniplan/student/StudentController.java | 44 ++----- .../unilab/uniplan/student/StudentMapper.java | 11 +- .../uniplan/student/StudentService.java | 67 +++------- .../uniplan/student/StudentValidator.java | 25 ++++ .../uniplan/student/StudentWebFacade.java | 65 ++++++++++ .../uniplan/student/StudentServiceTest.java | 99 +++----------- .../uniplan/student/StudentValidatorTest.java | 56 ++++++++ .../uniplan/student/StudentWebFacadeTest.java | 122 ++++++++++++++++++ 9 files changed, 324 insertions(+), 170 deletions(-) create mode 100644 src/main/java/org/unilab/uniplan/common/model/Validator.java create mode 100644 src/main/java/org/unilab/uniplan/student/StudentValidator.java create mode 100644 src/main/java/org/unilab/uniplan/student/StudentWebFacade.java create mode 100644 src/test/java/org/unilab/uniplan/student/StudentValidatorTest.java create mode 100644 src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java diff --git a/src/main/java/org/unilab/uniplan/common/model/Validator.java b/src/main/java/org/unilab/uniplan/common/model/Validator.java new file mode 100644 index 00000000..e4207496 --- /dev/null +++ b/src/main/java/org/unilab/uniplan/common/model/Validator.java @@ -0,0 +1,5 @@ +package org.unilab.uniplan.common.model; + +public interface Validator { + void validate(final T entity); +} diff --git a/src/main/java/org/unilab/uniplan/student/StudentController.java b/src/main/java/org/unilab/uniplan/student/StudentController.java index b21f45bb..869dda1a 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentController.java +++ b/src/main/java/org/unilab/uniplan/student/StudentController.java @@ -5,7 +5,6 @@ import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.UUID; -import jakarta.validation.constraints.Size; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -16,10 +15,7 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.unilab.uniplan.student.dto.StudentCourseMajorDto; -import org.unilab.uniplan.student.dto.StudentDto; import org.unilab.uniplan.student.dto.StudentRequestDto; import org.unilab.uniplan.student.dto.StudentResponseDto; @@ -29,54 +25,38 @@ @Tag(name = "Students", description = "Manage students, including faculty numbers and enrollment in course") public class StudentController { - private final StudentService studentService; - private final StudentMapper studentMapper; + private final StudentWebFacade studentWebFacade; @PostMapping - public ResponseEntity createStudent(@RequestBody @NotNull - @Valid final StudentRequestDto studentRequestDTO) { - final StudentDto studentDTO = studentMapper.toInternalDto(studentRequestDTO); - studentService.createStudent(studentDTO); - return ResponseEntity.status(HttpStatus.CREATED) - .body(studentMapper.toResponseDto(studentDTO)); + public ResponseEntity createStudent(@RequestBody @NotNull @Valid final StudentRequestDto studentRequestDTO) { + studentWebFacade.createStudent(studentRequestDTO); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @GetMapping("/{id}") - public ResponseEntity getStudent(@PathVariable + public ResponseEntity getStudentById(@PathVariable @NotNull final UUID id) { - final StudentResponseDto studentResponseDTO = studentMapper.toResponseDto(studentService.findStudentById( - id)); - - return ResponseEntity.ok(studentResponseDTO); + return ResponseEntity.ok(studentWebFacade.getStudentById(id)); } @GetMapping - public List getAllStudents() { - return studentMapper.toResponseDtoList(studentService.findAll()); - } - - @GetMapping("/student-course-major/getStudentCourseMajorInfo") - public List getStudentCourseMajorInfo(@RequestParam(required = false) @Size(max = 100) final String firstName, - @RequestParam(required = false) @Size(max = 100) final String lastName, - @RequestParam(required = false) final String facultyNumber, - @RequestParam(required = false) @Size(max = 200) final String majorName){ - return studentService.findStudentCourseMajorInfo(firstName, lastName, facultyNumber, majorName); + public ResponseEntity> getAllStudents() { + return ResponseEntity.ok(studentWebFacade.getAllStudents()); } @PutMapping("/{id}") - public ResponseEntity updateStudent(@PathVariable + public ResponseEntity updateStudent(@PathVariable @NotNull final UUID id, @RequestBody @NotNull @Valid final StudentRequestDto studentRequestDTO) { - final StudentDto studentDTO = studentMapper.toInternalDto(studentRequestDTO); - studentService.updateStudent(id, studentDTO); - return ResponseEntity.ok(studentMapper.toResponseDto(studentDTO)); + studentWebFacade.updateStudent(id, studentRequestDTO); + return ResponseEntity.noContent().build(); } @DeleteMapping("/{id}") public ResponseEntity deleteStudent(@PathVariable @NotNull final UUID id) { - studentService.deleteStudent(id); + studentWebFacade.deleteStudent(id); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/org/unilab/uniplan/student/StudentMapper.java b/src/main/java/org/unilab/uniplan/student/StudentMapper.java index 440e5043..0994e7a3 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentMapper.java +++ b/src/main/java/org/unilab/uniplan/student/StudentMapper.java @@ -12,21 +12,22 @@ public interface StudentMapper { @Mapping(source = "courseId", target = "course.id") - Student toEntity(StudentDto studentDto); + @Mapping(target = "id", ignore = true) + Student toEntity(StudentRequestDto requestDto); @Mapping(source = "course.id", target = "courseId") StudentDto toDto(Student student); @Mapping(target = "id", ignore = true) @Mapping(source = "courseId", target = "course.id") - void updateEntityFromDto(StudentDto studentDto, @MappingTarget Student student); + void updateEntity(StudentRequestDto requestDto, @MappingTarget Student student); @Mapping(target = "id", ignore = true) StudentDto toInternalDto(StudentRequestDto student); - @Mapping(source = "courseId", target = "courseId") - StudentResponseDto toResponseDto(StudentDto studentDto); + @Mapping(source = "course.id", target = "courseId") + StudentResponseDto toResponseDto(Student student); - List toResponseDtoList(List students); + List toResponseDtoList(List students); } \ No newline at end of file diff --git a/src/main/java/org/unilab/uniplan/student/StudentService.java b/src/main/java/org/unilab/uniplan/student/StudentService.java index c616dbd3..cd3dc4e6 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentService.java +++ b/src/main/java/org/unilab/uniplan/student/StudentService.java @@ -1,72 +1,35 @@ package org.unilab.uniplan.student; -import static org.unilab.uniplan.utils.ErrorConstants.STUDENT_NOT_FOUND; - -import jakarta.transaction.Transactional; import java.util.List; +import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.unilab.uniplan.exception.ResourceNotFoundException; -import org.unilab.uniplan.student.dto.StudentCourseMajorDto; -import org.unilab.uniplan.student.dto.StudentDto; +import org.unilab.uniplan.common.model.BaseService; @Service @RequiredArgsConstructor -public class StudentService { +public class StudentService implements BaseService { private final StudentRepository studentRepository; - private final StudentMapper studentMapper; - - @Transactional - public StudentDto createStudent(final StudentDto studentDTO) { - final Student student = studentMapper.toEntity(studentDTO); - return saveEntityAndConvertToDto(student); - } - - public StudentDto findStudentById(final UUID id) { - return studentRepository.findById(id) - .map(studentMapper::toDto) - .orElseThrow(() -> new ResourceNotFoundException(STUDENT_NOT_FOUND.getMessage( - String.valueOf(id)))); - } - - public List findAll() { - return studentRepository.findAll() - .stream().map(studentMapper::toDto).toList(); - } - - @Transactional - public StudentDto updateStudent(final UUID id, final StudentDto studentDTO) { - return studentRepository.findById(id) - .map(existingStudent -> updateEntityAndConvertToDto( - studentDTO, - existingStudent)) - .orElseThrow(() -> new ResourceNotFoundException(STUDENT_NOT_FOUND.getMessage( - String.valueOf(id)))); - } - @Transactional - public void deleteStudent(final UUID id) { - final Student student = studentRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException( - STUDENT_NOT_FOUND.getMessage(String.valueOf(id)))); - studentRepository.delete(student); + @Override + public void save(final Student entity) { + studentRepository.save(entity); } - public List findStudentCourseMajorInfo(final String firstName, final String lastName, - final String facultyNumber, final String majorName){ - return studentRepository.searchStudents(firstName, lastName, facultyNumber, majorName); + @Override + public List getAll() { + return studentRepository.findAll(); } - private StudentDto updateEntityAndConvertToDto(final StudentDto dto, - final Student entity) { - studentMapper.updateEntityFromDto(dto, entity); - return saveEntityAndConvertToDto(entity); + @Override + public Optional getById(final UUID id) { + return studentRepository.findById(id); } - private StudentDto saveEntityAndConvertToDto(final Student entity) { - final Student savedEntity = studentRepository.save(entity); - return studentMapper.toDto(savedEntity); + @Override + public void delete(final Student entity) { + studentRepository.delete(entity); } } diff --git a/src/main/java/org/unilab/uniplan/student/StudentValidator.java b/src/main/java/org/unilab/uniplan/student/StudentValidator.java new file mode 100644 index 00000000..17ae03f8 --- /dev/null +++ b/src/main/java/org/unilab/uniplan/student/StudentValidator.java @@ -0,0 +1,25 @@ +package org.unilab.uniplan.student; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.unilab.uniplan.common.model.Validator; +import org.unilab.uniplan.course.CourseRepository; +import org.unilab.uniplan.exception.ResourceNotFoundException; + +import java.util.UUID; + +import static org.unilab.uniplan.utils.ErrorConstants.COURSE_NOT_FOUND; + +@Component +@RequiredArgsConstructor +public class StudentValidator implements Validator { + private final CourseRepository courseRepository; + + @Override + public void validate(final Student entity) { + UUID id = entity.getCourse().getId(); + if (!courseRepository.existsById(id)){ + throw new ResourceNotFoundException(COURSE_NOT_FOUND.getMessage(id.toString())); + } + } +} diff --git a/src/main/java/org/unilab/uniplan/student/StudentWebFacade.java b/src/main/java/org/unilab/uniplan/student/StudentWebFacade.java new file mode 100644 index 00000000..6b8e79b8 --- /dev/null +++ b/src/main/java/org/unilab/uniplan/student/StudentWebFacade.java @@ -0,0 +1,65 @@ +package org.unilab.uniplan.student; + +import org.springframework.transaction.annotation.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.unilab.uniplan.exception.ResourceNotFoundException; +import org.unilab.uniplan.student.dto.StudentRequestDto; +import org.unilab.uniplan.student.dto.StudentResponseDto; +import java.util.List; +import java.util.UUID; + +import static org.unilab.uniplan.utils.ErrorConstants.STUDENT_NOT_FOUND; + +@Component +@Slf4j +@RequiredArgsConstructor +public class StudentWebFacade { + + private final StudentMapper studentMapper; + private final StudentService studentService; + private final StudentValidator studentValidator; + + @Transactional + public void createStudent(final StudentRequestDto request){ + final Student student = studentMapper.toEntity(request); + studentValidator.validate(student); + studentService.save(student); + } + + @Transactional(readOnly = true) + public List getAllStudents(){ + return studentMapper.toResponseDtoList(studentService.getAll()); + } + + @Transactional(readOnly = true) + public StudentResponseDto getStudentById(final UUID id){ + final Student student = getStudentOrThrow(id); + return studentMapper.toResponseDto(student); + } + + private Student getStudentOrThrow(final UUID id){ + return studentService.getById(id) + .orElseThrow(() -> new ResourceNotFoundException( + STUDENT_NOT_FOUND.getMessage(String.valueOf(id)) + )); + } + + @Transactional + public void updateStudent(final UUID id, + final StudentRequestDto request){ + final Student student = getStudentOrThrow(id); + studentValidator.validate(student); + studentMapper.updateEntity(request, student); + studentService.save(student); + log.info("updated student with ID: {}", student.getId()); + } + + @Transactional + public void deleteStudent(final UUID id){ + final Student student = getStudentOrThrow(id); + studentService.delete(student); + log.info("deleted student with ID: {}", id); + } +} diff --git a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java index f20e52c6..6018b209 100644 --- a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java +++ b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java @@ -1,11 +1,7 @@ package org.unilab.uniplan.student; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -18,8 +14,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.unilab.uniplan.exception.ResourceNotFoundException; -import org.unilab.uniplan.student.dto.StudentDto; @ExtendWith(MockitoExtension.class) class StudentServiceTest { @@ -27,108 +21,51 @@ class StudentServiceTest { @Mock private StudentRepository studentRepository; - @Mock - private StudentMapper studentMapper; - @InjectMocks private StudentService studentService; - private UUID studentId; - private StudentDto studentDTO; private Student student; @BeforeEach - void beforeAll() { - studentId = UUID.randomUUID(); - UUID courseId = UUID.randomUUID(); - studentDTO = new StudentDto(studentId, "Petar", "Petrov", "2301261005", courseId); + void setUp() { student = new Student(); } @Test - void createStudentShouldReturnSaveAndReturnStudentDTO() { - when(studentMapper.toEntity(studentDTO)).thenReturn(student); - when(studentRepository.save(student)).thenReturn(student); - when(studentMapper.toDto(student)).thenReturn(studentDTO); - - StudentDto result = studentService.createStudent(studentDTO); - - assertEquals(studentDTO, result); + void saveShouldDelegateToRepository() { + studentService.save(student); verify(studentRepository).save(student); } @Test - void findStudentByIdShouldReturnStudentDTOIfExists() { - when(studentRepository.findById(studentId)).thenReturn(Optional.of(student)); - when(studentMapper.toDto(student)).thenReturn(studentDTO); - - StudentDto result = studentService.findStudentById(studentId); - - assertEquals(studentDTO, result); - } - - @Test - void findStudentByIdShouldReturnEmptyIfNotExists() { - when(studentRepository.findById(studentId)).thenReturn(Optional.empty()); - - ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, () -> studentService.findStudentById(studentId)); - - assertTrue(exception.getMessage().contains(String.valueOf(studentId))); - } - - @Test - void findAllShouldReturnMappedStudentDTOList() { - List students = List.of(student); - when(studentRepository.findAll()).thenReturn(students); - when(studentMapper.toDto(any(Student.class))).thenReturn(studentDTO); + void getByIdShouldReturnStudentIfExists() { + UUID id = UUID.randomUUID(); + when(studentRepository.findById(id)).thenReturn(Optional.of(student)); - List result = studentService.findAll(); + Optional result = studentService.getById(id); - assertEquals(1, result.size()); - assertEquals(studentDTO, result.getFirst()); - verify(studentRepository).findAll(); + assertTrue(result.isPresent()); + assertEquals(student, result.get()); } @Test - void updateStudentShouldReturnUpdatedStudentDTOIfExists() { - when(studentRepository.findById(studentId)).thenReturn(Optional.of(student)); - doNothing().when(studentMapper).updateEntityFromDto(studentDTO, student); - when(studentRepository.save(student)).thenReturn(student); - when(studentMapper.toDto(student)).thenReturn(studentDTO); - - StudentDto result = studentService.updateStudent(studentId, studentDTO); + void getByIdShouldReturnEmptyIfNotExists() { + UUID id = UUID.randomUUID(); + when(studentRepository.findById(id)).thenReturn(Optional.empty()); - assertEquals(studentDTO, result); - verify(studentRepository).save(student); + assertTrue(studentService.getById(id).isEmpty()); } @Test - void updateStudentShouldReturnEmptyIfNotFound() { - when(studentRepository.findById(studentId)).thenReturn(Optional.empty()); - - ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, () -> studentService.updateStudent(studentId, studentDTO)); + void getAllShouldReturnAllStudents() { + when(studentRepository.findAll()).thenReturn(List.of(student)); - assertTrue(exception.getMessage().contains(String.valueOf(studentId))); - verify(studentRepository, never()).save(any()); + assertEquals(1, studentService.getAll().size()); } @Test - void deleteStudentShouldRemoveStudentDTOIfExists() { - when(studentRepository.findById(studentId)).thenReturn(Optional.of(student)); - - studentService.deleteStudent(studentId); - + void deleteShouldDelegateToRepository() { + studentService.delete(student); verify(studentRepository).delete(student); } - - @Test - void deleteStudentShouldThrowIfNotFound() { - when(studentRepository.findById(studentId)).thenReturn(Optional.empty()); - - ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, - () -> studentService.deleteStudent(studentId)); - - assertTrue(exception.getMessage().contains(String.valueOf(studentId))); - verify(studentRepository, never()).delete(any()); - } } diff --git a/src/test/java/org/unilab/uniplan/student/StudentValidatorTest.java b/src/test/java/org/unilab/uniplan/student/StudentValidatorTest.java new file mode 100644 index 00000000..e8bc21f3 --- /dev/null +++ b/src/test/java/org/unilab/uniplan/student/StudentValidatorTest.java @@ -0,0 +1,56 @@ +package org.unilab.uniplan.student; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.unilab.uniplan.course.Course; +import org.unilab.uniplan.course.CourseRepository; +import org.unilab.uniplan.exception.ResourceNotFoundException; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class StudentValidatorTest { + + @Mock + private CourseRepository courseRepository; + + @InjectMocks + private StudentValidator studentValidator; + + private Student student; + private UUID courseId; + + @BeforeEach + void setUp() { + courseId = UUID.randomUUID(); + Course course = new Course(); + course.setId(courseId); + student = new Student(); + student.setCourse(course); + } + + @Test + void validateShouldPassWhenCourseExists() { + when(courseRepository.existsById(courseId)).thenReturn(true); + + assertDoesNotThrow(() -> studentValidator.validate(student)); + } + + @Test + void validateShouldThrowWhenCourseNotFound() { + when(courseRepository.existsById(courseId)).thenReturn(false); + + ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, + () -> studentValidator.validate(student)); + + assertTrue(exception.getMessage().contains(courseId.toString())); + } +} diff --git a/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java b/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java new file mode 100644 index 00000000..8fafcff8 --- /dev/null +++ b/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java @@ -0,0 +1,122 @@ +package org.unilab.uniplan.student; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.unilab.uniplan.exception.ResourceNotFoundException; +import org.unilab.uniplan.student.dto.StudentRequestDto; +import org.unilab.uniplan.student.dto.StudentResponseDto; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class StudentWebFacadeTest { + + @Mock + private StudentMapper studentMapper; + + @Mock + private StudentService studentService; + + @Mock + private StudentValidator studentValidator; + + @InjectMocks + private StudentWebFacade studentWebFacade; + + private UUID studentId; + private UUID courseId; + private Student student; + private StudentRequestDto requestDto; + private StudentResponseDto responseDto; + + @BeforeEach + void setUp() { + studentId = UUID.randomUUID(); + courseId = UUID.randomUUID(); + student = new Student(); + requestDto = new StudentRequestDto("Petar", "Petrov", "2301261005", courseId); + responseDto = new StudentResponseDto(studentId, "Petar", "Petrov", "2301261005", courseId); + } + + @Test + void createStudentShouldMapValidateAndSave() { + when(studentMapper.toEntity(requestDto)).thenReturn(student); + + studentWebFacade.createStudent(requestDto); + + InOrder order = inOrder(studentMapper, studentValidator, studentService); + order.verify(studentMapper).toEntity(requestDto); + order.verify(studentValidator).validate(student); + order.verify(studentService).save(student); + } + + @Test + void updateStudentShouldThrowIfNotFound() { + when(studentService.getById(studentId)).thenReturn(Optional.empty()); + + assertThrows(ResourceNotFoundException.class, + () -> studentWebFacade.updateStudent(studentId, requestDto)); + + verify(studentService, never()).save(any()); + } + + @Test + void updateStudentShouldValidateAndSave() { + when(studentService.getById(studentId)).thenReturn(Optional.of(student)); + + studentWebFacade.updateStudent(studentId, requestDto); + + InOrder order = inOrder(studentValidator, studentMapper, studentService); + order.verify(studentValidator).validate(student); + order.verify(studentMapper).updateEntity(requestDto, student); + order.verify(studentService).save(student); + } + + @Test + void getStudentByIdShouldReturnMappedDto() { + when(studentService.getById(studentId)).thenReturn(Optional.of(student)); + when(studentMapper.toResponseDto(student)).thenReturn(responseDto); + + assertEquals(responseDto, studentWebFacade.getStudentById(studentId)); + } + + @Test + void getStudentByIdShouldThrowIfNotFound() { + when(studentService.getById(studentId)).thenReturn(Optional.empty()); + + assertThrows(ResourceNotFoundException.class, + () -> studentWebFacade.getStudentById(studentId)); + } + + @Test + void deleteStudentShouldDelegateIfExists() { + when(studentService.getById(studentId)).thenReturn(Optional.of(student)); + + studentWebFacade.deleteStudent(studentId); + + verify(studentService).delete(student); + } + + @Test + void deleteStudentShouldThrowIfNotFound() { + when(studentService.getById(studentId)).thenReturn(Optional.empty()); + + assertThrows(ResourceNotFoundException.class, + () -> studentWebFacade.deleteStudent(studentId)); + + verify(studentService, never()).delete(any()); + } +} From 312f559b3147353d9633392e3a634a0063d6d670 Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Mon, 29 Jun 2026 14:00:14 +0300 Subject: [PATCH 08/15] fixed leftover update from merge conflict --- .../java/org/unilab/uniplan/student/StudentController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/unilab/uniplan/student/StudentController.java b/src/main/java/org/unilab/uniplan/student/StudentController.java index ba867762..869dda1a 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentController.java +++ b/src/main/java/org/unilab/uniplan/student/StudentController.java @@ -49,9 +49,8 @@ public ResponseEntity updateStudent(@PathVariable @NotNull final UUID id, @RequestBody @NotNull @Valid final StudentRequestDto studentRequestDTO) { - final StudentDto studentDTO = studentMapper.toInternalDto(studentRequestDTO); - studentService.updateStudent(id, studentDTO); - return ResponseEntity.ok(studentMapper.toResponseDto(studentDTO)); + studentWebFacade.updateStudent(id, studentRequestDTO); + return ResponseEntity.noContent().build(); } @DeleteMapping("/{id}") From 7d85e694010fc9d2dc9f444bb68ab3f9ea9e9e42 Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Mon, 29 Jun 2026 14:00:50 +0300 Subject: [PATCH 09/15] removed toInternalDto --- src/main/java/org/unilab/uniplan/student/StudentMapper.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/unilab/uniplan/student/StudentMapper.java b/src/main/java/org/unilab/uniplan/student/StudentMapper.java index 0994e7a3..46148a13 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentMapper.java +++ b/src/main/java/org/unilab/uniplan/student/StudentMapper.java @@ -22,9 +22,6 @@ public interface StudentMapper { @Mapping(source = "courseId", target = "course.id") void updateEntity(StudentRequestDto requestDto, @MappingTarget Student student); - @Mapping(target = "id", ignore = true) - StudentDto toInternalDto(StudentRequestDto student); - @Mapping(source = "course.id", target = "courseId") StudentResponseDto toResponseDto(Student student); From 4f6a1cb99095439bc10317d82f10e08e26b96461 Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Tue, 30 Jun 2026 11:40:01 +0300 Subject: [PATCH 10/15] changed Student Mapper mappings and Repository queries to adjust for Facade layer, added NotBlank validation to RequestDto and switched out Service repository calls --- .../unilab/uniplan/student/StudentMapper.java | 13 ++++-- .../uniplan/student/StudentRepository.java | 42 +++++-------------- .../uniplan/student/StudentService.java | 4 +- .../student/dto/StudentRequestDto.java | 1 + 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/unilab/uniplan/student/StudentMapper.java b/src/main/java/org/unilab/uniplan/student/StudentMapper.java index 0aad65de..2c8afa79 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentMapper.java +++ b/src/main/java/org/unilab/uniplan/student/StudentMapper.java @@ -19,13 +19,20 @@ public interface StudentMapper { StudentDto toDto(Student student); @Mapping(target = "id", ignore = true) - @Mapping(source = "courseId", target = "course.id") + @Mapping(target = "course", ignore = true) void updateEntity(StudentRequestDto requestDto, @MappingTarget Student student); - @Mapping(source = "course.id", target = "courseId") - @Mapping(target = "name", expression = "java(toFullName(dto.firstName(), dto.lastName()))") + @Mapping(target = "name", expression = "java(toFullName(student.getFirstName(), student.getLastName()))") + @Mapping(source = "course.major.id", target = "majorId") + @Mapping(source = "course.major.majorName", target = "majorName") + @Mapping(source = "course.courseType", target="courseType") + @Mapping(source = "course.courseSubtype", target = "courseSubtype") + @Mapping(source = "course.courseYear", target = "courseYear") StudentResponseDto toResponseDto(Student student); List toResponseDtoList(List students); + default String toFullName(String firstName, String lastName) { + return firstName + " " + lastName; + } } \ No newline at end of file diff --git a/src/main/java/org/unilab/uniplan/student/StudentRepository.java b/src/main/java/org/unilab/uniplan/student/StudentRepository.java index eaa0650f..2850ef11 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentRepository.java +++ b/src/main/java/org/unilab/uniplan/student/StudentRepository.java @@ -7,41 +7,21 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import org.unilab.uniplan.student.dto.StudentCourseMajorDto; @Repository public interface StudentRepository extends JpaRepository { @Query(""" - select new org.unilab.uniplan.student.dto.StudentCourseMajorDto( - s.id, s.firstName, s.lastName, s.facultyNumber, - c.id, c.courseType, c.courseSubtype, c.courseYear, - m.id, m.majorName - ) - from Student s - join s.course c - join c.major m - where (:firstName is null or lower(s.firstName) like lower(concat('%', :firstName, '%'))) - and (:lastName is null or lower(s.lastName) like lower(concat('%', :lastName, '%'))) - and (:facultyNumber is null or s.facultyNumber like concat('%', :facultyNumber, '%')) - and (:majorName is null or lower(m.majorName) like lower(concat('%', :majorName, '%'))) - """) - List searchStudents( - @Param("firstName") String firstName, - @Param("lastName") String lastName, - @Param("facultyNumber") String facultyNumber, - @Param("majorName") String majorName - ); + SELECT s FROM Student s + JOIN FETCH s.course c + JOIN FETCH c.major m + """) + List findAllWithCourseAndMajor(); @Query(""" - select new org.unilab.uniplan.student.dto.StudentCourseMajorDto( - s.id, s.firstName, s.lastName, s.facultyNumber, - c.id, c.courseType, c.courseSubtype, c.courseYear, - m.id, m.majorName - ) - from Student s - join s.course c - join c.major m - where s.id = :id - """) - Optional findStudentWithDetailsById(@Param("id") UUID id); + SELECT s FROM Student s + JOIN FETCH s.course c + JOIN FETCH c.major m + WHERE s.id = :id + """) + Optional findByIdWithCourseAndMajor(@Param("id") UUID id); } diff --git a/src/main/java/org/unilab/uniplan/student/StudentService.java b/src/main/java/org/unilab/uniplan/student/StudentService.java index cd3dc4e6..5c14855a 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentService.java +++ b/src/main/java/org/unilab/uniplan/student/StudentService.java @@ -20,12 +20,12 @@ public void save(final Student entity) { @Override public List getAll() { - return studentRepository.findAll(); + return studentRepository.findAllWithCourseAndMajor(); } @Override public Optional getById(final UUID id) { - return studentRepository.findById(id); + return studentRepository.findByIdWithCourseAndMajor(id); } @Override diff --git a/src/main/java/org/unilab/uniplan/student/dto/StudentRequestDto.java b/src/main/java/org/unilab/uniplan/student/dto/StudentRequestDto.java index 3f715455..75699ced 100644 --- a/src/main/java/org/unilab/uniplan/student/dto/StudentRequestDto.java +++ b/src/main/java/org/unilab/uniplan/student/dto/StudentRequestDto.java @@ -12,6 +12,7 @@ public record StudentRequestDto( @NotBlank @Size(max = 100) String lastName, + @NotBlank @Size(max = 40) String facultyNumber, @NotNull From a39fd3d1289c99737d5309831202e8dfe001fae6 Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Tue, 30 Jun 2026 11:54:21 +0300 Subject: [PATCH 11/15] adjusted StudentWebFacadeTest test mock data --- .../uniplan/student/StudentWebFacadeTest.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java b/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java index 8fafcff8..624dcd6c 100644 --- a/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java +++ b/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java @@ -38,6 +38,7 @@ class StudentWebFacadeTest { private UUID studentId; private UUID courseId; + private UUID majorId; private Student student; private StudentRequestDto requestDto; private StudentResponseDto responseDto; @@ -46,9 +47,23 @@ class StudentWebFacadeTest { void setUp() { studentId = UUID.randomUUID(); courseId = UUID.randomUUID(); + majorId = UUID.randomUUID(); student = new Student(); - requestDto = new StudentRequestDto("Petar", "Petrov", "2301261005", courseId); - responseDto = new StudentResponseDto(studentId, "Petar", "Petrov", "2301261005", courseId); + requestDto = new StudentRequestDto( + "Petar", + "Petrov", + "2301261005", + courseId); + responseDto = new StudentResponseDto( + studentId, + "Petar Petrov", + "2301261005", + majorId, + "Software Engineering", + "FULL_TIME", + "редовно", + (byte) 2 + ); } @Test From 60ed996ec7b502e54294d42cb436b3b155f4b9ee Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Tue, 30 Jun 2026 12:49:31 +0300 Subject: [PATCH 12/15] adjusted StudentServiceTest repository calls --- .../java/org/unilab/uniplan/student/StudentServiceTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java index 6018b209..da37cdc0 100644 --- a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java +++ b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java @@ -40,7 +40,7 @@ void saveShouldDelegateToRepository() { @Test void getByIdShouldReturnStudentIfExists() { UUID id = UUID.randomUUID(); - when(studentRepository.findById(id)).thenReturn(Optional.of(student)); + when(studentRepository.findByIdWithCourseAndMajor(id)).thenReturn(Optional.of(student)); Optional result = studentService.getById(id); @@ -51,14 +51,14 @@ void getByIdShouldReturnStudentIfExists() { @Test void getByIdShouldReturnEmptyIfNotExists() { UUID id = UUID.randomUUID(); - when(studentRepository.findById(id)).thenReturn(Optional.empty()); + when(studentRepository.findByIdWithCourseAndMajor(id)).thenReturn(Optional.empty()); assertTrue(studentService.getById(id).isEmpty()); } @Test void getAllShouldReturnAllStudents() { - when(studentRepository.findAll()).thenReturn(List.of(student)); + when(studentRepository.findAllWithCourseAndMajor()).thenReturn(List.of(student)); assertEquals(1, studentService.getAll().size()); } From c164387d0b610337beaf0a737e0597a0e6706a57 Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Tue, 30 Jun 2026 15:11:31 +0300 Subject: [PATCH 13/15] got rid of internal Dtos and responseDto validations, removed @NotNull validation on controller @PathVariable parameters --- .../uniplan/student/StudentController.java | 9 ++--- .../unilab/uniplan/student/StudentMapper.java | 4 --- .../uniplan/student/StudentRepository.java | 24 -------------- .../student/dto/StudentCourseMajorDto.java | 33 ------------------- .../uniplan/student/dto/StudentDto.java | 21 ------------ .../student/dto/StudentResponseDto.java | 6 ---- 6 files changed, 3 insertions(+), 94 deletions(-) delete mode 100644 src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java delete mode 100644 src/main/java/org/unilab/uniplan/student/dto/StudentDto.java diff --git a/src/main/java/org/unilab/uniplan/student/StudentController.java b/src/main/java/org/unilab/uniplan/student/StudentController.java index 869dda1a..2f757c45 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentController.java +++ b/src/main/java/org/unilab/uniplan/student/StudentController.java @@ -34,8 +34,7 @@ public ResponseEntity createStudent(@RequestBody @NotNull @Valid final Stu } @GetMapping("/{id}") - public ResponseEntity getStudentById(@PathVariable - @NotNull final UUID id) { + public ResponseEntity getStudentById(@PathVariable final UUID id) { return ResponseEntity.ok(studentWebFacade.getStudentById(id)); } @@ -45,8 +44,7 @@ public ResponseEntity> getAllStudents() { } @PutMapping("/{id}") - public ResponseEntity updateStudent(@PathVariable - @NotNull final UUID id, + public ResponseEntity updateStudent(@PathVariable final UUID id, @RequestBody @NotNull @Valid final StudentRequestDto studentRequestDTO) { studentWebFacade.updateStudent(id, studentRequestDTO); @@ -54,8 +52,7 @@ public ResponseEntity updateStudent(@PathVariable } @DeleteMapping("/{id}") - public ResponseEntity deleteStudent(@PathVariable - @NotNull final UUID id) { + public ResponseEntity deleteStudent(@PathVariable final UUID id) { studentWebFacade.deleteStudent(id); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/org/unilab/uniplan/student/StudentMapper.java b/src/main/java/org/unilab/uniplan/student/StudentMapper.java index 46148a13..a3890b48 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentMapper.java +++ b/src/main/java/org/unilab/uniplan/student/StudentMapper.java @@ -4,7 +4,6 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; -import org.unilab.uniplan.student.dto.StudentDto; import org.unilab.uniplan.student.dto.StudentRequestDto; import org.unilab.uniplan.student.dto.StudentResponseDto; @@ -15,9 +14,6 @@ public interface StudentMapper { @Mapping(target = "id", ignore = true) Student toEntity(StudentRequestDto requestDto); - @Mapping(source = "course.id", target = "courseId") - StudentDto toDto(Student student); - @Mapping(target = "id", ignore = true) @Mapping(source = "courseId", target = "course.id") void updateEntity(StudentRequestDto requestDto, @MappingTarget Student student); diff --git a/src/main/java/org/unilab/uniplan/student/StudentRepository.java b/src/main/java/org/unilab/uniplan/student/StudentRepository.java index 85904938..d373973d 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentRepository.java +++ b/src/main/java/org/unilab/uniplan/student/StudentRepository.java @@ -1,33 +1,9 @@ package org.unilab.uniplan.student; -import java.util.List; import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import org.unilab.uniplan.student.dto.StudentCourseMajorDto; @Repository public interface StudentRepository extends JpaRepository { - @Query(""" - select new org.unilab.uniplan.student.dto.StudentCourseMajorDto( - s.id, s.firstName, s.lastName, s.facultyNumber, - c.id, c.courseType, c.courseSubtype, c.courseYear, - m.id, m.majorName - ) - from Student s - join s.course c - join c.major m - where (:firstName is null or lower(s.firstName) like lower(concat('%', :firstName, '%'))) - and (:lastName is null or lower(s.lastName) like lower(concat('%', :lastName, '%'))) - and (:facultyNumber is null or s.facultyNumber like concat('%', :facultyNumber, '%')) - and (:majorName is null or lower(m.majorName) like lower(concat('%', :majorName, '%'))) - """) - List searchStudents( - @Param("firstName") String firstName, - @Param("lastName") String lastName, - @Param("facultyNumber") String facultyNumber, - @Param("majorName") String majorName - ); } diff --git a/src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java b/src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java deleted file mode 100644 index 9dc5a51d..00000000 --- a/src/main/java/org/unilab/uniplan/student/dto/StudentCourseMajorDto.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.unilab.uniplan.student.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.util.UUID; - -public record StudentCourseMajorDto( - UUID studentId, - @NotBlank - @Size(max = 100) - String firstName, - @NotBlank - @Size(max = 100) - String lastName, - @Size(max = 40) - String facultyNumber, - @NotNull - UUID courseId, - @NotNull - String courseType, - @NotNull - String courseSubType, - @NotNull - Byte courseYear, - @NotNull - UUID majorId, - @NotBlank - @Size(max = 200) - String majorName -) { - -} diff --git a/src/main/java/org/unilab/uniplan/student/dto/StudentDto.java b/src/main/java/org/unilab/uniplan/student/dto/StudentDto.java deleted file mode 100644 index 0329a527..00000000 --- a/src/main/java/org/unilab/uniplan/student/dto/StudentDto.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.unilab.uniplan.student.dto; - -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.util.UUID; - -public record StudentDto( - UUID id, - @NotNull - @Size(max = 100) - String firstName, - @NotNull - @Size(max = 100) - String lastName, - @Size(max = 40) - String facultyNumber, - @NotNull - UUID courseId -) { - -} diff --git a/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java b/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java index 8ed48fec..9cba5090 100644 --- a/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java +++ b/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java @@ -1,16 +1,10 @@ package org.unilab.uniplan.student.dto; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; import java.util.UUID; public record StudentResponseDto( UUID id, - @NotNull - @Size(max = 100) String firstName, - @NotNull - @Size(max = 100) String lastName, String facultyNumber, UUID courseId From 545c212039838a28cbdf5625fefdda28b8d746fd Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Wed, 1 Jul 2026 11:17:42 +0300 Subject: [PATCH 14/15] Added StudentMapperTest, changed StudentValidator to accept StudentRequestDto instead of Student entity, adjusted tests and changed test names to adhere to convention --- .../uniplan/common/model/Validator.java | 5 - .../uniplan/student/StudentValidator.java | 9 +- .../uniplan/student/StudentWebFacade.java | 19 +-- .../uniplan/student/StudentMapperTest.java | 110 ++++++++++++++++++ .../uniplan/student/StudentServiceTest.java | 10 +- .../uniplan/student/StudentValidatorTest.java | 17 ++- .../uniplan/student/StudentWebFacadeTest.java | 20 ++-- 7 files changed, 146 insertions(+), 44 deletions(-) delete mode 100644 src/main/java/org/unilab/uniplan/common/model/Validator.java create mode 100644 src/test/java/org/unilab/uniplan/student/StudentMapperTest.java diff --git a/src/main/java/org/unilab/uniplan/common/model/Validator.java b/src/main/java/org/unilab/uniplan/common/model/Validator.java deleted file mode 100644 index e4207496..00000000 --- a/src/main/java/org/unilab/uniplan/common/model/Validator.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.unilab.uniplan.common.model; - -public interface Validator { - void validate(final T entity); -} diff --git a/src/main/java/org/unilab/uniplan/student/StudentValidator.java b/src/main/java/org/unilab/uniplan/student/StudentValidator.java index 17ae03f8..5a2fcbe2 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentValidator.java +++ b/src/main/java/org/unilab/uniplan/student/StudentValidator.java @@ -2,9 +2,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import org.unilab.uniplan.common.model.Validator; import org.unilab.uniplan.course.CourseRepository; import org.unilab.uniplan.exception.ResourceNotFoundException; +import org.unilab.uniplan.student.dto.StudentRequestDto; import java.util.UUID; @@ -12,12 +12,11 @@ @Component @RequiredArgsConstructor -public class StudentValidator implements Validator { +public class StudentValidator{ private final CourseRepository courseRepository; - @Override - public void validate(final Student entity) { - UUID id = entity.getCourse().getId(); + public void validate(final StudentRequestDto request) { + UUID id = request.courseId(); if (!courseRepository.existsById(id)){ throw new ResourceNotFoundException(COURSE_NOT_FOUND.getMessage(id.toString())); } diff --git a/src/main/java/org/unilab/uniplan/student/StudentWebFacade.java b/src/main/java/org/unilab/uniplan/student/StudentWebFacade.java index 6b8e79b8..3f41e0f7 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentWebFacade.java +++ b/src/main/java/org/unilab/uniplan/student/StudentWebFacade.java @@ -23,9 +23,10 @@ public class StudentWebFacade { @Transactional public void createStudent(final StudentRequestDto request){ + studentValidator.validate(request); final Student student = studentMapper.toEntity(request); - studentValidator.validate(student); studentService.save(student); + log.info("created student with ID: {}", student.getId()); } @Transactional(readOnly = true) @@ -39,18 +40,11 @@ public StudentResponseDto getStudentById(final UUID id){ return studentMapper.toResponseDto(student); } - private Student getStudentOrThrow(final UUID id){ - return studentService.getById(id) - .orElseThrow(() -> new ResourceNotFoundException( - STUDENT_NOT_FOUND.getMessage(String.valueOf(id)) - )); - } - @Transactional public void updateStudent(final UUID id, final StudentRequestDto request){ + studentValidator.validate(request); final Student student = getStudentOrThrow(id); - studentValidator.validate(student); studentMapper.updateEntity(request, student); studentService.save(student); log.info("updated student with ID: {}", student.getId()); @@ -62,4 +56,11 @@ public void deleteStudent(final UUID id){ studentService.delete(student); log.info("deleted student with ID: {}", id); } + + private Student getStudentOrThrow(final UUID id){ + return studentService.getById(id) + .orElseThrow(() -> new ResourceNotFoundException( + STUDENT_NOT_FOUND.getMessage(String.valueOf(id)) + )); + } } diff --git a/src/test/java/org/unilab/uniplan/student/StudentMapperTest.java b/src/test/java/org/unilab/uniplan/student/StudentMapperTest.java new file mode 100644 index 00000000..7a6a5e8e --- /dev/null +++ b/src/test/java/org/unilab/uniplan/student/StudentMapperTest.java @@ -0,0 +1,110 @@ +package org.unilab.uniplan.student; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mapstruct.factory.Mappers; +import org.mockito.junit.jupiter.MockitoExtension; +import org.unilab.uniplan.course.Course; +import org.unilab.uniplan.student.dto.StudentRequestDto; +import org.unilab.uniplan.student.dto.StudentResponseDto; + +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.AssertionsKt.assertNull; + +@ExtendWith(MockitoExtension.class) +class StudentMapperTest { + + private final StudentMapper studentMapper = Mappers.getMapper(StudentMapper.class); + + private UUID studentId; + private UUID courseId; + private Student student; + private StudentRequestDto requestDto; + + @BeforeEach + void setUp() { + studentId = UUID.randomUUID(); + courseId = UUID.randomUUID(); + + requestDto = new StudentRequestDto("Petar", "Petrov", "2301261005", courseId); + + Course course = new Course(); + course.setId(courseId); + + student = new Student(); + student.setId(studentId); + student.setFirstName("Petar"); + student.setLastName("Petrov"); + student.setFacultyNumber("2301261005"); + student.setCourse(course); + } + + @Test + void toEntity_ShouldMapFieldsCorrectly() { + Student result = studentMapper.toEntity(requestDto); + + assertNull(result.getId()); + assertEquals("Petar", result.getFirstName()); + assertEquals("Petrov", result.getLastName()); + assertEquals("2301261005", result.getFacultyNumber()); + assertEquals(courseId, result.getCourse().getId()); + } + + @Test + void toEntity_ShouldIgnoreId() { + Student result = studentMapper.toEntity(requestDto); + + assertNull(result.getId()); + } + + @Test + void updateEntity_ShouldUpdateFieldsCorrectly() { + UUID newCourseId = UUID.randomUUID(); + StudentRequestDto updateDto = new StudentRequestDto("Ivan", "Ivanov", "1234567890", newCourseId); + + studentMapper.updateEntity(updateDto, student); + + assertEquals("Ivan", student.getFirstName()); + assertEquals("Ivanov", student.getLastName()); + assertEquals("1234567890", student.getFacultyNumber()); + assertEquals(newCourseId, student.getCourse().getId()); + } + + @Test + void updateEntity_ShouldNotAlterStudentId() { + studentMapper.updateEntity(requestDto, student); + + assertEquals(studentId, student.getId()); + } + + @Test + void toResponseDto_ShouldMapFieldsCorrectly() { + StudentResponseDto result = studentMapper.toResponseDto(student); + + assertEquals(studentId, result.id()); + assertEquals("Petar", result.firstName()); + assertEquals("Petrov", result.lastName()); + assertEquals("2301261005", result.facultyNumber()); + assertEquals(courseId, result.courseId()); + } + + @Test + void toResponseDtoList_ShouldMapAllElements() { + List result = studentMapper.toResponseDtoList(List.of(student)); + + assertEquals(1, result.size()); + assertEquals(studentId, result.getFirst().id()); + } + + @Test + void toResponseDtoList_ShouldReturnEmptyForEmptyInput() { + List result = studentMapper.toResponseDtoList(List.of()); + + assertTrue(result.isEmpty()); + } +} diff --git a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java index 6018b209..ca1bc5e4 100644 --- a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java +++ b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java @@ -32,13 +32,13 @@ void setUp() { } @Test - void saveShouldDelegateToRepository() { + void save_ShouldDelegateToRepository() { studentService.save(student); verify(studentRepository).save(student); } @Test - void getByIdShouldReturnStudentIfExists() { + void getById_ShouldReturnStudent_IfExists() { UUID id = UUID.randomUUID(); when(studentRepository.findById(id)).thenReturn(Optional.of(student)); @@ -49,7 +49,7 @@ void getByIdShouldReturnStudentIfExists() { } @Test - void getByIdShouldReturnEmptyIfNotExists() { + void getById_ShouldReturnEmpty_IfNotExists() { UUID id = UUID.randomUUID(); when(studentRepository.findById(id)).thenReturn(Optional.empty()); @@ -57,14 +57,14 @@ void getByIdShouldReturnEmptyIfNotExists() { } @Test - void getAllShouldReturnAllStudents() { + void getAll_ShouldReturnAllStudents() { when(studentRepository.findAll()).thenReturn(List.of(student)); assertEquals(1, studentService.getAll().size()); } @Test - void deleteShouldDelegateToRepository() { + void delete_ShouldDelegateToRepository() { studentService.delete(student); verify(studentRepository).delete(student); } diff --git a/src/test/java/org/unilab/uniplan/student/StudentValidatorTest.java b/src/test/java/org/unilab/uniplan/student/StudentValidatorTest.java index e8bc21f3..1dd07d23 100644 --- a/src/test/java/org/unilab/uniplan/student/StudentValidatorTest.java +++ b/src/test/java/org/unilab/uniplan/student/StudentValidatorTest.java @@ -6,9 +6,9 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.unilab.uniplan.course.Course; import org.unilab.uniplan.course.CourseRepository; import org.unilab.uniplan.exception.ResourceNotFoundException; +import org.unilab.uniplan.student.dto.StudentRequestDto; import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -25,31 +25,28 @@ class StudentValidatorTest { @InjectMocks private StudentValidator studentValidator; - private Student student; private UUID courseId; + private StudentRequestDto requestDto; @BeforeEach void setUp() { courseId = UUID.randomUUID(); - Course course = new Course(); - course.setId(courseId); - student = new Student(); - student.setCourse(course); + requestDto = new StudentRequestDto("Petar", "Petrov", "2301261005", courseId); } @Test - void validateShouldPassWhenCourseExists() { + void validate_ShouldPass_WhenCourseExists() { when(courseRepository.existsById(courseId)).thenReturn(true); - assertDoesNotThrow(() -> studentValidator.validate(student)); + assertDoesNotThrow(() -> studentValidator.validate(requestDto)); } @Test - void validateShouldThrowWhenCourseNotFound() { + void validate_ShouldThrow_WhenCourseNotFound() { when(courseRepository.existsById(courseId)).thenReturn(false); ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, - () -> studentValidator.validate(student)); + () -> studentValidator.validate(requestDto)); assertTrue(exception.getMessage().contains(courseId.toString())); } diff --git a/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java b/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java index 8fafcff8..cc459844 100644 --- a/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java +++ b/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java @@ -52,19 +52,19 @@ void setUp() { } @Test - void createStudentShouldMapValidateAndSave() { + void createStudent_ShouldMapValidateAndSave() { when(studentMapper.toEntity(requestDto)).thenReturn(student); studentWebFacade.createStudent(requestDto); - InOrder order = inOrder(studentMapper, studentValidator, studentService); + InOrder order = inOrder(studentValidator, studentMapper, studentService); + order.verify(studentValidator).validate(requestDto); order.verify(studentMapper).toEntity(requestDto); - order.verify(studentValidator).validate(student); order.verify(studentService).save(student); } @Test - void updateStudentShouldThrowIfNotFound() { + void updateStudent_ShouldThrow_IfNotFound() { when(studentService.getById(studentId)).thenReturn(Optional.empty()); assertThrows(ResourceNotFoundException.class, @@ -74,19 +74,19 @@ void updateStudentShouldThrowIfNotFound() { } @Test - void updateStudentShouldValidateAndSave() { + void updateStudent_ShouldValidateAndSave() { when(studentService.getById(studentId)).thenReturn(Optional.of(student)); studentWebFacade.updateStudent(studentId, requestDto); InOrder order = inOrder(studentValidator, studentMapper, studentService); - order.verify(studentValidator).validate(student); + order.verify(studentValidator).validate(requestDto); order.verify(studentMapper).updateEntity(requestDto, student); order.verify(studentService).save(student); } @Test - void getStudentByIdShouldReturnMappedDto() { + void getStudentById_ShouldReturnMappedDto() { when(studentService.getById(studentId)).thenReturn(Optional.of(student)); when(studentMapper.toResponseDto(student)).thenReturn(responseDto); @@ -94,7 +94,7 @@ void getStudentByIdShouldReturnMappedDto() { } @Test - void getStudentByIdShouldThrowIfNotFound() { + void getStudentById_ShouldThrow_IfNotFound() { when(studentService.getById(studentId)).thenReturn(Optional.empty()); assertThrows(ResourceNotFoundException.class, @@ -102,7 +102,7 @@ void getStudentByIdShouldThrowIfNotFound() { } @Test - void deleteStudentShouldDelegateIfExists() { + void deleteStudent_ShouldDelegateIfExists() { when(studentService.getById(studentId)).thenReturn(Optional.of(student)); studentWebFacade.deleteStudent(studentId); @@ -111,7 +111,7 @@ void deleteStudentShouldDelegateIfExists() { } @Test - void deleteStudentShouldThrowIfNotFound() { + void deleteStudent_ShouldThrow_IfNotFound() { when(studentService.getById(studentId)).thenReturn(Optional.empty()); assertThrows(ResourceNotFoundException.class, From f1abca7e7a37009c7bf27dd2d2592181aac67cc6 Mon Sep 17 00:00:00 2001 From: constantine0621 Date: Wed, 1 Jul 2026 11:48:47 +0300 Subject: [PATCH 15/15] Updated Mapper and Service tests after merge --- .../uniplan/student/StudentMapperTest.java | 22 +++++++++++++++---- .../uniplan/student/StudentServiceTest.java | 6 ++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/unilab/uniplan/student/StudentMapperTest.java b/src/test/java/org/unilab/uniplan/student/StudentMapperTest.java index 7a6a5e8e..a9cdb68f 100644 --- a/src/test/java/org/unilab/uniplan/student/StudentMapperTest.java +++ b/src/test/java/org/unilab/uniplan/student/StudentMapperTest.java @@ -6,6 +6,7 @@ import org.mapstruct.factory.Mappers; import org.mockito.junit.jupiter.MockitoExtension; import org.unilab.uniplan.course.Course; +import org.unilab.uniplan.major.Major; import org.unilab.uniplan.student.dto.StudentRequestDto; import org.unilab.uniplan.student.dto.StudentResponseDto; @@ -23,6 +24,7 @@ class StudentMapperTest { private UUID studentId; private UUID courseId; + private UUID majorId; private Student student; private StudentRequestDto requestDto; @@ -30,11 +32,20 @@ class StudentMapperTest { void setUp() { studentId = UUID.randomUUID(); courseId = UUID.randomUUID(); + majorId = UUID.randomUUID(); requestDto = new StudentRequestDto("Petar", "Petrov", "2301261005", courseId); + Major major = new Major(); + major.setId(majorId); + major.setMajorName("Software Engineering"); + Course course = new Course(); course.setId(courseId); + course.setMajor(major); + course.setCourseType("FULL_TIME"); + course.setCourseSubtype("редовно"); + course.setCourseYear((byte) 2); student = new Student(); student.setId(studentId); @@ -72,7 +83,7 @@ void updateEntity_ShouldUpdateFieldsCorrectly() { assertEquals("Ivan", student.getFirstName()); assertEquals("Ivanov", student.getLastName()); assertEquals("1234567890", student.getFacultyNumber()); - assertEquals(newCourseId, student.getCourse().getId()); + assertEquals(courseId, student.getCourse().getId()); } @Test @@ -87,10 +98,13 @@ void toResponseDto_ShouldMapFieldsCorrectly() { StudentResponseDto result = studentMapper.toResponseDto(student); assertEquals(studentId, result.id()); - assertEquals("Petar", result.firstName()); - assertEquals("Petrov", result.lastName()); + assertEquals("Petar Petrov", result.name()); assertEquals("2301261005", result.facultyNumber()); - assertEquals(courseId, result.courseId()); + assertEquals(majorId, result.majorId()); // adjust if majorId comes from major.id + assertEquals("Software Engineering", result.majorName()); + assertEquals("FULL_TIME", result.courseType()); + assertEquals("редовно", result.courseSubtype()); + assertEquals((byte) 2, result.courseYear()); } @Test diff --git a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java index ca1bc5e4..1506f264 100644 --- a/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java +++ b/src/test/java/org/unilab/uniplan/student/StudentServiceTest.java @@ -40,7 +40,7 @@ void save_ShouldDelegateToRepository() { @Test void getById_ShouldReturnStudent_IfExists() { UUID id = UUID.randomUUID(); - when(studentRepository.findById(id)).thenReturn(Optional.of(student)); + when(studentRepository.findByIdWithCourseAndMajor(id)).thenReturn(Optional.of(student)); Optional result = studentService.getById(id); @@ -51,14 +51,14 @@ void getById_ShouldReturnStudent_IfExists() { @Test void getById_ShouldReturnEmpty_IfNotExists() { UUID id = UUID.randomUUID(); - when(studentRepository.findById(id)).thenReturn(Optional.empty()); + when(studentRepository.findByIdWithCourseAndMajor(id)).thenReturn(Optional.empty()); assertTrue(studentService.getById(id).isEmpty()); } @Test void getAll_ShouldReturnAllStudents() { - when(studentRepository.findAll()).thenReturn(List.of(student)); + when(studentRepository.findAllWithCourseAndMajor()).thenReturn(List.of(student)); assertEquals(1, studentService.getAll().size()); }