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 diff --git a/src/main/java/org/unilab/uniplan/student/StudentController.java b/src/main/java/org/unilab/uniplan/student/StudentController.java index b21f45bb..2f757c45 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,35 @@ @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 - @NotNull final UUID id) { - final StudentResponseDto studentResponseDTO = studentMapper.toResponseDto(studentService.findStudentById( - id)); - - return ResponseEntity.ok(studentResponseDTO); + public ResponseEntity getStudentById(@PathVariable final UUID id) { + 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 - @NotNull final UUID id, + public ResponseEntity updateStudent(@PathVariable 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); + 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 440e5043..b116a655 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; @@ -12,21 +11,24 @@ public interface StudentMapper { @Mapping(source = "courseId", target = "course.id") - Student toEntity(StudentDto studentDto); - - @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); + Student toEntity(StudentRequestDto requestDto); @Mapping(target = "id", ignore = true) - StudentDto toInternalDto(StudentRequestDto student); - - @Mapping(source = "courseId", target = "courseId") - StudentResponseDto toResponseDto(StudentDto studentDto); - - List toResponseDtoList(List students); - + @Mapping(target = "course", ignore = true) + void updateEntity(StudentRequestDto requestDto, @MappingTarget Student student); + + @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 85904938..2850ef11 100644 --- a/src/main/java/org/unilab/uniplan/student/StudentRepository.java +++ b/src/main/java/org/unilab/uniplan/student/StudentRepository.java @@ -1,33 +1,27 @@ 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; 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 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 c616dbd3..5c14855a 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.findAllWithCourseAndMajor(); } - 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.findByIdWithCourseAndMajor(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..5a2fcbe2 --- /dev/null +++ b/src/main/java/org/unilab/uniplan/student/StudentValidator.java @@ -0,0 +1,24 @@ +package org.unilab.uniplan.student; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +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.unilab.uniplan.utils.ErrorConstants.COURSE_NOT_FOUND; + +@Component +@RequiredArgsConstructor +public class StudentValidator{ + private final CourseRepository courseRepository; + + 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 new file mode 100644 index 00000000..3f41e0f7 --- /dev/null +++ b/src/main/java/org/unilab/uniplan/student/StudentWebFacade.java @@ -0,0 +1,66 @@ +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){ + studentValidator.validate(request); + final Student student = studentMapper.toEntity(request); + studentService.save(student); + log.info("created student with ID: {}", student.getId()); + } + + @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); + } + + @Transactional + public void updateStudent(final UUID id, + final StudentRequestDto request){ + studentValidator.validate(request); + final Student student = getStudentOrThrow(id); + 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); + } + + private Student getStudentOrThrow(final UUID id){ + return studentService.getById(id) + .orElseThrow(() -> new ResourceNotFoundException( + STUDENT_NOT_FOUND.getMessage(String.valueOf(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 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/StudentRequestDto.java b/src/main/java/org/unilab/uniplan/student/dto/StudentRequestDto.java index 2f3ee4ae..75699ced 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,21 @@ 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, + @NotBlank + @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..ee4f1377 100644 --- a/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java +++ b/src/main/java/org/unilab/uniplan/student/dto/StudentResponseDto.java @@ -1,19 +1,16 @@ 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, - UUID courseId + UUID majorId, + String majorName, + String courseType, + String courseSubtype, + Byte courseYear ) { } 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..a9cdb68f --- /dev/null +++ b/src/test/java/org/unilab/uniplan/student/StudentMapperTest.java @@ -0,0 +1,124 @@ +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.major.Major; +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 UUID majorId; + private Student student; + private StudentRequestDto requestDto; + + @BeforeEach + 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); + 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(courseId, 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 Petrov", result.name()); + assertEquals("2301261005", result.facultyNumber()); + 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 + 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 f20e52c6..1506f264 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 save_ShouldDelegateToRepository() { + 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 getById_ShouldReturnStudent_IfExists() { + UUID id = UUID.randomUUID(); + when(studentRepository.findByIdWithCourseAndMajor(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 getById_ShouldReturnEmpty_IfNotExists() { + UUID id = UUID.randomUUID(); + when(studentRepository.findByIdWithCourseAndMajor(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 getAll_ShouldReturnAllStudents() { + when(studentRepository.findAllWithCourseAndMajor()).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 delete_ShouldDelegateToRepository() { + 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..1dd07d23 --- /dev/null +++ b/src/test/java/org/unilab/uniplan/student/StudentValidatorTest.java @@ -0,0 +1,53 @@ +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.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; +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 UUID courseId; + private StudentRequestDto requestDto; + + @BeforeEach + void setUp() { + courseId = UUID.randomUUID(); + requestDto = new StudentRequestDto("Petar", "Petrov", "2301261005", courseId); + } + + @Test + void validate_ShouldPass_WhenCourseExists() { + when(courseRepository.existsById(courseId)).thenReturn(true); + + assertDoesNotThrow(() -> studentValidator.validate(requestDto)); + } + + @Test + void validate_ShouldThrow_WhenCourseNotFound() { + when(courseRepository.existsById(courseId)).thenReturn(false); + + ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, + () -> 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 new file mode 100644 index 00000000..f8820988 --- /dev/null +++ b/src/test/java/org/unilab/uniplan/student/StudentWebFacadeTest.java @@ -0,0 +1,137 @@ +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 UUID majorId; + private Student student; + private StudentRequestDto requestDto; + private StudentResponseDto responseDto; + + @BeforeEach + 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", + majorId, + "Software Engineering", + "FULL_TIME", + "редовно", + (byte) 2 + ); + } + + @Test + void createStudent_ShouldMapValidateAndSave() { + when(studentMapper.toEntity(requestDto)).thenReturn(student); + + studentWebFacade.createStudent(requestDto); + + InOrder order = inOrder(studentValidator, studentMapper, studentService); + order.verify(studentValidator).validate(requestDto); + order.verify(studentMapper).toEntity(requestDto); + order.verify(studentService).save(student); + } + + @Test + void updateStudent_ShouldThrow_IfNotFound() { + when(studentService.getById(studentId)).thenReturn(Optional.empty()); + + assertThrows(ResourceNotFoundException.class, + () -> studentWebFacade.updateStudent(studentId, requestDto)); + + verify(studentService, never()).save(any()); + } + + @Test + 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(requestDto); + order.verify(studentMapper).updateEntity(requestDto, student); + order.verify(studentService).save(student); + } + + @Test + void getStudentById_ShouldReturnMappedDto() { + when(studentService.getById(studentId)).thenReturn(Optional.of(student)); + when(studentMapper.toResponseDto(student)).thenReturn(responseDto); + + assertEquals(responseDto, studentWebFacade.getStudentById(studentId)); + } + + @Test + void getStudentById_ShouldThrow_IfNotFound() { + when(studentService.getById(studentId)).thenReturn(Optional.empty()); + + assertThrows(ResourceNotFoundException.class, + () -> studentWebFacade.getStudentById(studentId)); + } + + @Test + void deleteStudent_ShouldDelegateIfExists() { + when(studentService.getById(studentId)).thenReturn(Optional.of(student)); + + studentWebFacade.deleteStudent(studentId); + + verify(studentService).delete(student); + } + + @Test + void deleteStudent_ShouldThrow_IfNotFound() { + when(studentService.getById(studentId)).thenReturn(Optional.empty()); + + assertThrows(ResourceNotFoundException.class, + () -> studentWebFacade.deleteStudent(studentId)); + + verify(studentService, never()).delete(any()); + } +}