diff --git a/application/model/src/main/java/org/fuseri/model/dto/certificate/CertificateCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/certificate/CertificateCreateDto.java index cf2c9c983c16ee1555f22dcfa3b489cf96752c89..a6b4f1f7e183d139a19ef838f26ef27d14d5298d 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/certificate/CertificateCreateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/certificate/CertificateCreateDto.java @@ -1,10 +1,11 @@ package org.fuseri.model.dto.certificate; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; -import org.fuseri.model.dto.course.CourseDto; +import org.fuseri.model.dto.course.CourseCertificateDto; import org.fuseri.model.dto.user.UserDto; @@ -12,6 +13,36 @@ import org.fuseri.model.dto.user.UserDto; * This class represents a Data Transfer Object (DTO) for creating Certificate entities. * It is used for creating Certificate entity. */ +@Schema(example = """ + { + "user": { + "id": 1, + "username": "adelkaxxx", + "email": "adelkaxxx@muni.mail.cz", + "firstName": "AdĂ©la", + "lastName": "Pulcová", + "address": { + "country": "Czechia", + "city": "Praha", + "street": "BubenskĂ© nábĹ™eĹľĂ", + "houseNumber": "306/13", + "zip": "170 00" + }, + "userType": "STUDENT", + "languageProficiency": { + "CZECH": "A2" + } + }, + "course": { + "id": 1, + "name": "english a1", + "capacity": 10, + "language": "ENGLISH", + "proficiency": "A1" + } + + } + """) @Getter @Setter public class CertificateCreateDto { @@ -20,9 +51,9 @@ public class CertificateCreateDto { private UserDto user; @NotNull @Valid - private CourseDto course; + private CourseCertificateDto course; - public CertificateCreateDto(UserDto user, CourseDto course) { + public CertificateCreateDto(UserDto user, CourseCertificateDto course) { this.user = user; this.course = course; } diff --git a/application/model/src/main/java/org/fuseri/model/dto/course/CourseCertificateDto.java b/application/model/src/main/java/org/fuseri/model/dto/course/CourseCertificateDto.java new file mode 100644 index 0000000000000000000000000000000000000000..8a7e5982f48e0b9edbd0e513eec26dafaca26c77 --- /dev/null +++ b/application/model/src/main/java/org/fuseri/model/dto/course/CourseCertificateDto.java @@ -0,0 +1,47 @@ +package org.fuseri.model.dto.course; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.fuseri.model.dto.common.DomainObjectDto; + +/** + * This class represents a Data Transfer Object (DTO) for Course entities. + * It is used for passing Course data to Certificate module. + * It extends the DomainObjectDto class and includes additional Course-specific fields. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public class CourseCertificateDto extends DomainObjectDto { + + @NotBlank(message = "Course name is required") + @Size(max = 63, message = "Course name must not exceed {max} characters") + private String name; + + @NotNull(message = "Lecture capacity cannot be null") + @Min(value = 1, message = "Lecture capacity must be at least 1") + private Integer capacity; + + @NotNull(message = "Language type is required") + @Valid + private LanguageTypeDto language; + + @NotNull(message = "Proficiency level is required") + @Valid + private ProficiencyLevelDto proficiency; + + public CourseCertificateDto(String name, Integer capacity, LanguageTypeDto languageTypeDto, ProficiencyLevelDto proficiencyLevelDto) { + setId(0L); + this.name = name; + this.capacity = capacity; + this.language = languageTypeDto; + this.proficiency = proficiencyLevelDto; + } + +} diff --git a/application/model/src/main/java/org/fuseri/model/dto/course/CourseCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/course/CourseCreateDto.java index 2ba3673e96f42b9b3b14fdb6406faed1030268ac..c7548adb3095a544c5975d69b2f2c94b03081fc3 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/course/CourseCreateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/course/CourseCreateDto.java @@ -1,5 +1,6 @@ package org.fuseri.model.dto.course; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; @@ -14,6 +15,14 @@ import lombok.Setter; * This class represents a Data Transfer Object (DTO) for Course entities. * It is used to create a new Course with the init data. */ +@Schema(example = """ + { + "name": "english a1", + "capacity": 10, + "language": "ENGLISH", + "proficiency": "A1" + } + """) @Getter @Setter @AllArgsConstructor diff --git a/application/model/src/main/java/org/fuseri/model/dto/course/CourseDto.java b/application/model/src/main/java/org/fuseri/model/dto/course/CourseDto.java index 2bb3eb99ccd5b353c16c7813d6170183042dcd62..c9d2d39ba344bc0d35337068691564323cb2217f 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/course/CourseDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/course/CourseDto.java @@ -1,5 +1,6 @@ package org.fuseri.model.dto.course; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; @@ -9,7 +10,6 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.fuseri.model.dto.common.DomainObjectDto; -import org.fuseri.model.dto.user.UserDto; import java.util.ArrayList; import java.util.List; @@ -19,6 +19,17 @@ import java.util.List; * It is used to transfer Course data between different layers of the application. * It extends the DomainObjectDto class and includes additional Course-specific fields. */ +@Schema(example = """ + { + "id": 1, + "name": "english a1", + "capacity": 10, + "language": "ENGLISH", + "proficiency": "A1", + "students": [], + "finished": false + } + """) @Getter @Setter @EqualsAndHashCode(callSuper = false) @@ -42,14 +53,26 @@ public class CourseDto extends DomainObjectDto { @NotNull(message = "Student's list is required") @Valid - private List<UserDto> students; + private List<Long> students; - public CourseDto(String name, Integer capacity, LanguageTypeDto languageTypeDto, ProficiencyLevelDto proficiencyLevelDto) { + @NotNull(message = "Finished status required") + @Valid + private Boolean finished; + + public CourseDto(String name, Integer capacity, LanguageTypeDto languageTypeDto, ProficiencyLevelDto proficiencyLevelDto, List<Long> students, Boolean finished) { this.name = name; this.capacity = capacity; this.language = languageTypeDto; this.proficiency = proficiencyLevelDto; - this.students = new ArrayList<>(); + this.students = students; + this.finished = finished; } + public CourseDto(String name, int capacity, LanguageTypeDto languageTypeDto, ProficiencyLevelDto proficiencyLevelDto) { + this.name = name; + this.capacity = capacity; + this.language = languageTypeDto; + this.proficiency = proficiencyLevelDto; + this.students = new ArrayList<>(); + } } diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerCreateDto.java index 2418847a2d483585f0d0a57505f0cfd148aa2094..7b4539a9ab48612adffdc7dce62372939a4dec89 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerCreateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerCreateDto.java @@ -4,17 +4,19 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; @AllArgsConstructor +@NoArgsConstructor @Getter public class AnswerCreateDto { - @NotBlank + @NotBlank(message = "answer text is required") private String text; - @NotNull + @NotNull(message = "answer correctness must be specified") private boolean correct; - @NotNull + @NotNull(message = "answer must belong to a question, questionId can not be null") private long questionId; } diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerInQuestionCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerInQuestionCreateDto.java index 229eea30d0c9f12e27237841064dbd92489f7758..8add213c5a60f1b1e332d3fc783dc367bf9f25ce 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerInQuestionCreateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerInQuestionCreateDto.java @@ -9,9 +9,9 @@ import lombok.Getter; @Getter public class AnswerInQuestionCreateDto { - @NotBlank + @NotBlank(message = "answer text is required") private String text; - @NotNull + @NotNull(message = "answer correctness must be specified") private boolean correct; } diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswersCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswersCreateDto.java index f2c7da24058c59d4a600d9a7d7d0403652e46832..438d96cafdc3867c6a8e07300dd87e00da0f3eab 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswersCreateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswersCreateDto.java @@ -11,7 +11,7 @@ import java.util.List; @Getter public class AnswersCreateDto { - @NotNull + @NotNull(message = "answers must belong to a question, questionId can not be null") private long questionId; @Valid diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/ExerciseCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/ExerciseCreateDto.java index 6d1cb1a7727b669e683f775e63aafc0e781816fe..9f22fd51ce723cab2a1214fa70769bda8e9ed28d 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/exercise/ExerciseCreateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/ExerciseCreateDto.java @@ -5,20 +5,22 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PositiveOrZero; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; @AllArgsConstructor +@NoArgsConstructor @Getter public class ExerciseCreateDto { - @NotBlank + @NotBlank(message = "exercise name is required") private String name; - @NotBlank + @NotBlank(message = "exercise description is required") private String description; - @NotNull + @NotNull(message = "exercise difficulty is required") @PositiveOrZero private int difficulty; - @NotNull + @NotNull(message = "exercise must belong to a course, courseId can not be null") private long courseId; } diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionCreateDto.java index cd215815305d8bb06de5b81e47f6b6e8380954d2..fa7c786f55bcd743ab02730cdac9d251c925dd7b 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionCreateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionCreateDto.java @@ -5,17 +5,19 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import java.util.List; @AllArgsConstructor +@NoArgsConstructor @Getter public class QuestionCreateDto { - @NotBlank + @NotBlank(message = "question text is required") private String text; - @NotNull + @NotNull(message = "question must belong to an exercise, exerciseId can not be null") private long exerciseId; @Valid diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionUpdateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionUpdateDto.java index 59e0fda131a540c0c08da981fd36d156a49cb5ae..e23b02ec6aa7d28556f5d65232a9cea4098f2a1d 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionUpdateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionUpdateDto.java @@ -9,9 +9,9 @@ import lombok.Getter; @Builder public class QuestionUpdateDto { - @NotBlank + @NotBlank(message = "question text is required") private String text; - @NotNull + @NotNull(message = "question must belong to an exercise, exerciseId can not be null") private long exerciseId; } diff --git a/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureCreateDto.java index b7ec0926fd958f722109fcf52421f4c47934f8b4..b54ff0182cc8661d59a8193f95dadf68b1cc3608 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureCreateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureCreateDto.java @@ -1,11 +1,21 @@ package org.fuseri.model.dto.lecture; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.*; import lombok.Getter; import lombok.Setter; import java.time.LocalDateTime; +@Schema(example = """ + { + "lectureFrom": "2069-04-20T22:24:33.038Z", + "lectureTo": "2069-04-20T23:24:33.038Z", + "topic": "pronouns", + "capacity": 10, + "courseId": 1 + } + """) @Getter @Setter public class LectureCreateDto { diff --git a/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java b/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java index 73820adce9c8b5281064b96c6c0811253488b63c..f60d220b915138c416f747026ecceb5323a9bd29 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java @@ -1,5 +1,6 @@ package org.fuseri.model.dto.lecture; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.*; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -9,6 +10,20 @@ import org.fuseri.model.dto.common.DomainObjectDto; import java.time.LocalDateTime; import java.util.List; +@Schema(example = """ + { + "id": 1, + "lectureFrom": "2069-04-20T22:24:33.038Z", + "lectureTo": "2069-04-20T23:24:33.038Z", + "topic": "pronouns", + "capacity": 10, + "lecturerId": 1, + "courseId": 1, + "students": [ + 0 + ] + } + """) @Getter @Setter @EqualsAndHashCode(callSuper = false) diff --git a/application/model/src/main/java/org/fuseri/model/dto/user/UserAddLanguageDto.java b/application/model/src/main/java/org/fuseri/model/dto/user/UserAddLanguageDto.java index 271ce6435594e1a25946db945556ad8c048943cc..d4456bcd61c008ead27a17c5b6034395c8f19540 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/user/UserAddLanguageDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/user/UserAddLanguageDto.java @@ -1,5 +1,6 @@ package org.fuseri.model.dto.user; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; @@ -8,6 +9,12 @@ import lombok.Setter; import org.fuseri.model.dto.course.LanguageTypeDto; import org.fuseri.model.dto.course.ProficiencyLevelDto; +@Schema(example = """ + { + "language": "ENGLISH", + "proficiency": "A1" + } + """) @Getter @Setter @AllArgsConstructor diff --git a/application/model/src/main/java/org/fuseri/model/dto/user/UserCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/user/UserCreateDto.java index 2336e937e1d65e819bd2095e9641213942d0fc8e..69dbdba233057db846499fa1a6fc34b86cf59ddc 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/user/UserCreateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/user/UserCreateDto.java @@ -1,5 +1,6 @@ package org.fuseri.model.dto.user; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -12,6 +13,26 @@ import org.fuseri.model.dto.course.ProficiencyLevelDto; import java.util.Map; +@Schema(example = """ + { + "username": "adelkaxxx", + "password": "123456", + "email": "adelkaxxx@muni.mail.cz", + "firstName": "AdĂ©la", + "lastName": "Pulcová", + "address": { + "country": "Czechia", + "city": "Praha", + "street": "BubenskĂ© nábĹ™eĹľĂ", + "houseNumber": "306/13", + "zip": "170 00" + }, + "userType": "STUDENT", + "languageProficiency": { + "CZECH": "A2" + } + } + """) @Getter @Setter @AllArgsConstructor diff --git a/application/model/src/main/java/org/fuseri/model/dto/user/UserDto.java b/application/model/src/main/java/org/fuseri/model/dto/user/UserDto.java index 0e3ebf029e28af0b99d1d9c47fadfc2bf776c780..a5aa263f9de41e0e3c2b6f442926a7bd8463f1c3 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/user/UserDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/user/UserDto.java @@ -1,20 +1,40 @@ package org.fuseri.model.dto.user; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.EqualsAndHashCode; +import lombok.*; import org.fuseri.model.dto.common.DomainObjectDto; -import lombok.Getter; -import lombok.Setter; import org.fuseri.model.dto.course.LanguageTypeDto; import org.fuseri.model.dto.course.ProficiencyLevelDto; import java.util.Map; +@Schema(example = """ + { + "id": 1, + "username": "adelkaxxx", + "email": "adelkaxxx@muni.mail.cz", + "firstName": "AdĂ©la", + "lastName": "Pulcová", + "address": { + "country": "Czechia", + "city": "Praha", + "street": "BubenskĂ© nábĹ™eĹľĂ", + "houseNumber": "306/13", + "zip": "170 00" + }, + "userType": "STUDENT", + "languageProficiency": { + "CZECH": "A2" + } + } + """) @Getter @Setter @EqualsAndHashCode(callSuper = false) +@NoArgsConstructor public class UserDto extends DomainObjectDto { @NotBlank @@ -47,4 +67,16 @@ public class UserDto extends DomainObjectDto { this.address = address; this.userType = userType; } + + + public UserDto(String username, String email, String firstName, String lastName, AddressDto address, UserType userType,Map<LanguageTypeDto, ProficiencyLevelDto> languageProficiency) { + setId(0L); + this.username = username; + this.email = email; + this.firstName = firstName; + this.lastName = lastName; + this.address = address; + this.userType = userType; + this.languageProficiency = languageProficiency; + } } diff --git a/application/model/src/main/java/org/fuseri/model/dto/user/UserLoginDto.java b/application/model/src/main/java/org/fuseri/model/dto/user/UserLoginDto.java index 45ed8e257d736edd26a79f6d07fb1137455676d7..d3e93a574fa0a4ae484953711c50d358694548de 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/user/UserLoginDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/user/UserLoginDto.java @@ -1,10 +1,17 @@ package org.fuseri.model.dto.user; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +@Schema(example = """ + { + "username": "adelkaxxx", + "password": "123456" + } + """) @Getter @Setter @AllArgsConstructor diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/Certificate.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/Certificate.java similarity index 98% rename from application/module-certificate/src/main/java/org/fuseri/modulecertificate/Certificate.java rename to application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/Certificate.java index 3b9b0a94666ab63b462e85c64e177f467769cb20..38c6b94678066cc9d7cf2626a97c94a72404a732 100644 --- a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/Certificate.java +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/Certificate.java @@ -1,4 +1,4 @@ -package org.fuseri.modulecertificate; +package org.fuseri.modulecertificate.certificate; import jakarta.persistence.*; diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateController.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateController.java similarity index 91% rename from application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateController.java rename to application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateController.java index e63254df618398a0faf40d7360551918cf9e5cdb..f7376d98019c7f6a4085efd77bcb9a70e36f0fb3 100644 --- a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateController.java +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateController.java @@ -1,9 +1,8 @@ -package org.fuseri.modulecertificate.service; +package org.fuseri.modulecertificate.certificate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.persistence.EntityNotFoundException; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import org.fuseri.model.dto.certificate.CertificateCreateDto; @@ -44,7 +43,7 @@ public class CertificateController { description = "Generates certificate, saves it into database and returns certificate information and certificate file.") @ApiResponses(value = { @ApiResponse(responseCode = "201", description = "Certificate generated successfully."), - @ApiResponse(responseCode = "400", description = "Invalid input.") + @ApiResponse(responseCode = "500", description = "Internal server error.") }) @PostMapping public ResponseEntity<CertificateSimpleDto> generate(@Valid @RequestBody CertificateCreateDto certificateCreateDto) { @@ -61,15 +60,12 @@ public class CertificateController { @Operation(summary = "Get a certificate by ID", description = "Returns a certificate with the specified ID.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Certificate with the specified ID retrieved successfully."), - @ApiResponse(responseCode = "404", description = "Certificate with the specified ID was not found.") + @ApiResponse(responseCode = "404", description = "Certificate with the specified ID was not found."), + @ApiResponse(responseCode = "400", description = "Invalid input.") }) @GetMapping("/{id}") public ResponseEntity<CertificateSimpleDto> find(@NotNull @PathVariable Long id) { - try { return ResponseEntity.ok(certificateFacade.findById(id)); - } catch (EntityNotFoundException e) { - return ResponseEntity.notFound().build(); - } } /** @@ -82,7 +78,7 @@ public class CertificateController { @Operation(summary = "Get certificates for user", description = "Returns certificates for given user in list.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully retrieved certificates"), - @ApiResponse(responseCode = "400", description = "Invalid input."), + @ApiResponse(responseCode = "500", description = "Internal server error."), }) @GetMapping("/findForUser") public ResponseEntity<List<CertificateSimpleDto>> findForUser(@RequestParam Long userId) { @@ -101,6 +97,7 @@ public class CertificateController { description = "Returns certificates for given user and course in list.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully retrieved certificates"), + @ApiResponse(responseCode = "500", description = "Internal server error."), @ApiResponse(responseCode = "400", description = "Invalid input."), }) @GetMapping("/findForUserAndCourse") @@ -116,6 +113,8 @@ public class CertificateController { @Operation(summary = "Delete a certificate with specified ID", description = "Deletes a certificate with the specified ID.") @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "Certificate with the specified ID deleted successfully."), + @ApiResponse(responseCode = "500", description = "Internal server error."), + @ApiResponse(responseCode = "400", description = "Invalid input.") }) @DeleteMapping("/{id}") public ResponseEntity<Void> delete(@NotNull @PathVariable Long id) { @@ -131,7 +130,7 @@ public class CertificateController { @Operation(summary = "Get certificates in paginated format", description = "Returns certificates in paginated format.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully retrieved paginated certificates"), - @ApiResponse(responseCode = "400", description = "Invalid page number supplied"), + @ApiResponse(responseCode = "500", description = "Internal server error.") }) @GetMapping public ResponseEntity<Page<CertificateSimpleDto>> findAllCertificates(Pageable pageable) { diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateFacade.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateFacade.java similarity index 97% rename from application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateFacade.java rename to application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateFacade.java index 018c0591e6e30f92730d5cf7374fa26defca3b23..b3524b9779b200eac627f490eff73890d5ea531e 100644 --- a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateFacade.java +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateFacade.java @@ -1,4 +1,4 @@ -package org.fuseri.modulecertificate.service; +package org.fuseri.modulecertificate.certificate; import org.fuseri.model.dto.certificate.CertificateCreateDto; diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateMapper.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateMapper.java similarity index 90% rename from application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateMapper.java rename to application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateMapper.java index f6738c4428f97b4bb6cacf0b2806491aabf00e8b..f806843fb0b439d51b8d496dea091d956fd2de16 100644 --- a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateMapper.java +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateMapper.java @@ -1,9 +1,7 @@ -package org.fuseri.modulecertificate.service; +package org.fuseri.modulecertificate.certificate; import org.fuseri.model.dto.certificate.CertificateCreateDto; -import org.fuseri.model.dto.certificate.CertificateDto; import org.fuseri.model.dto.certificate.CertificateSimpleDto; -import org.fuseri.modulecertificate.Certificate; import org.mapstruct.Mapper; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateRepository.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateRepository.java similarity index 85% rename from application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateRepository.java rename to application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateRepository.java index 832d2d22d002b82e3a93287f4382cfa66273b56d..367431f98c3b62f1e883e165ba1c379c2f640aa8 100644 --- a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateRepository.java +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateRepository.java @@ -1,6 +1,5 @@ -package org.fuseri.modulecertificate.service; +package org.fuseri.modulecertificate.certificate; -import org.fuseri.modulecertificate.Certificate; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateService.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateService.java similarity index 94% rename from application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateService.java rename to application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateService.java index 245c88c35ea55f4d59d0d01ed2b15290fddc42ed..a32a659ca5f06da29e69190cc90503c8f47c27ff 100644 --- a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/CertificateService.java +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/certificate/CertificateService.java @@ -1,6 +1,5 @@ -package org.fuseri.modulecertificate.service; +package org.fuseri.modulecertificate.certificate; -import org.fuseri.modulecertificate.Certificate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiError.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiError.java new file mode 100644 index 0000000000000000000000000000000000000000..617a0da1509d27121fb234e12f901547a20fafb7 --- /dev/null +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiError.java @@ -0,0 +1,43 @@ +package org.fuseri.modulecertificate.exceptions; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@ToString +class ApiError { + + private HttpStatus status; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") + private LocalDateTime timestamp; + private String message; + private List<ApiSubError> subErrors; + private String path; + + private ApiError() { + timestamp = LocalDateTime.now(Clock.systemUTC()); + } + + ApiError(HttpStatus status, Throwable ex, String path) { + this(); + this.status = status; + this.path = path; + this.message = ex.getLocalizedMessage(); + } + + ApiError(HttpStatus status, List<ApiSubError> subErrors, Throwable ex, String path) { + this(); + this.status = status; + this.subErrors = subErrors; + this.path = path; + this.message = ex.getLocalizedMessage(); + } +} + + diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiSubError.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiSubError.java new file mode 100644 index 0000000000000000000000000000000000000000..f5ad2d920a83bfeb8157083b809af4cf0de3cc75 --- /dev/null +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiSubError.java @@ -0,0 +1,4 @@ +package org.fuseri.modulecertificate.exceptions; + +public interface ApiSubError { +} diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiValidationError.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiValidationError.java new file mode 100644 index 0000000000000000000000000000000000000000..43b6acd7e2baba228cdcbaf0a4e13f4b59cfaf76 --- /dev/null +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiValidationError.java @@ -0,0 +1,19 @@ +package org.fuseri.modulecertificate.exceptions; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Data +@EqualsAndHashCode(callSuper = false) +@AllArgsConstructor +@Getter +@ToString +class ApiValidationError implements ApiSubError { + private String object; + private String field; + private Object rejectedValue; + private String message; +} diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/RestResponseEntityExceptionHandler.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/RestResponseEntityExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..3161f0e18cc5be5e005110b135161879910a6b8b --- /dev/null +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/RestResponseEntityExceptionHandler.java @@ -0,0 +1,76 @@ +package org.fuseri.modulecertificate.exceptions; + +import jakarta.persistence.EntityNotFoundException; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.util.UrlPathHelper; + +import java.util.List; + +@ControllerAdvice +public class RestResponseEntityExceptionHandler { + private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper(); + + /** + * Handle ResourceNotFoundException exceptions + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler(value = {EntityNotFoundException.class}) + public ResponseEntity<ApiError> handleNotFoundError(EntityNotFoundException ex, HttpServletRequest request) { + ApiError error = new ApiError( + HttpStatus.NOT_FOUND, + ex, + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + /** + * Handle Validation exceptions + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler(value = {MethodArgumentNotValidException.class, HttpMessageNotReadableException.class}) + public ResponseEntity<ApiError> handleValidationErrors(MethodArgumentNotValidException ex, HttpServletRequest request) { + List<ApiSubError> subErrors = ex.getBindingResult().getFieldErrors() + .stream() + .map(e -> (ApiSubError) new ApiValidationError(e.getObjectName(), e.getField(), e.getRejectedValue(), e.getDefaultMessage())) + .toList(); + ApiError error = new ApiError( + HttpStatus.BAD_REQUEST, + subErrors, + ex, + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + /** + * Handle exceptions not matched by above handler methods + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler({Exception.class}) + public ResponseEntity<ApiError> handleAll(final Exception ex, HttpServletRequest request) { + final ApiError error = new ApiError( + HttpStatus.INTERNAL_SERVER_ERROR, + ExceptionUtils.getRootCause(ex), + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) { + return new ResponseEntity<>(apiError, apiError.getStatus()); + } +} diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/ResourceNotFoundException.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/ResourceNotFoundException.java deleted file mode 100644 index d24c445da1fcc4ba826a0e614778823400a4c719..0000000000000000000000000000000000000000 --- a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/service/ResourceNotFoundException.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.fuseri.modulecertificate.service; - -public class ResourceNotFoundException extends RuntimeException { - public ResourceNotFoundException() { - } - - public ResourceNotFoundException(String message) { - super(message); - } - - public ResourceNotFoundException(String message, Throwable cause) { - super(message, cause); - } - - public ResourceNotFoundException(Throwable cause) { - super(cause); - } - - public ResourceNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateControllerTests.java b/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateControllerTests.java index a74e9f10e84d11bdd60549886981f91d9d480025..77f5c3eec1edab3d80211e8004de540362c0fcfe 100644 --- a/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateControllerTests.java +++ b/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateControllerTests.java @@ -3,13 +3,13 @@ package org.fuseri.modulecertificate; import com.fasterxml.jackson.databind.ObjectMapper; import org.fuseri.model.dto.certificate.CertificateCreateDto; import org.fuseri.model.dto.certificate.CertificateSimpleDto; -import org.fuseri.model.dto.course.CourseDto; +import org.fuseri.model.dto.course.CourseCertificateDto; import org.fuseri.model.dto.course.LanguageTypeDto; import org.fuseri.model.dto.course.ProficiencyLevelDto; import org.fuseri.model.dto.user.AddressDto; import org.fuseri.model.dto.user.UserDto; import org.fuseri.model.dto.user.UserType; -import org.fuseri.modulecertificate.service.CertificateFacade; +import org.fuseri.modulecertificate.certificate.CertificateFacade; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @@ -24,6 +24,7 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import java.time.Instant; +import java.util.HashMap; import java.util.List; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; @@ -35,8 +36,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. class CertificateControllerTests { private final UserDto USER = new UserDto("novakovat", - "novakova@gamil.com", "Tereza", "Nováková", new AddressDto(), UserType.STUDENT); - private final CourseDto COURSE = new CourseDto("AJ1", 10, + "novakova@gamil.com", "Tereza", "Nováková", + new AddressDto("USA", "New York", "Main Street", "123", "10001"), + UserType.STUDENT, new HashMap<>()); + private final CourseCertificateDto COURSE = new CourseCertificateDto("AJ1", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.A1); private final CertificateCreateDto certificateCreateDto = new CertificateCreateDto(USER, COURSE); private final CertificateSimpleDto certificateDto = new CertificateSimpleDto(0L, USER.getId(), @@ -56,9 +59,26 @@ class CertificateControllerTests { } } + @Test + void generateCertificate() throws Exception { + Mockito.when(certificateFacade.generate(ArgumentMatchers.any(CertificateCreateDto.class))) + .thenReturn(certificateDto); + + mockMvc.perform(post("/certificates") + .content(asJsonString(certificateCreateDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.id").value(certificateDto.getId())) + .andExpect(jsonPath("$.userId").value(certificateDto.getUserId())) + .andExpect(jsonPath("$.generatedAt").value(certificateDto.getGeneratedAt().toString())) + .andExpect(jsonPath("$.courseId").value(certificateDto.getCourseId())) + .andExpect(jsonPath("$.certificateFile").value(certificateDto.getCertificateFile())) + .andExpect(jsonPath("$.certificateFileName").value(certificateDto.getCertificateFileName())); + } + @Test void generateCertificateWithNullUser() throws Exception { - mockMvc.perform(post("/certificates/generate") + mockMvc.perform(post("/certificates") .content(asJsonString(new CertificateCreateDto(null, COURSE))) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is4xxClientError()); @@ -66,7 +86,7 @@ class CertificateControllerTests { @Test void generateCertificateWithNullCourse() throws Exception { - mockMvc.perform(post("/certificates/generate") + mockMvc.perform(post("/certificates") .content(asJsonString(new CertificateCreateDto(USER, null))) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is4xxClientError()); @@ -74,7 +94,7 @@ class CertificateControllerTests { @Test void generateCertificateWithoutParams() throws Exception { - mockMvc.perform(post("/certificates/generate") + mockMvc.perform(post("/certificates") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is4xxClientError()); } @@ -107,7 +127,7 @@ class CertificateControllerTests { @Test void findCertificatesWithoutUserId() throws Exception { mockMvc.perform(get("/certificates/findForUser")) - .andExpect(status().is4xxClientError()); + .andExpect(status().is5xxServerError()); } @Test @@ -128,20 +148,20 @@ class CertificateControllerTests { void findCertificateIdWithoutUserId() throws Exception { mockMvc.perform(get("/certificates/findForUserAndCourse") .param("courseId", "0")) - .andExpect(status().is4xxClientError()); + .andExpect(status().is5xxServerError()); } @Test void findCertificateIdWithoutCourseId() throws Exception { mockMvc.perform(get("/certificates/findForUserAndCourse") .param("userId", "0")) - .andExpect(status().is4xxClientError()); + .andExpect(status().is5xxServerError()); } @Test void findCertificateIdWithoutParams() throws Exception { mockMvc.perform(get("/certificates/findForUserAndCourse")) - .andExpect(status().is4xxClientError()); + .andExpect(status().is5xxServerError()); } @Test diff --git a/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateFacadeTests.java b/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateFacadeTests.java index 6875ef6247bd33155f98d8bf6c651cabc2fd0f04..99784c042c37a2f01d57045e2ccfc002c3c0cff2 100644 --- a/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateFacadeTests.java +++ b/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateFacadeTests.java @@ -2,15 +2,16 @@ package org.fuseri.modulecertificate; import org.fuseri.model.dto.certificate.CertificateCreateDto; import org.fuseri.model.dto.certificate.CertificateSimpleDto; -import org.fuseri.model.dto.course.CourseDto; +import org.fuseri.model.dto.course.CourseCertificateDto; import org.fuseri.model.dto.course.LanguageTypeDto; import org.fuseri.model.dto.course.ProficiencyLevelDto; import org.fuseri.model.dto.user.AddressDto; import org.fuseri.model.dto.user.UserDto; import org.fuseri.model.dto.user.UserType; -import org.fuseri.modulecertificate.service.CertificateFacade; -import org.fuseri.modulecertificate.service.CertificateMapper; -import org.fuseri.modulecertificate.service.CertificateService; +import org.fuseri.modulecertificate.certificate.Certificate; +import org.fuseri.modulecertificate.certificate.CertificateFacade; +import org.fuseri.modulecertificate.certificate.CertificateMapper; +import org.fuseri.modulecertificate.certificate.CertificateService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -34,7 +35,7 @@ import static org.mockito.Mockito.when; final class CertificateFacadeTests { private final UserDto USER = new UserDto("novakovat", "novakova@gamil.com", "Tereza", "Nováková", new AddressDto(), UserType.STUDENT); - private final CourseDto COURSE = new CourseDto("AJ1", 10, + private final CourseCertificateDto COURSE = new CourseCertificateDto("AJ1", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.A1); private final CertificateCreateDto certificateCreateDto = new CertificateCreateDto(USER, COURSE); private final CertificateSimpleDto certificateDto = new CertificateSimpleDto(0L, USER.getId(), diff --git a/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateMapperTests.java b/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateMapperTests.java index 00cf08aa380c2a3d068315df2bbd5119c7acb08d..7f66f907759c6ddeb9c0012cad1425147bad1cce 100644 --- a/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateMapperTests.java +++ b/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateMapperTests.java @@ -2,13 +2,14 @@ package org.fuseri.modulecertificate; import org.fuseri.model.dto.certificate.CertificateCreateDto; import org.fuseri.model.dto.certificate.CertificateSimpleDto; -import org.fuseri.model.dto.course.CourseDto; +import org.fuseri.model.dto.course.CourseCertificateDto; import org.fuseri.model.dto.course.LanguageTypeDto; import org.fuseri.model.dto.course.ProficiencyLevelDto; import org.fuseri.model.dto.user.AddressDto; import org.fuseri.model.dto.user.UserDto; import org.fuseri.model.dto.user.UserType; -import org.fuseri.modulecertificate.service.CertificateMapper; +import org.fuseri.modulecertificate.certificate.Certificate; +import org.fuseri.modulecertificate.certificate.CertificateMapper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -26,7 +27,7 @@ final class CertificateMapperTests { private final UserDto USER = new UserDto("novakovat", "novakova@gamil.com", "Tereza", "Nováková", new AddressDto(), UserType.STUDENT); - private final CourseDto COURSE = new CourseDto("AJ1", 10, + private final CourseCertificateDto COURSE = new CourseCertificateDto("AJ1", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.A1); private final Instant instant = Instant.now(); private final String fileName = "fileName"; diff --git a/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateRepositoryTests.java b/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateRepositoryTests.java index 34ca57badbed1ae6a79c31b20ef4f810e40fc14c..08ae933b958fea4bfc16265c9bd209a6f443ddf7 100644 --- a/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateRepositoryTests.java +++ b/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateRepositoryTests.java @@ -1,6 +1,7 @@ package org.fuseri.modulecertificate; -import org.fuseri.modulecertificate.service.CertificateRepository; +import org.fuseri.modulecertificate.certificate.Certificate; +import org.fuseri.modulecertificate.certificate.CertificateRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateServiceTests.java b/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateServiceTests.java index dc11b78a1734722c0926a440e16329a8853bfeff..4ab79d90ffbd16c7bd4e521bc93e73d7ebbdcba9 100644 --- a/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateServiceTests.java +++ b/application/module-certificate/src/test/java/org/fuseri/modulecertificate/CertificateServiceTests.java @@ -6,8 +6,9 @@ import org.fuseri.model.dto.course.ProficiencyLevelDto; import org.fuseri.model.dto.user.AddressDto; import org.fuseri.model.dto.user.UserDto; import org.fuseri.model.dto.user.UserType; -import org.fuseri.modulecertificate.service.CertificateRepository; -import org.fuseri.modulecertificate.service.CertificateService; +import org.fuseri.modulecertificate.certificate.Certificate; +import org.fuseri.modulecertificate.certificate.CertificateRepository; +import org.fuseri.modulecertificate.certificate.CertificateService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerController.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerController.java index b3829f98eff78333be094ae152fc0f6a5425d9d6..87ac6a7d115550a4053de3a7b47c3eaf77863f3b 100644 --- a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerController.java +++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerController.java @@ -3,7 +3,6 @@ package org.fuseri.moduleexercise.answer; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.persistence.EntityNotFoundException; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import org.fuseri.model.dto.exercise.AnswerCreateDto; @@ -49,11 +48,7 @@ public class AnswerController { }) @PostMapping public ResponseEntity<AnswerDto> create(@Valid @RequestBody AnswerCreateDto dto) { - try { - return ResponseEntity.status(HttpStatus.CREATED).body(facade.create(dto)); - } catch (EntityNotFoundException e) { - return ResponseEntity.notFound().build(); - } + return ResponseEntity.status(HttpStatus.CREATED).body(facade.create(dto)); } /** @@ -72,11 +67,7 @@ public class AnswerController { }) @PutMapping("/{id}") public ResponseEntity<AnswerDto> update(@NotNull @PathVariable long id, @Valid @RequestBody AnswerCreateDto dto) { - try { - return ResponseEntity.ok(facade.update(id, dto)); - } catch (EntityNotFoundException e) { - return ResponseEntity.notFound().build(); - } + return ResponseEntity.ok(facade.update(id, dto)); } /** @@ -92,11 +83,7 @@ public class AnswerController { }) @DeleteMapping("/{id}") public ResponseEntity<Void> delete(@NotNull @PathVariable long id) { - try { - facade.delete(id); - return ResponseEntity.noContent().build(); - } catch (EntityNotFoundException e) { - return ResponseEntity.notFound().build(); - } + facade.delete(id); + return ResponseEntity.noContent().build(); } } diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerService.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerService.java index f5346fc5993874b4de7ca7d49fe39ba8c17ee6ba..c7903475ff42ba8341446bdcdbb4a12cab98ef45 100644 --- a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerService.java +++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerService.java @@ -7,7 +7,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; /** * Represent a service for managing Answer entities @@ -31,21 +30,6 @@ public class AnswerService extends DomainService<Answer> { this.repository = repository; } - /** - * Retrieve a list of Answer entities with the specified question ID - * - * @param questionId the ID of the question to retrieve answers for - * @return a list of Answer entities with the specified question ID - * @throws EntityNotFoundException if question with questionId does not exist - */ - @Transactional(readOnly = true) - public List<Answer> findAllByQuestionId(long questionId) { - if (!getRepository().existsById(questionId)) { - throw new EntityNotFoundException("Question with id " + questionId + " not found."); - } - return repository.findByQuestionId(questionId); - } - /** * Retrieve the Answer entity with the specified id * diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/ApiError.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/ApiError.java new file mode 100644 index 0000000000000000000000000000000000000000..6d1e2622531492e1f65615a71a50be2ab933cf78 --- /dev/null +++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/ApiError.java @@ -0,0 +1,41 @@ +package org.fuseri.moduleexercise.exceptions; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@ToString +class ApiError { + + private HttpStatus status; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") + private LocalDateTime timestamp; + private String message; + private List<ApiSubError> subErrors; + private String path; + + private ApiError() { + timestamp = LocalDateTime.now(Clock.systemUTC()); + } + + ApiError(HttpStatus status, Throwable ex, String path) { + this(); + this.status = status; + this.path = path; + this.message = ex.getLocalizedMessage(); + } + + ApiError(HttpStatus status, List<ApiSubError> subErrors, Throwable ex, String path) { + this(); + this.status = status; + this.subErrors = subErrors; + this.path = path; + this.message = ex.getLocalizedMessage(); + } +} \ No newline at end of file diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/ApiSubError.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/ApiSubError.java new file mode 100644 index 0000000000000000000000000000000000000000..3c34fae9c17a204a05c129c188f71fdd42f191f8 --- /dev/null +++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/ApiSubError.java @@ -0,0 +1,4 @@ +package org.fuseri.moduleexercise.exceptions; + +interface ApiSubError { +} diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/ApiValidationError.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/ApiValidationError.java new file mode 100644 index 0000000000000000000000000000000000000000..6fd4ed3fcab6c7def0207ca4cf9cbfa2d625f397 --- /dev/null +++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/ApiValidationError.java @@ -0,0 +1,19 @@ +package org.fuseri.moduleexercise.exceptions; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Data +@EqualsAndHashCode(callSuper = false) +@AllArgsConstructor +@Getter +@ToString +class ApiValidationError implements ApiSubError { + private String object; + private String field; + private Object rejectedValue; + private String message; +} \ No newline at end of file diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/RestResponseEntityExceptionHandler.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/RestResponseEntityExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..04b438126aec94aace781412f58ed51f4f8688c9 --- /dev/null +++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exceptions/RestResponseEntityExceptionHandler.java @@ -0,0 +1,76 @@ +package org.fuseri.moduleexercise.exceptions; + +import jakarta.persistence.EntityNotFoundException; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.util.UrlPathHelper; + +import java.util.List; + +@ControllerAdvice +public class RestResponseEntityExceptionHandler { + private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper(); + + /** + * Handle ResourceNotFoundException exceptions + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler(value = {EntityNotFoundException.class}) + public ResponseEntity<ApiError> handleNotFoundError(EntityNotFoundException ex, HttpServletRequest request) { + ApiError error = new ApiError( + HttpStatus.NOT_FOUND, + ex, + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + /** + * Handle Validation exceptions + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler(value = {MethodArgumentNotValidException.class, HttpMessageNotReadableException.class}) + public ResponseEntity<ApiError> handleValidationErrors(MethodArgumentNotValidException ex, HttpServletRequest request) { + List<ApiSubError> subErrors = ex.getBindingResult().getFieldErrors() + .stream() + .map(e -> (ApiSubError) new ApiValidationError(e.getObjectName(), e.getField(), e.getRejectedValue(), e.getDefaultMessage())) + .toList(); + ApiError error = new ApiError( + HttpStatus.BAD_REQUEST, + subErrors, + ex, + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + /** + * Handle exceptions not matched by above handler methods + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler({Exception.class}) + public ResponseEntity<ApiError> handleAll(final Exception ex, HttpServletRequest request) { + final ApiError error = new ApiError( + HttpStatus.INTERNAL_SERVER_ERROR, + ExceptionUtils.getRootCause(ex), + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) { + return new ResponseEntity<>(apiError, apiError.getStatus()); + } +} \ No newline at end of file diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseController.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseController.java index 96f91fc77b92040b231cc51218ff393a26184530..dd437b24997be84a50b5653ca8cc68554ae7ae6a 100644 --- a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseController.java +++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseController.java @@ -3,7 +3,6 @@ package org.fuseri.moduleexercise.exercise; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.persistence.EntityNotFoundException; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PositiveOrZero; @@ -75,11 +74,7 @@ public class ExerciseController { }) @GetMapping("/{id}") public ResponseEntity<ExerciseDto> find(@NotNull @PathVariable long id) { - try { - return ResponseEntity.ok(facade.find(id)); - } catch (EntityNotFoundException e) { - return ResponseEntity.notFound().build(); - } + return ResponseEntity.ok(facade.find(id)); } /** @@ -135,12 +130,8 @@ public class ExerciseController { @GetMapping("/{exercise-id}/questions") public ResponseEntity<Page<QuestionDto>> findQuestions(@NotNull @PathVariable("exercise-id") long exerciseId, @PositiveOrZero @RequestParam int page) { - try { - Page<QuestionDto> questions = facade.getQuestions(exerciseId, page); - return ResponseEntity.ok(questions); - } catch (EntityNotFoundException e) { - return ResponseEntity.notFound().build(); - } + Page<QuestionDto> questions = facade.getQuestions(exerciseId, page); + return ResponseEntity.ok(questions); } /** @@ -159,11 +150,7 @@ public class ExerciseController { }) @PutMapping("/{id}") public ResponseEntity<ExerciseDto> update(@NotNull @PathVariable long id, @Valid @RequestBody ExerciseCreateDto dto) { - try { - return ResponseEntity.ok(facade.update(id, dto)); - } catch (EntityNotFoundException e) { - return ResponseEntity.notFound().build(); - } + return ResponseEntity.ok(facade.update(id, dto)); } /** diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/Question.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/Question.java index 03f62910b63062589484675c9f4d1ba436ec60c5..b3ff308eba12f885063011f0a0f702e71b138ee8 100644 --- a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/Question.java +++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/Question.java @@ -2,6 +2,7 @@ package org.fuseri.moduleexercise.question; import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; @@ -35,7 +36,7 @@ public class Question extends DomainObject { @OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true) private Set<Answer> answers = new HashSet<>(); - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "exercise_id", nullable = false) private Exercise exercise; @@ -46,5 +47,8 @@ public class Question extends DomainObject { */ public void addAnswers(Set<Answer> answersToAdd) { answers.addAll(answersToAdd); + for (var answer : answersToAdd) { + answer.setQuestion(this); + } } } diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionController.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionController.java index 9857e1c3d46120979b04339b2ddf0293c22c0ed8..e494fcb0e7e2f5063d3ae91dd485cbdff9747e0d 100644 --- a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionController.java +++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionController.java @@ -5,7 +5,6 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.persistence.EntityNotFoundException; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import org.fuseri.model.dto.exercise.AnswerDto; @@ -63,11 +62,7 @@ public class QuestionController { }) @GetMapping("/{id}") public ResponseEntity<QuestionDto> find(@NotNull @PathVariable long id) { - try { - return ResponseEntity.ok(questionFacade.find(id)); - } catch (EntityNotFoundException e) { - return ResponseEntity.notFound().build(); - } + return ResponseEntity.ok(questionFacade.find(id)); } /** @@ -83,11 +78,7 @@ public class QuestionController { @ApiResponse(responseCode = "404", description = "Question not found") @GetMapping("/{id}/answers") public ResponseEntity<List<AnswerDto>> getQuestionAnswers(@NotNull @PathVariable long id) { - try { - return ResponseEntity.ok(questionFacade.getQuestionAnswers(id)); - } catch (EntityNotFoundException e) { - return ResponseEntity.notFound().build(); - } + return ResponseEntity.ok(questionFacade.getQuestionAnswers(id)); } /** @@ -104,13 +95,9 @@ public class QuestionController { @ApiResponse(responseCode = "404", description = "Exercise with the specified ID was not found.") }) @PostMapping - public ResponseEntity<QuestionDto> addQuestion(@Valid @RequestBody QuestionCreateDto dto) { - try { - QuestionDto createdQuestionDto = questionFacade.create(dto); - return ResponseEntity.status(HttpStatus.CREATED).body(createdQuestionDto); - } catch (EntityNotFoundException e) { - return ResponseEntity.notFound().build(); - } + public ResponseEntity<QuestionDto> createQuestion(@Valid @RequestBody QuestionCreateDto dto) { + QuestionDto createdQuestionDto = questionFacade.create(dto); + return ResponseEntity.status(HttpStatus.CREATED).body(createdQuestionDto); } /** @@ -127,11 +114,7 @@ public class QuestionController { }) @PutMapping("/{id}") public ResponseEntity<QuestionDto> updateQuestion(@NotNull @PathVariable long id, @Valid @RequestBody QuestionUpdateDto dto) { - try { - return ResponseEntity.ok(questionFacade.patchUpdate(id, dto)); - } catch (EntityNotFoundException e) { - return ResponseEntity.notFound().build(); - } + return ResponseEntity.ok(questionFacade.patchUpdate(id, dto)); } /** diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionFacade.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionFacade.java index 8fe1e2a19f4fd2663aa3a22e0e5f9848829f8800..65070d1376f9a196ef23fdb3654f45e21422e6ca 100644 --- a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionFacade.java +++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionFacade.java @@ -7,10 +7,10 @@ import org.fuseri.model.dto.exercise.QuestionCreateDto; import org.fuseri.model.dto.exercise.QuestionDto; import org.fuseri.model.dto.exercise.QuestionUpdateDto; import org.fuseri.moduleexercise.answer.AnswerMapper; -import org.fuseri.moduleexercise.answer.AnswerService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; /** @@ -21,17 +21,14 @@ import java.util.List; @Service public class QuestionFacade { private final QuestionService questionService; - private final AnswerService answerService; private final QuestionMapper questionMapper; private final AnswerMapper answerMapper; @Autowired public QuestionFacade( - QuestionService questionService, - AnswerService answerService, QuestionMapper questionMapper, + QuestionService questionService, QuestionMapper questionMapper, AnswerMapper answerMapper) { this.questionService = questionService; - this.answerService = answerService; this.questionMapper = questionMapper; this.answerMapper = answerMapper; } @@ -53,7 +50,8 @@ public class QuestionFacade { * @return a List of AnswerDto objects */ public List<AnswerDto> getQuestionAnswers(long questionId) { - return answerMapper.toDtoList(answerService.findAllByQuestionId(questionId)); + var question = questionService.find(questionId); + return answerMapper.toDtoList(new ArrayList<>(question.getAnswers())); } /** diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionService.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionService.java index f0676eee28256d926e768b0fee5df3cf52275fac..d6a44f0b988642b23b34105001559f59dc6a6338 100644 --- a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionService.java +++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionService.java @@ -83,6 +83,19 @@ public class QuestionService extends DomainService<Question> { throw new EntityNotFoundException( "Question with id: " + id + " was not found."); } + } + /** + * Create a question with answers + * + * @param question the question to create + * @return the created question + */ + @Override + public Question create(Question question) { + for (var answer: question.getAnswers()) { + answer.setQuestion(question); + } + return getRepository().save(question); } } diff --git a/application/module-exercise/src/test/java/org/fuseri/moduleexercise/answer/AnswerServiceTest.java b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/answer/AnswerServiceTest.java index ee53178ff5e7f13e73f3ed97da52421e3c6e3dcb..04b364bbff8b255c6339406ef4eb778f3b464552 100644 --- a/application/module-exercise/src/test/java/org/fuseri/moduleexercise/answer/AnswerServiceTest.java +++ b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/answer/AnswerServiceTest.java @@ -9,9 +9,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Optional; import static org.mockito.ArgumentMatchers.anyLong; @@ -62,17 +60,4 @@ class AnswerServiceTest { Assertions.assertEquals(answer, result); verify(answerRepository).findById(anyLong()); } - - @Test - void findAllByQuestionId() { - long id = 1; - List<Answer> list = Collections.emptyList(); - - when(answerRepository.existsById(id)).thenReturn(true); - when(answerRepository.findByQuestionId(id)).thenReturn(list); - List<Answer> result = service.findAllByQuestionId(1L); - - Assertions.assertEquals(list, result); - verify(answerRepository).findByQuestionId(id); - } } diff --git a/application/module-exercise/src/test/java/org/fuseri/moduleexercise/exercise/ExerciseMapperTest.java b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/exercise/ExerciseMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..239bfcdbbe463fdd4f6ef02b649f756303faf2a6 --- /dev/null +++ b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/exercise/ExerciseMapperTest.java @@ -0,0 +1,47 @@ +package org.fuseri.moduleexercise.exercise; + +import org.fuseri.model.dto.exercise.ExerciseCreateDto; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ExerciseMapperTest { + + private final ExerciseCreateDto exerciseCreateDto = new ExerciseCreateDto( + "History exercise", "Great", 2, 4); + + + @Autowired + private ExerciseMapper exerciseMapper; + + @Test + void mapNullEntityToDto() { + var entity = exerciseMapper.fromCreateDto(null); + Assertions.assertNull(entity); + } + + @Test + void mapFromNullCreateDto() { + var entity = exerciseMapper.fromCreateDto(null); + Assertions.assertNull(entity); + } + + @Test + void mapFromCreateDto() { + var entity = exerciseMapper.fromCreateDto(exerciseCreateDto); + Assertions.assertNotNull(entity); + } + + + + + @Test + void mapNullToDto() { + var createdDto = exerciseMapper.toDto(null); + Assertions.assertNull(createdDto); + } + + +} diff --git a/application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionTest.java b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionControllerTest.java similarity index 68% rename from application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionTest.java rename to application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionControllerTest.java index 8383971d23f186ac9740263ebb47ed4ab303be1f..0f5cbfa707c3660a91f6ab7efaf342fc14a88d3b 100644 --- a/application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionTest.java +++ b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionControllerTest.java @@ -4,43 +4,32 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.persistence.EntityNotFoundException; import org.fuseri.model.dto.exercise.AnswerDto; import org.fuseri.model.dto.exercise.AnswerInQuestionCreateDto; -import org.fuseri.model.dto.exercise.ExerciseCreateDto; import org.fuseri.model.dto.exercise.QuestionCreateDto; import org.fuseri.model.dto.exercise.QuestionDto; import org.fuseri.model.dto.exercise.QuestionUpdateDto; -import org.fuseri.moduleexercise.exercise.Exercise; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatchers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultActions; + +import java.util.ArrayList; +import java.util.List; import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.util.ArrayList; -import java.util.List; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; - @SpringBootTest @AutoConfigureMockMvc -public class QuestionTest { +public class QuestionControllerTest { @Autowired ObjectMapper objectMapper; @@ -60,28 +49,9 @@ public class QuestionTest { } } - private void createExercise() { - var postExercise = new ExerciseCreateDto("idioms", "exercise on basic idioms", 2, 0L); - var postExercise2 = new ExerciseCreateDto("riddles", "simple english riddles", 2, 0L); - try { - mockMvc.perform(post("/exercises").content(asJsonString(postExercise)).contentType(MediaType.APPLICATION_JSON)); - mockMvc.perform(post("/exercises").content(asJsonString(postExercise2)).contentType(MediaType.APPLICATION_JSON)); - - } catch (Exception e) { - assert (false); - } - } - @BeforeEach void init() { -// createExercise(); - - qston = new QuestionDto("\"what is the meaning of: costs an arm and leg\"",1,new ArrayList<>()); - long id = 2; - - var question = new QuestionCreateDto("this statement is false", id, List.of(new AnswerInQuestionCreateDto("dis a logical paradox", true))); - var question2 = new QuestionCreateDto("What month of the year has 28 days?", id, List.of(new AnswerInQuestionCreateDto("February", false), new AnswerInQuestionCreateDto("All of them", true))); - ResultActions posted = null; + qston = new QuestionDto("\"what is the meaning of: costs an arm and leg\"", 1, new ArrayList<>()); } @@ -94,7 +64,7 @@ public class QuestionTest { posted.andExpect(status().isCreated()) .andExpect(jsonPath("$.text", is(qston.getText()))) - .andExpect(jsonPath("$.exerciseId", is((int)qston.getExerciseId()))); + .andExpect(jsonPath("$.exerciseId", is((int) qston.getExerciseId()))); } @@ -137,18 +107,10 @@ public class QuestionTest { posted.andExpect(status().is4xxClientError()); } - private QuestionDto createQuestion(long id) throws Exception { - var question = new QuestionCreateDto("this statement is false", id, List.of(new AnswerInQuestionCreateDto("dis a logical paradox", true))); - var posted = mockMvc.perform(post("/questions").content(asJsonString(question)).contentType(MediaType.APPLICATION_JSON)); - var cont = posted.andReturn().getResponse().getContentAsString(); - var res = objectMapper.readValue(cont, QuestionDto.class); - return res; - } - @Test void getQuestion() throws Exception { - var question = new QuestionDto("this statement is false",1L,new ArrayList<>()); + var question = new QuestionDto("this statement is false", 1L, new ArrayList<>()); when(facade.find(1)).thenReturn(question); var gets = mockMvc.perform(get("/questions/1")); gets.andExpect(status().isOk()).andExpect(jsonPath("$.text", is("this statement is false"))); @@ -164,8 +126,6 @@ public class QuestionTest { @Test void getAnswer() throws Exception { - - var sss = List.of(new AnswerDto("February", false), new AnswerDto("All of them", true)); when(facade.getQuestionAnswers(2)).thenReturn(sss); var gets = mockMvc.perform(get("/questions/2/answers")); @@ -177,7 +137,7 @@ public class QuestionTest { } @Test - void TestUpdate() throws Exception { + void testUpdate() throws Exception { var updated = """ { "text": "wat a paradox?", @@ -185,7 +145,7 @@ public class QuestionTest { } """; - var question = new QuestionDto("wat a paradox?",1,new ArrayList<>()); + var question = new QuestionDto("wat a paradox?", 1, new ArrayList<>()); when(facade.patchUpdate(ArgumentMatchers.eq(1), ArgumentMatchers.isA(QuestionUpdateDto.class))).thenReturn(question); var gets = mockMvc.perform(put("/questions/1").content(updated).contentType(MediaType.APPLICATION_JSON)); @@ -193,13 +153,13 @@ public class QuestionTest { gets.andExpect(status().isOk()); } -@Test + @Test void deleteExisting() { - try { - mockMvc.perform(delete("/questions/1")).andExpect(status().isNoContent()); - } catch (Exception e) { - throw new RuntimeException(e); + try { + mockMvc.perform(delete("/questions/1")).andExpect(status().isNoContent()); + } catch (Exception e) { + throw new RuntimeException(e); + } } -} } diff --git a/application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionFacadeTest.java b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionFacadeTest.java index 33e1ca0a8f8b9ff86d3ec16f12a5df86738c5d29..b9123098f93b35c413250cd96ec6b76d9d7e64f6 100644 --- a/application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionFacadeTest.java +++ b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionFacadeTest.java @@ -102,9 +102,10 @@ class QuestionFacadeTest { void getQuestionAnswers() { long id = 1L; List<Answer> answers = List.of(new Answer("BA", true, question)); + question.setAnswers(new HashSet<>(answers)); List<AnswerDto> answerDtos = List.of(new AnswerDto("BA", true)); + when(questionService.find(id)).thenReturn(question); when(answerMapper.toDtoList(answers)).thenReturn(answerDtos); - when(answerService.findAllByQuestionId(id)).thenReturn(answers); var actualAnswers = questionFacade.getQuestionAnswers(id); assertEquals(answerDtos, actualAnswers); } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java index fa2b1c1f3cd92a328a4c53f28ad2c6b31a21a1bd..03668ba1289468d8f98c7daa784ad1d244c4be56 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java @@ -30,6 +30,15 @@ public class Course extends DomainObject { private List<User> students; private boolean finished = false; + + public Course(String name, int capacity, Language language, ProficiencyLevel proficiency) { + this.name = name; + this.capacity = capacity; + this.language = language; + this.proficiency = proficiency; + this.students = new ArrayList<>(); + } + public void enrolStudent(User student) { students.add(student); } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java index 2011884b168ead61234283c8fa115b16e059492c..62dba59d4ccd5a1d0c53d0213d57004974243b74 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java @@ -9,7 +9,6 @@ import org.fuseri.model.dto.course.CourseCreateDto; import org.fuseri.model.dto.course.CourseDto; import org.fuseri.model.dto.course.LanguageTypeDto; import org.fuseri.model.dto.course.ProficiencyLevelDto; -import org.fuseri.model.dto.user.UserDto; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -103,6 +102,7 @@ public class CourseController { List<CourseDto> courseDtos = courseFacade.findAll(lang); return ResponseEntity.ok(courseDtos); } + /** * Retrieves a paginated list of courses of a given language and proficiency * @@ -117,7 +117,7 @@ public class CourseController { @ApiResponse(code = 400, message = "Invalid request body") }) public ResponseEntity<List<CourseDto>> findAll(@RequestParam LanguageTypeDto lang, - @RequestParam ProficiencyLevelDto prof) { + @RequestParam ProficiencyLevelDto prof) { List<CourseDto> courses = courseFacade.findAll(lang, prof); return ResponseEntity.ok(courses); } @@ -188,8 +188,8 @@ public class CourseController { @ApiResponse(code = 200, message = "Successfully expelled student from course"), @ApiResponse(code = 404, message = "Course not found") }) - public ResponseEntity<CourseDto> expel(@PathVariable Long id, @RequestBody UserDto student) { - CourseDto updatedCourse = courseFacade.expel(id, student); + public ResponseEntity<CourseDto> expel(@PathVariable Long id, @RequestParam Long studentId) { + CourseDto updatedCourse = courseFacade.expel(id, studentId); return ResponseEntity.ok(updatedCourse); } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java index dba3056b203a76632fa11943d512f7c68d3e5901..7640ae2653bd40b331dcb48623eb90188c15777e 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java @@ -4,7 +4,6 @@ import org.fuseri.model.dto.course.CourseCreateDto; import org.fuseri.model.dto.course.CourseDto; import org.fuseri.model.dto.course.LanguageTypeDto; import org.fuseri.model.dto.course.ProficiencyLevelDto; -import org.fuseri.model.dto.user.UserDto; import org.fuseri.modulelanguageschool.user.UserMapper; import org.fuseri.modulelanguageschool.user.UserService; import org.springframework.beans.factory.annotation.Autowired; @@ -71,7 +70,8 @@ public class CourseFacade { return courseMapper.mapToDto(courseService.enrol(id, student)); } - public CourseDto expel(Long id, UserDto student) { - return courseMapper.mapToDto(courseService.expel(id, userMapper.fromDto(student))); + public CourseDto expel(Long id, Long studentId) { + var student = userService.find(studentId); + return courseMapper.mapToDto(courseService.expel(id, student)); } } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseMapper.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseMapper.java index 9a5bf7c032fb11df53ef65ddad42b0e82230a7bf..89d3bbc90cf0ec35f564a73a78d6aba8d13b4c0d 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseMapper.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseMapper.java @@ -2,26 +2,78 @@ package org.fuseri.modulelanguageschool.course; import org.fuseri.model.dto.course.CourseCreateDto; import org.fuseri.model.dto.course.CourseDto; +import org.fuseri.model.dto.course.LanguageTypeDto; +import org.fuseri.model.dto.course.ProficiencyLevelDto; +import org.fuseri.modulelanguageschool.user.User; +import org.fuseri.modulelanguageschool.user.UserService; import org.mapstruct.Mapper; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; +import java.util.ArrayList; import java.util.List; -@Mapper(componentModel = "spring") +@Mapper(componentModel = "spring", uses = {UserService.class}) public interface CourseMapper { - CourseDto mapToDto(Course course); + default CourseDto mapToDto(Course course) { + if (course == null) { + return null; + } - Course mapToCourse(CourseDto courseDto); + var dto = new CourseDto(course.getName(), + course.getCapacity(), + LanguageTypeDto.valueOf(course.getLanguage().name()), + ProficiencyLevelDto.valueOf(course.getProficiency().name()), + new ArrayList<>(), + course.isFinished()); + dto.setId(course.getId()); + var students = new ArrayList<Long>(); + for (User user : course.getStudents()) { + students.add(user.getId()); + } + dto.setStudents(students); + return dto; + } - List<CourseDto> mapToList(List<Course> persons); + default Course mapToCourse(CourseDto dto, UserService userService) { + if (dto == null) { + return null; + } + var course = new Course(dto.getName(), + dto.getCapacity(), + Language.valueOf(dto.getLanguage().name()), + ProficiencyLevel.valueOf(dto.getProficiency().name()), + new ArrayList<>(), + dto.getFinished()); + course.setId(dto.getId()); + for (Long userId : dto.getStudents()) { + course.enrolStudent(userService.find(userId)); + } + return course; + } + default List<CourseDto> mapToList(List<Course> courses) { + if (courses == null) { + return null; + } + return new ArrayList<>(courses.stream().map(this::mapToDto).toList()); + } default Page<CourseDto> mapToPageDto(Page<Course> courses) { return new PageImpl<>(mapToList(courses.getContent()), courses.getPageable(), courses.getTotalPages()); } - Course mapToCourse(CourseCreateDto dto); + default Course mapToCourse(CourseCreateDto createDto) { + if (createDto == null) { + return null; + } + return new Course(createDto.getName(), + createDto.getCapacity(), + Language.valueOf(createDto.getLanguage().name()), + ProficiencyLevel.valueOf(createDto.getProficiency().name()), + new ArrayList<>(), + false); + } } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/ApiError.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/ApiError.java new file mode 100644 index 0000000000000000000000000000000000000000..7318305debe345eeb7f0f4f67a3fea86cd549915 --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/ApiError.java @@ -0,0 +1,41 @@ +package org.fuseri.modulelanguageschool.exceptions; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@ToString +class ApiError { + + private HttpStatus status; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") + private LocalDateTime timestamp; + private String message; + private List<ApiSubError> subErrors; + private String path; + + private ApiError() { + timestamp = LocalDateTime.now(Clock.systemUTC()); + } + + ApiError(HttpStatus status, Throwable ex, String path) { + this(); + this.status = status; + this.path = path; + this.message = ex.getLocalizedMessage(); + } + + ApiError(HttpStatus status, List<ApiSubError> subErrors, Throwable ex, String path) { + this(); + this.status = status; + this.subErrors = subErrors; + this.path = path; + this.message = ex.getLocalizedMessage(); + } +} diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/ApiSubError.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/ApiSubError.java new file mode 100644 index 0000000000000000000000000000000000000000..dcb35ff3ca93e2aa9430558dd1a34e77532876c8 --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/ApiSubError.java @@ -0,0 +1,4 @@ +package org.fuseri.modulelanguageschool.exceptions; + +public interface ApiSubError { +} diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/ApiValidationError.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/ApiValidationError.java new file mode 100644 index 0000000000000000000000000000000000000000..b9962eceae7ee473a78999824e91922cd99b0a74 --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/ApiValidationError.java @@ -0,0 +1,19 @@ +package org.fuseri.modulelanguageschool.exceptions; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Data +@EqualsAndHashCode(callSuper = false) +@AllArgsConstructor +@Getter +@ToString +class ApiValidationError implements ApiSubError { + private String object; + private String field; + private Object rejectedValue; + private String message; +} diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/RestResponseEntityExceptionHandler.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/RestResponseEntityExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..0c6fb9d71ebbfde07f35e1f0fc94268111f902a3 --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/exceptions/RestResponseEntityExceptionHandler.java @@ -0,0 +1,76 @@ +package org.fuseri.modulelanguageschool.exceptions; + +import jakarta.persistence.EntityNotFoundException; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.util.UrlPathHelper; + +import java.util.List; + +@ControllerAdvice +public class RestResponseEntityExceptionHandler { + private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper(); + + /** + * Handle ResourceNotFoundException exceptions + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler(value = {EntityNotFoundException.class}) + public ResponseEntity<ApiError> handleNotFoundError(EntityNotFoundException ex, HttpServletRequest request) { + ApiError error = new ApiError( + HttpStatus.NOT_FOUND, + ex, + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + /** + * Handle Validation exceptions + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler(value = {MethodArgumentNotValidException.class, HttpMessageNotReadableException.class}) + public ResponseEntity<ApiError> handleValidationErrors(MethodArgumentNotValidException ex, HttpServletRequest request) { + List<ApiSubError> subErrors = ex.getBindingResult().getFieldErrors() + .stream() + .map(e -> (ApiSubError) new ApiValidationError(e.getObjectName(), e.getField(), e.getRejectedValue(), e.getDefaultMessage())) + .toList(); + ApiError error = new ApiError( + HttpStatus.BAD_REQUEST, + subErrors, + ex, + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + /** + * Handle exceptions not matched by above handler methods + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler({Exception.class}) + public ResponseEntity<ApiError> handleAll(final Exception ex, HttpServletRequest request) { + final ApiError error = new ApiError( + HttpStatus.INTERNAL_SERVER_ERROR, + ExceptionUtils.getRootCause(ex), + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) { + return new ResponseEntity<>(apiError, apiError.getStatus()); + } +} diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java index 80b4f8e154f831ff52fc87745e0596c9f57b8c66..8ace25211f77c35d3485cd4721d8cb09014454e8 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java @@ -7,7 +7,6 @@ import io.swagger.annotations.ApiResponses; import jakarta.validation.Valid; import org.fuseri.model.dto.lecture.LectureCreateDto; import org.fuseri.model.dto.lecture.LectureDto; -import org.fuseri.model.dto.user.UserDto; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -119,8 +118,8 @@ public class LectureController { /** * Adds lecturer to the existing lecture resource * - * @param id id of lecture to update - * @param lecturerDto UserDto for the course lecturer + * @param id id of lecture to update + * @param lecturerId UserDto for the course lecturer * @return the LectureDto representing the updated lecture */ @ApiOperation(value = "Add lecturer to the existing lecture") @@ -130,15 +129,15 @@ public class LectureController { @ApiResponse(code = 400, message = "The request body is invalid"), @ApiResponse(code = 404, message = "The lecture with the specified ID does not exist") }) - public ResponseEntity<LectureDto> setLecturer(@PathVariable Long id, @RequestBody UserDto lecturerDto) { - return ResponseEntity.ok(lectureFacade.setLecturer(id, lecturerDto)); + public ResponseEntity<LectureDto> setLecturer(@PathVariable Long id, @RequestParam Long lecturerId) { + return ResponseEntity.ok(lectureFacade.setLecturer(id, lecturerId)); } /** * Adds student to the existing lecture resource * - * @param id id of lecture to update - * @param student UserDto for the course student + * @param id id of lecture to update + * @param studentId id for the course student * @return the LectureDto representing the updated lecture */ @ApiOperation(value = "Add student to the existing lecture") @@ -148,15 +147,15 @@ public class LectureController { @ApiResponse(code = 400, message = "The request body is invalid"), @ApiResponse(code = 404, message = "The lecture with the specified ID does not exist") }) - public ResponseEntity<LectureDto> enrol(@PathVariable Long id, @RequestBody UserDto student) { - return ResponseEntity.ok(lectureFacade.enrol(id, student)); + public ResponseEntity<LectureDto> enrol(@PathVariable Long id, @RequestParam Long studentId) { + return ResponseEntity.ok(lectureFacade.enrol(id, studentId)); } /** * Removes student from the existing lecture resource * - * @param id id of lecture to update - * @param student UserDto for the course student + * @param id id of lecture to update + * @param studentId id for the course student * @return the LectureDto representing the updated lecture */ @ApiOperation(value = "Remove student from the existing lecture") @@ -166,7 +165,7 @@ public class LectureController { @ApiResponse(code = 400, message = "The request body is invalid"), @ApiResponse(code = 404, message = "The lecture with the specified ID does not exist") }) - public ResponseEntity<LectureDto> expel(@PathVariable Long id, @RequestBody UserDto student) { - return ResponseEntity.ok(lectureFacade.expel(id, student)); + public ResponseEntity<LectureDto> expel(@PathVariable Long id, @RequestParam Long studentId) { + return ResponseEntity.ok(lectureFacade.expel(id, studentId)); } } \ No newline at end of file diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureFacade.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureFacade.java index ce9214dd71a6f10b6a9aa47187692e3ff6470fd7..dbd9359d81345db74f3ab809772665dc35ad6919 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureFacade.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureFacade.java @@ -4,11 +4,11 @@ import org.fuseri.model.dto.course.LanguageTypeDto; import org.fuseri.model.dto.course.ProficiencyLevelDto; import org.fuseri.model.dto.lecture.LectureCreateDto; import org.fuseri.model.dto.lecture.LectureDto; -import org.fuseri.model.dto.user.UserDto; import org.fuseri.modulelanguageschool.course.CourseService; import org.fuseri.modulelanguageschool.course.Language; import org.fuseri.modulelanguageschool.course.ProficiencyLevel; import org.fuseri.modulelanguageschool.user.UserMapper; +import org.fuseri.modulelanguageschool.user.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @@ -21,13 +21,16 @@ import java.util.List; public class LectureFacade { private final LectureService lectureService; + + private final UserService userService; private final LectureMapper lectureMapper; private final UserMapper userMapper; private final CourseService courseService; @Autowired - public LectureFacade(LectureService lectureService, LectureMapper lectureMapper, UserMapper userMapper, CourseService courseService) { + public LectureFacade(LectureService lectureService, UserService userService, LectureMapper lectureMapper, UserMapper userMapper, CourseService courseService) { this.lectureService = lectureService; + this.userService = userService; this.lectureMapper = lectureMapper; this.userMapper = userMapper; this.courseService = courseService; @@ -67,15 +70,18 @@ public class LectureFacade { return lectureMapper.mapToList(lectureService.findAll(Language.valueOf(lang.name()), ProficiencyLevel.valueOf(prof.name()))); } - public LectureDto enrol(Long id, UserDto student) { - return lectureMapper.mapToDto(lectureService.enrol(id, userMapper.fromDto(student))); + public LectureDto enrol(Long id, long studentId) { + var student = userService.find(studentId); + return lectureMapper.mapToDto(lectureService.enrol(id, student)); } - public LectureDto expel(Long id, UserDto student) { - return lectureMapper.mapToDto(lectureService.expel(id, userMapper.fromDto(student))); + public LectureDto expel(Long id, Long studentId) { + var student = userService.find(studentId); + return lectureMapper.mapToDto(lectureService.expel(id, student)); } - public LectureDto setLecturer(Long id, UserDto lecturerDto) { - return lectureMapper.mapToDto(lectureService.setLecturer(id, userMapper.fromDto(lecturerDto))); + public LectureDto setLecturer(Long id, Long lecturerId) { + var lecturer = userService.find(lecturerId); + return lectureMapper.mapToDto(lectureService.setLecturer(id, lecturer)); } } diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseControllerTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseControllerTest.java index 9aeca9f70e9c027d6ed34b4eaa61f7f2d90e0e8e..e2ea898bf6b3357b45dc4a914ce541bfed507b73 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseControllerTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseControllerTest.java @@ -21,8 +21,6 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -36,7 +34,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. public class CourseControllerTest { private final CourseCreateDto courseCreateDto = new CourseCreateDto("english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); - private final CourseDto courseDto = new CourseDto("english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); + private final CourseDto courseDto = new CourseDto("english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2, new ArrayList<>(), false); @Autowired ObjectMapper objectMapper; @@ -116,7 +114,7 @@ public class CourseControllerTest { @Test void findAllWithoutPage() throws Exception { mockMvc.perform(get("/courses/findAll")) - .andExpect(status().is4xxClientError()); + .andExpect(status().is5xxServerError()); } @@ -130,12 +128,13 @@ public class CourseControllerTest { .param("lang", lang.toString())) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); - assertTrue(response.endsWith("[]")); } + assertTrue(response.endsWith("[]")); + } @Test void findAllByLangWithoutLang() throws Exception { mockMvc.perform(get("/courses/findAllByLang")) - .andExpect(status().is4xxClientError()); + .andExpect(status().is5xxServerError()); } @Test @@ -154,14 +153,14 @@ public class CourseControllerTest { @Test void findAllByLangProfWithoutParameters() throws Exception { mockMvc.perform(get("/courses/findAllByLangProf")) - .andExpect(status().is4xxClientError()); + .andExpect(status().is5xxServerError()); } @Test void findAllByLangProfWithoutLangProf() throws Exception { String page = "0"; mockMvc.perform(get("/courses/findAllByLangProf").param("page", page)) - .andExpect(status().is4xxClientError()); + .andExpect(status().is5xxServerError()); } @Test @@ -211,7 +210,7 @@ public class CourseControllerTest { student.setId(1L); CourseDto courseDtoWithStudent = new CourseDto("english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); - courseDtoWithStudent.setStudents(new ArrayList<>(List.of(student))); + courseDtoWithStudent.setStudents(new ArrayList<>(List.of(student.getId()))); Mockito.when(courseFacade.enrol(ArgumentMatchers.anyLong(), ArgumentMatchers.anyLong())).thenReturn(courseDtoWithStudent); @@ -229,7 +228,7 @@ public class CourseControllerTest { @Test void enrolCourseWithoutUserParameter() throws Exception { mockMvc.perform(patch("/courses/enrol/" + 0L)) - .andExpect(status().is4xxClientError()); + .andExpect(status().is5xxServerError()); } @Test @@ -247,14 +246,14 @@ public class CourseControllerTest { void expelCourse() throws Exception { Long id = 0L; Mockito.when(courseFacade.expel(ArgumentMatchers.eq(id), - ArgumentMatchers.isA(UserDto.class))) + ArgumentMatchers.isA(Long.class))) .thenReturn(courseDto); UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza", "Nováková", new AddressDto(), UserType.STUDENT); + student.setId(0L); - mockMvc.perform(patch("/courses/expel/" + id) - .content(asJsonString(student)) + mockMvc.perform(patch("/courses/expel/" + id + "?studentId=" + student.getId()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("english b2 course")) @@ -267,7 +266,7 @@ public class CourseControllerTest { @Test void expelCourseWithoutUserParameter() throws Exception { mockMvc.perform(patch("/courses/expel/" + 0L)) - .andExpect(status().is4xxClientError()); + .andExpect(status().is5xxServerError()); } @Test diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseFacadeTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseFacadeTest.java index e0653ae4ea65e4d524531dc187346e81a5b57ed0..c1567d6765b46c33443ab09e94dce5a7c1d46fd6 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseFacadeTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseFacadeTest.java @@ -5,11 +5,8 @@ import org.fuseri.model.dto.course.CourseCreateDto; import org.fuseri.model.dto.course.CourseDto; import org.fuseri.model.dto.course.LanguageTypeDto; import org.fuseri.model.dto.course.ProficiencyLevelDto; -import org.fuseri.model.dto.user.AddressDto; -import org.fuseri.model.dto.user.UserDto; -import org.fuseri.model.dto.user.UserType; import org.fuseri.modulelanguageschool.user.User; -import org.fuseri.modulelanguageschool.user.UserMapper; +import org.fuseri.modulelanguageschool.user.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -20,6 +17,7 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,10 +32,8 @@ import static org.mockito.Mockito.when; @AutoConfigureMockMvc final class CourseFacadeTest { - private final UserDto USER = new UserDto("novakovat", - "novakova@gamil.com", "Tereza", "Nováková", new AddressDto(), UserType.STUDENT); private final CourseDto courseDto = new CourseDto("AJ1", 10, - LanguageTypeDto.ENGLISH, ProficiencyLevelDto.A1); + LanguageTypeDto.ENGLISH, ProficiencyLevelDto.A1, new ArrayList<>(), false); private final CourseCreateDto courseCreateDto = new CourseCreateDto("AJ1", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.A1); private final Course course = new Course(); @@ -58,7 +54,7 @@ final class CourseFacadeTest { private CourseMapper courseMapper; @MockBean - private UserMapper userMapper; + private UserService userService; @Test void create() { @@ -142,15 +138,13 @@ final class CourseFacadeTest { void testExpel() { Long id = 0L; when(courseMapper.mapToDto(course)).thenReturn(courseDto); - when(userMapper.fromDto(USER)).thenReturn(user); + when(userService.find(0L)).thenReturn(user); when(courseService.expel(anyLong(), any(User.class))).thenReturn(course); - CourseDto actualDto = courseFacade.expel(id, USER); + CourseDto actualDto = courseFacade.expel(id, 0L); assertNotNull(actualDto); assertEquals(courseDto, actualDto); } - - } diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseMapperTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseMapperTest.java index 8e87376603461ab9eb8362f365d0cdcd73659862..6dcc56d1937c2b7177732b64d61f8f29222fba55 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseMapperTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseMapperTest.java @@ -4,6 +4,7 @@ import org.fuseri.model.dto.course.CourseCreateDto; import org.fuseri.model.dto.course.CourseDto; import org.fuseri.model.dto.course.LanguageTypeDto; import org.fuseri.model.dto.course.ProficiencyLevelDto; +import org.fuseri.modulelanguageschool.user.UserService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -13,98 +14,102 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @SpringBootTest final class CourseMapperTest { - private final CourseDto courseDto = new CourseDto("AJ1", 10, - LanguageTypeDto.ENGLISH, ProficiencyLevelDto.A1); - private final CourseCreateDto courseCreateDto = new CourseCreateDto("AJ1", 10, + private final CourseDto courseDto = new CourseDto("AJ1", 10, + LanguageTypeDto.ENGLISH, ProficiencyLevelDto.A1, new ArrayList<>(), false); + private final CourseCreateDto courseCreateDto = new CourseCreateDto("AJ1", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.A1); - private final Course course = new Course("AJ1", 10, - Language.ENGLISH, ProficiencyLevel.A1); - @Autowired - private CourseMapper CourseMapper; + private final Course course = new Course("AJ1", 10, + Language.ENGLISH, ProficiencyLevel.A1); - @BeforeEach - void setUp() - { - course.setId(1L); - courseDto.setId(1L); - } + @Autowired + private CourseMapper CourseMapper; - @Test - void mapNullToDto() { - var createdDto = CourseMapper.mapToDto(null); + @Autowired + private UserService userService; - Assertions.assertNull(createdDto); - } + @BeforeEach + void setUp() { + course.setId(1L); + courseDto.setId(1L); + } - @Test - void mapToDto() { - var createdDto = CourseMapper.mapToDto(course); + @Test + void mapNullToDto() { + var createdDto = CourseMapper.mapToDto(null); - Assertions.assertNotNull(createdDto); - } + Assertions.assertNull(createdDto); + } - @Test - void mapNullToCourseCourseDto() { - var createdCourse = CourseMapper.mapToCourse((CourseDto) null); + @Test + void mapToDto() { + var createdDto = CourseMapper.mapToDto(course); - Assertions.assertNull(createdCourse); - } + Assertions.assertNotNull(createdDto); + } - @Test - void mapToCourseCourseDto() { - var createdCourse = CourseMapper.mapToCourse(courseDto); + @Test + void mapNullToCourseCourseDto() { + var createdCourse = CourseMapper.mapToCourse((CourseDto) null, null); - Assertions.assertNotNull(createdCourse); - } + Assertions.assertNull(createdCourse); + } - @Test - void mapNullToList() { - var courseDtos = CourseMapper.mapToList(null); + @Test + void mapToCourseCourseDto() { + var createdCourse = CourseMapper.mapToCourse(courseDto, userService); - Assertions.assertNull(courseDtos); - } + Assertions.assertNotNull(createdCourse); + } - @Test - void mapToEmptyList() { - var courseDtos = CourseMapper.mapToList(Collections.emptyList()); + @Test + void mapNullToList() { + var courseDtos = CourseMapper.mapToList(null); - Assertions.assertEquals(courseDtos.size(), 0); - } + Assertions.assertNull(courseDtos); + } - @Test - void mapToList() { - var courseDtos = CourseMapper.mapToList(Collections.singletonList(course)); + @Test + void mapToEmptyList() { + var courseDtos = CourseMapper.mapToList(Collections.emptyList()); - Assertions.assertEquals(1, courseDtos.size()); - } + Assertions.assertEquals(courseDtos.size(), 0); + } - @Test - void mapToEmptyPageDto() { - Page<CourseDto> pageDto = CourseMapper.mapToPageDto(Page.empty()); + @Test + void mapToList() { + var courseDtos = CourseMapper.mapToList(Collections.singletonList(course)); - Assertions.assertEquals(1, pageDto.getTotalPages()); - } + Assertions.assertEquals(1, courseDtos.size()); + } - @Test - void mapToPageDto() { - List<Course> courses = List.of(course); - Page<Course> page = new PageImpl<>(courses, PageRequest.of(0, 1), courses.size()); - Page<CourseDto> pageDto = CourseMapper.mapToPageDto(page); + @Test + void mapToEmptyPageDto() { + Page<CourseDto> pageDto = CourseMapper.mapToPageDto(Page.empty()); - Assertions.assertEquals(page.getTotalPages(), pageDto.getTotalPages()); - Assertions.assertEquals(page.getNumber(), pageDto.getNumber()); - Assertions.assertEquals(page.getNumberOfElements(), pageDto.getNumberOfElements()); - Assertions.assertEquals(page.getSize(), pageDto.getSize()); - Assertions.assertEquals(page.getTotalElements(), pageDto.getTotalElements()); + Assertions.assertEquals(1, pageDto.getTotalPages()); + } + + @Test + void mapToPageDto() { + List<Course> courses = List.of(course); + Page<Course> page = new PageImpl<>(courses, PageRequest.of(0, 1), courses.size()); + Page<CourseDto> pageDto = CourseMapper.mapToPageDto(page); + + Assertions.assertEquals(page.getTotalPages(), pageDto.getTotalPages()); + Assertions.assertEquals(page.getNumber(), pageDto.getNumber()); + Assertions.assertEquals(page.getNumberOfElements(), pageDto.getNumberOfElements()); + Assertions.assertEquals(page.getSize(), pageDto.getSize()); + Assertions.assertEquals(page.getTotalElements(), pageDto.getTotalElements()); // Assertions.assertEquals(courseDto, pageDto.getContent().get(0)); - } + } @Test void mapNullToCourseCourseCreateDto() { @@ -119,5 +124,5 @@ final class CourseMapperTest { Assertions.assertEquals(course, createdCourse); } - + } diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseServiceTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseServiceTest.java index 5b952e05332fc5c8b49abb22479e4d131bc5a845..3d92ba1881bd3d3f1fec8087cd6e01fd04cb51bc 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseServiceTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseServiceTest.java @@ -21,22 +21,21 @@ import static org.mockito.Mockito.*; @SpringBootTest final class CourseServiceTest { - @MockBean - private CourseRepository courseRepository; - - @Autowired - private CourseService courseService; - - private final Course course = new Course("AJ1", 10, - Language.ENGLISH, ProficiencyLevel.A1, new ArrayList<>(), false); - + private final Course course = new Course("AJ1", 10, + Language.ENGLISH, ProficiencyLevel.A1); private final User user = new User("novakovat", UserType.STUDENT, - "novakova@gamil.com", "password", "Tereza", + "novakova@gamil.com", "password", "Tereza", "Nováková", new Address(), new HashSet<>(), new HashMap<>()); private final Course courseWithEnrolledStudent = new Course("AJ1", 10, Language.ENGLISH, ProficiencyLevel.A1, new ArrayList<>(List.of(user)), false); private final List<Course> courses = List.of(course, course); + @MockBean + private CourseRepository courseRepository; + + @Autowired + private CourseService courseService; + @Test void save() { when(courseRepository.save(course)).thenReturn(course); diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureControllerTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureControllerTest.java index a6177a7f2bf64e84a3f3e78926a0ad34fe79e8f9..b20ba4d8f7cbceaf23e3ba827c4c38ca50b2d7bf 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureControllerTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureControllerTest.java @@ -7,6 +7,7 @@ import org.fuseri.model.dto.lecture.LectureDto; import org.fuseri.model.dto.user.AddressDto; import org.fuseri.model.dto.user.UserDto; import org.fuseri.model.dto.user.UserType; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @@ -20,6 +21,7 @@ import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; @@ -31,23 +33,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. public class LectureControllerTest { private final LocalDateTime now = LocalDateTime.now(); - private final LectureCreateDto lectureCreateDto = new LectureCreateDto( - now.plusDays(2), - now.plusDays(2).plusHours(2), - "Learning how to spell deprecated", - 10, 0L); - private final LectureDto lectureDto = new LectureDto( - now.plusDays(2), - now.plusDays(2).plusHours(2), - "Learning how to spell deprecated", - 10, 0L, 0L, Collections.emptyList()); - @Autowired ObjectMapper objectMapper; - + private LectureCreateDto lectureCreateDto; + private LectureDto lectureDto; + private LectureDto lectureDtoWithStudent; + private UserDto student; + private UserDto lecturer; @Autowired private MockMvc mockMvc; - @MockBean private LectureFacade lectureFacade; @@ -61,6 +55,32 @@ public class LectureControllerTest { } } + @BeforeEach + void setup() { + student = new UserDto("novakovat", "novakovat@gamil.com", "Tereza", + "Nováková", new AddressDto(), UserType.STUDENT); + lecturer = new UserDto("novakoval", "novakoval@gamil.com", "Lucka", + "Nováková", new AddressDto(), UserType.LECTURER); + student.setId(1L); + lecturer.setId(2L); + + lectureCreateDto = new LectureCreateDto( + now.plusDays(2), + now.plusDays(2).plusHours(2), + "Learning how to spell deprecated", + 10, 0L); + lectureDto = new LectureDto( + now.plusDays(2), + now.plusDays(2).plusHours(2), + "Learning how to spell deprecated", + 10, lecturer.getId(), 0L, Collections.emptyList()); + lectureDtoWithStudent = new LectureDto( + now.plusDays(2), + now.plusDays(2).plusHours(2), + "Learning how to spell deprecated", + 10, lecturer.getId(), 0L, List.of(student.getId())); + } + @Test void createLecture() throws Exception { Mockito.when(lectureFacade.create(ArgumentMatchers.isA(LectureCreateDto.class))).thenReturn(lectureDto); @@ -72,7 +92,7 @@ public class LectureControllerTest { .andExpect(jsonPath("$.lectureTo").isNotEmpty()) .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated")) .andExpect(jsonPath("$.capacity").value("10")) - .andExpect(jsonPath("$.lecturerId").value("0")) + .andExpect(jsonPath("$.lecturerId").value("2")) .andExpect(jsonPath("$.courseId").value("0")); } @@ -102,7 +122,7 @@ public class LectureControllerTest { .andExpect(jsonPath("$.lectureTo").isNotEmpty()) .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated")) .andExpect(jsonPath("$.capacity").value("10")) - .andExpect(jsonPath("$.lecturerId").value("0")) + .andExpect(jsonPath("$.lecturerId").value("2")) .andExpect(jsonPath("$.courseId").value("0")); } @@ -126,7 +146,7 @@ public class LectureControllerTest { @Test void findLecturesByCourseWithoutParameter() throws Exception { mockMvc.perform(get("/lectures/findByCourse")) - .andExpect(status().is4xxClientError()); + .andExpect(status().is5xxServerError()); } @Test @@ -143,7 +163,7 @@ public class LectureControllerTest { .andExpect(jsonPath("$.lectureTo").isNotEmpty()) .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated")) .andExpect(jsonPath("$.capacity").value("10")) - .andExpect(jsonPath("$.lecturerId").value("0")) + .andExpect(jsonPath("$.lecturerId").value("2")) .andExpect(jsonPath("$.courseId").value("0")); } @@ -171,20 +191,14 @@ public class LectureControllerTest { void setLecturerForLecture() throws Exception { Long id = 0L; Mockito.when(lectureFacade.setLecturer(ArgumentMatchers.eq(id), - ArgumentMatchers.isA(UserDto.class))) + ArgumentMatchers.isA(Long.class))) .thenReturn(lectureDto); - UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza", - "Nováková", new AddressDto(), UserType.STUDENT); - mockMvc.perform(patch("/lectures/setLecturer/" + id) - .content(asJsonString(student)) + student.setId(0L); + mockMvc.perform(patch("/lectures/setLecturer/" + id + "?lecturerId=" + lecturer.getId()) + .content(asJsonString(student.getId())) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.lectureFrom").isNotEmpty()) - .andExpect(jsonPath("$.lectureTo").isNotEmpty()) - .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated")) - .andExpect(jsonPath("$.capacity").value("10")) - .andExpect(jsonPath("$.lecturerId").value("0")) - .andExpect(jsonPath("$.courseId").value("0")); + .andExpect(jsonPath("$.lecturerId").value("2")); } @Test @@ -197,20 +211,12 @@ public class LectureControllerTest { void enrolLecture() throws Exception { Long id = 0L; Mockito.when(lectureFacade.enrol(ArgumentMatchers.eq(id), - ArgumentMatchers.isA(UserDto.class))) - .thenReturn(lectureDto); - UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza", - "Nováková", new AddressDto(),UserType.STUDENT ); - mockMvc.perform(patch("/lectures/enrol/" + id) - .content(asJsonString(student)) + ArgumentMatchers.isA(Long.class))) + .thenReturn(lectureDtoWithStudent); + mockMvc.perform(patch("/lectures/enrol/" + id + "?studentId=" + student.getId()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.lectureFrom").isNotEmpty()) - .andExpect(jsonPath("$.lectureTo").isNotEmpty()) - .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated")) - .andExpect(jsonPath("$.capacity").value("10")) - .andExpect(jsonPath("$.lecturerId").value("0")) - .andExpect(jsonPath("$.courseId").value("0")); + .andExpect(jsonPath("$.students").isNotEmpty()); } @Test @@ -223,20 +229,12 @@ public class LectureControllerTest { void expelLecture() throws Exception { Long id = 0L; Mockito.when(lectureFacade.expel(ArgumentMatchers.eq(id), - ArgumentMatchers.isA(UserDto.class))) + ArgumentMatchers.isA(Long.class))) .thenReturn(lectureDto); - UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza", - "Nováková", new AddressDto(), UserType.STUDENT); - mockMvc.perform(patch("/lectures/expel/" + id) - .content(asJsonString(student)) + mockMvc.perform(patch("/lectures/expel/" + id + "?studentId=" + student.getId()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.lectureFrom").isNotEmpty()) - .andExpect(jsonPath("$.lectureTo").isNotEmpty()) - .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated")) - .andExpect(jsonPath("$.capacity").value("10")) - .andExpect(jsonPath("$.lecturerId").value("0")) - .andExpect(jsonPath("$.courseId").value("0")); + .andExpect(jsonPath("$.students").isEmpty()); } @Test diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureFacadeTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureFacadeTest.java index e970e966e1caf8b314bfd13f8580693eaddd6613..a74977d6576960cb79cf77ffe511d4be21bb0377 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureFacadeTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureFacadeTest.java @@ -13,6 +13,7 @@ import org.fuseri.modulelanguageschool.course.Language; import org.fuseri.modulelanguageschool.course.ProficiencyLevel; import org.fuseri.modulelanguageschool.user.User; import org.fuseri.modulelanguageschool.user.UserMapper; +import org.fuseri.modulelanguageschool.user.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -67,6 +68,9 @@ final class LectureFacadeTest { @MockBean private UserMapper userMapper; + @MockBean + private UserService userService; + @Autowired private CourseService courseService; @@ -149,10 +153,10 @@ final class LectureFacadeTest { void testEnrol() { Long id = 0L; when(lectureMapper.mapToDto(lecture)).thenReturn(lectureDto); - when(userMapper.fromDto(USER)).thenReturn(user); + when(userService.find(0L)).thenReturn(user); when(lectureService.enrol(anyLong(), any(User.class))).thenReturn(lecture); - LectureDto actualDto = lectureFacade.enrol(id, USER); + LectureDto actualDto = lectureFacade.enrol(id, 0L); assertNotNull(actualDto); assertEquals(lectureDto, actualDto); @@ -162,10 +166,10 @@ final class LectureFacadeTest { void testExpel() { Long id = 0L; when(lectureMapper.mapToDto(lecture)).thenReturn(lectureDto); - when(userMapper.fromDto(USER)).thenReturn(user); + when(userService.find(1L)).thenReturn(user); when(lectureService.expel(anyLong(), any(User.class))).thenReturn(lecture); - LectureDto actualDto = lectureFacade.expel(id, USER); + LectureDto actualDto = lectureFacade.expel(id, 1L); assertNotNull(actualDto); assertEquals(lectureDto, actualDto); @@ -175,10 +179,10 @@ final class LectureFacadeTest { void testSetLecturer() { Long id = 0L; when(lectureMapper.mapToDto(lecture)).thenReturn(lectureDto); - when(userMapper.fromDto(USER)).thenReturn(user); + when(userService.find(1L)).thenReturn(user); when(lectureService.setLecturer(anyLong(), any(User.class))).thenReturn(lecture); - LectureDto actualDto = lectureFacade.setLecturer(id, USER); + LectureDto actualDto = lectureFacade.setLecturer(id, 1L); assertNotNull(actualDto); assertEquals(lectureDto, actualDto);