-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/user crud unit tests #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
88b988f
b4a54a6
3f43a09
36fea87
aeab4c4
e6863ea
5d2d8ac
b05fced
c029e26
6e7090c
3cc439c
24cb12f
8c77613
7573b74
9b94cf3
fe62a14
73764bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package com.orderflow.ecommerce.controllers; | ||
|
|
||
| import com.orderflow.ecommerce.dtos.UserDto; | ||
| import com.orderflow.ecommerce.services.UserService; | ||
| import jakarta.validation.Valid; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.stereotype.Controller; | ||
| import org.springframework.web.bind.annotation.*; | ||
| import org.springframework.web.servlet.support.ServletUriComponentsBuilder; | ||
|
|
||
| import java.net.URI; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/users") | ||
| public class UserController { | ||
|
|
||
| @Autowired | ||
| private UserService service; | ||
|
|
||
| @GetMapping(value = "/{id}") | ||
| public ResponseEntity<UserDto> findById(@PathVariable Long id) { | ||
| return ResponseEntity.ok(service.findById(id)); | ||
| } | ||
|
|
||
| @GetMapping | ||
| public ResponseEntity<Page<UserDto>> findAll(Pageable pageable) { | ||
| return ResponseEntity.ok().body(service.findAllPaged(pageable)); | ||
| } | ||
|
|
||
| @GetMapping(params = "email") | ||
| public ResponseEntity<UserDto> findByEmail(@RequestParam String email) { | ||
| return ResponseEntity.ok().body(service.findByEmail(email)); | ||
| } | ||
|
|
||
| @Valid | ||
| @PostMapping | ||
| public ResponseEntity<UserDto> insert(@Valid @RequestBody UserDto dto) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| dto = service.insert(dto); | ||
| URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}") | ||
| .buildAndExpand(dto.id()).toUri(); | ||
| return ResponseEntity.created(uri).body(dto); | ||
| } | ||
|
|
||
| @DeleteMapping(value = "/{id}") | ||
| public ResponseEntity<Void> delete(@PathVariable Long id, @RequestParam(required = false) boolean verify) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| service.delete(id, verify); | ||
| return ResponseEntity.noContent().build(); | ||
| } | ||
|
|
||
| @PutMapping(value = "/{id}") | ||
| public ResponseEntity<UserDto> update(@PathVariable Long id, @Valid @RequestBody UserDto dto) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return ResponseEntity.ok().body(service.update(id, dto)); | ||
| } | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ControllerExceptionHandler removed, Handlers moved to GlobalExceptionHandler. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package com.orderflow.ecommerce.controllers.exceptions; | ||
|
|
||
| import com.orderflow.ecommerce.dtos.ErrorResponse; | ||
| import com.orderflow.ecommerce.exceptions.DuplicateResourceValidationException; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import org.springframework.dao.DataIntegrityViolationException; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.validation.FieldError; | ||
| import org.springframework.web.bind.MethodArgumentNotValidException; | ||
| import org.springframework.web.bind.annotation.ControllerAdvice; | ||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||
|
|
||
| import java.time.Instant; | ||
|
|
||
| @ControllerAdvice | ||
| public class ControllerExceptionHandler { | ||
|
|
||
| @ExceptionHandler(MethodArgumentNotValidException.class) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refactored MethodArgumentNotValidException handler in GlobalExceptionHandler to replace this. |
||
| public ResponseEntity<ValidationError> validation(MethodArgumentNotValidException ex, HttpServletRequest request) { | ||
| HttpStatus status = HttpStatus.UNPROCESSABLE_ENTITY; | ||
| ValidationError err = new ValidationError(); | ||
| err.setTimestamp(Instant.now()); | ||
| err.setStatus(status.value()); | ||
| err.setError("Validation exception"); | ||
| err.setMessage(ex.getMessage()); | ||
| err.setPath(request.getRequestURI()); | ||
|
|
||
| for (FieldError f : ex.getBindingResult().getFieldErrors()) { | ||
| err.addError(f.getField(), f.getDefaultMessage()); | ||
| } | ||
|
|
||
| return ResponseEntity.status(status).body(err); | ||
| } | ||
| @ExceptionHandler(DataIntegrityViolationException.class) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| public ResponseEntity<ErrorResponse> handleIntegrityViolation(DataIntegrityViolationException ex, HttpServletRequest request) { | ||
| HttpStatus status = HttpStatus.BAD_REQUEST; | ||
| ErrorResponse err = new ErrorResponse(Instant.now(), status.value(), ex.getMessage(), request.getRequestURI()); | ||
| return ResponseEntity.status(status).body(err); | ||
| } | ||
|
|
||
| @ExceptionHandler(DuplicateResourceValidationException.class) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| public ResponseEntity<ValidationError> handleDuplicateResource(DuplicateResourceValidationException ex, HttpServletRequest request) { | ||
| HttpStatus status = HttpStatus.UNPROCESSABLE_ENTITY; | ||
| ValidationError err = new ValidationError(); | ||
| err.setTimestamp(Instant.now()); | ||
| err.setStatus(status.value()); | ||
| err.setError("Validation exception"); | ||
| err.setMessage(ex.getMessage()); | ||
| err.setPath(request.getRequestURI()); | ||
|
|
||
| ex.getErrors().forEach(f -> { | ||
| err.addError(f.getFieldName(), f.getMessage()); | ||
| }); | ||
|
|
||
| return ResponseEntity.status(status).body(err); | ||
| } | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FieldMessage moved to api/src/main/java/com/orderflow/ecommerce/dtos/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.orderflow.ecommerce.controllers.exceptions; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.Setter; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class FieldMessage { | ||
| private String fieldName; | ||
| private String message; | ||
| } |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed after refator ValidationError. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.orderflow.ecommerce.controllers.exceptions; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.Setter; | ||
|
|
||
| import java.time.Instant; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| @NoArgsConstructor | ||
| public class StandardError { | ||
| private Instant timestamp; | ||
| private Integer status; | ||
| private String error; | ||
| private String message; | ||
| private String path; | ||
|
|
||
| } |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to DTO. ErrorResponse can not be extended once it is a Record, but ValidationError refactored to use ErrorResponse instead of extend StandardError (removed). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.orderflow.ecommerce.controllers.exceptions; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| @Getter | ||
| public class ValidationError extends StandardError{ | ||
| private final List<FieldMessage> errors = new ArrayList<>(); | ||
|
|
||
| public void addError(String fieldName, String message) { | ||
| errors.add(new FieldMessage(fieldName, message)); | ||
| } | ||
| } |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package com.orderflow.ecommerce.dtos; | ||
|
|
||
| import com.orderflow.ecommerce.entities.User; | ||
| import jakarta.validation.constraints.Email; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.Pattern; | ||
| import jakarta.validation.constraints.Size; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| public record UserDto( | ||
| Long id, | ||
| @NotBlank(message = "Campo requerido") | ||
| String name, | ||
| @NotBlank(message = "Campo requerido") | ||
| @Email(message = "Email inválido") | ||
| String email, | ||
| @NotBlank(message = "Campo requerido") | ||
| @Pattern( | ||
| regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&.]).{8,}$", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Coundn't find much referenced for whats should we consider as special caracteres (as we don't have this specified for the project), but the one or two articles I found consider |
||
| message = "A senha deve conter pelo menos 8 caracteres, incluindo letras maiúsculas, minúsculas, números e caracteres especiais" | ||
| ) | ||
| String password, | ||
| String taxId, | ||
| String stateRegistration, | ||
| String phone, | ||
| LocalDate birthDate, | ||
| Boolean taxpayer, | ||
| String googleId, | ||
| @Size(max = 40, message = "Máximo 40 caracteres") | ||
| String street, | ||
| @Size(max = 40, message = "Máximo 40 caracteres") | ||
| String complement, | ||
| @Size(max = 10, message = "Máximo 10 caracteres") | ||
| String number, | ||
| @Size(max = 40, message = "Máximo 40 caracteres") | ||
| String neighborhood, | ||
| @Size(max = 40, message = "Máximo 40 caracteres") | ||
| String city, | ||
| @Size(max = 40, message = "Máximo 40 caracteres") | ||
| String country, | ||
| @Size(max = 2, message = "Máximo 2 caracteres") | ||
| String state, | ||
| @Size(max = 10, message = "Máximo 10 caracteres") | ||
| String zipCode | ||
| ) { | ||
| public UserDto(User entity) { | ||
| this(entity.getId(), entity.getName(), entity.getEmail(), entity.getPassword(), entity.getTaxId(), entity.getStateRegistration(), entity.getPhone(), entity.getBirthDate(), entity.getTaxpayer(), entity.getGoogleId(), entity.getStreet(), entity.getComplement(), entity.getNumber(), entity.getNeighborhood(), entity.getCity(), entity.getCountry(), entity.getState(), entity.getZipCode()); | ||
| } | ||
| } | ||
|
|
||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package com.orderflow.ecommerce.entities; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import lombok.*; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| @Entity | ||
| @Table(name = "tb_user") | ||
| @Getter | ||
| @Setter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| @EqualsAndHashCode(of = "id") | ||
| public class User { | ||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @Column(nullable = false) | ||
| private String name; | ||
| @Column(nullable = false, unique = true) | ||
| private String email; | ||
| private String password; | ||
|
|
||
|
|
||
| // Customer information for Invoices | ||
| /** | ||
| * CPF or CNPJ | ||
| */ | ||
| @Column(name = "tax_id", nullable = false, unique = true, length = 20) | ||
| private String taxId; | ||
|
|
||
| /** | ||
| * State registration (IE) | ||
| */ | ||
| @Column(length = 30) | ||
| private String stateRegistration; | ||
|
|
||
| private String phone; | ||
| private LocalDate birthDate; | ||
|
|
||
| /** | ||
| * Used in Invoices (NF-e) | ||
| */ | ||
| private Boolean taxpayer; | ||
|
|
||
| /** | ||
| * Google API | ||
| */ | ||
| @Column(unique = true) | ||
| private String googleId; | ||
|
|
||
| /** | ||
| * private Address address; | ||
| */ | ||
| @Column(length = 40) | ||
| private String street; | ||
| @Column(length = 40) | ||
| private String complement; | ||
| @Column(length = 10) | ||
| private String number; | ||
| @Column(length = 40) | ||
| private String neighborhood; | ||
| @Column(length = 40) | ||
| private String city; | ||
| @Column(length = 40) | ||
| private String country; | ||
| @Column(length = 2) | ||
| private String state; | ||
| @Column(length = 10) | ||
| private String zipCode; | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.orderflow.ecommerce.exceptions; | ||
|
|
||
| import com.orderflow.ecommerce.controllers.exceptions.FieldMessage; | ||
| import lombok.Getter; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| @Getter | ||
| public class DuplicateResourceValidationException extends RuntimeException { | ||
|
|
||
| private final List<FieldMessage> errors = new ArrayList<>(); | ||
|
|
||
| public DuplicateResourceValidationException(List<FieldMessage> errors, String message) { | ||
| super(message); | ||
| this.errors.addAll(errors); | ||
| } | ||
|
|
||
| public void addError(String fieldName, String message) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| errors.add(new FieldMessage(fieldName, message)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.orderflow.ecommerce.repositories; | ||
|
|
||
| import com.orderflow.ecommerce.entities.User; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface UserRepository extends JpaRepository<User, Long> { | ||
|
|
||
| Optional<User> findByEmailIgnoreCase(String email); | ||
|
|
||
| boolean existsByEmail(String email); | ||
| boolean existsByEmailAndIdNot(String email, Long id); | ||
|
|
||
| boolean existsByTaxId(String taxId); | ||
| boolean existsByTaxIdAndIdNot(String taxId, Long id); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Validwithin the method parameters.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.