Skip to content
Snippets Groups Projects
Commit 04e9d5b9 authored by Ester Vilímková's avatar Ester Vilímková
Browse files

Merge branch 'M2-fix-certificate' into 'main'

M2 fix certificate

See merge request !36
parents b080444b 6ff0ff6d
No related branches found
No related tags found
1 merge request!36M2 fix certificate
Pipeline #
Showing
with 297 additions and 69 deletions
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;
}
......
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;
}
}
......@@ -4,10 +4,8 @@ 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;
......@@ -36,6 +34,7 @@ import java.util.Map;
@Getter
@Setter
@EqualsAndHashCode(callSuper = false)
@NoArgsConstructor
public class UserDto extends DomainObjectDto {
@NotBlank
......@@ -68,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;
}
}
package org.fuseri.modulecertificate;
package org.fuseri.modulecertificate.certificate;
import jakarta.persistence.*;
......
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) {
......
package org.fuseri.modulecertificate.service;
package org.fuseri.modulecertificate.certificate;
import org.fuseri.model.dto.certificate.CertificateCreateDto;
......
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;
......
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;
......
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;
......
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();
}
}
package org.fuseri.modulecertificate.exceptions;
public interface ApiSubError {
}
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;
}
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());
}
}
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);
}
}
......@@ -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
......
......@@ -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(),
......
......@@ -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";
......
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;
......
......@@ -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;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment