Skip to content

Commit

Permalink
Feature/create test for authentication controller (#93)
Browse files Browse the repository at this point in the history
- Added one more test to CategoryControllerTest complete all cases
* Created news test and improved Exceptions
  • Loading branch information
LauroSilveira authored Dec 30, 2023
1 parent 1dac226 commit f27aa66
Show file tree
Hide file tree
Showing 38 changed files with 1,004 additions and 447 deletions.
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,13 @@
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<configuration>
<excludes>**/domain/**</excludes>
<excludes>
<exclude>**/springdoc/*</exclude>
<exclude>**/domain/**</exclude>
<exclude>**/infraestructure/security/SecurityConfigurations.class</exclude>
<exclude>**/AluraFlixApiApplication.class</exclude>
<exclude>**/infraestructure/security/SecurityFilter.class</exclude>
</excludes>
</configuration>
<executions>
<execution>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -67,10 +68,11 @@ public ResponseEntity<List<VideoDto>> getVideosByCategory(
}

@DeleteMapping(value = "/{id}", produces = "application/json")
@PreAuthorize("hasRole('Admin')")
public ResponseEntity<HttpStatus> deleteCategory(@NotBlank @PathVariable final String id) {
log.info("{} Request to Delete Category with Id: {}", PREFIX_LOGGING, id);
final var response = this.categoryService.deleteCategory(id);
return ResponseEntity.status(response).build();
this.categoryService.deleteCategory(id);
return ResponseEntity.ok().build();
}

}
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
package com.alura.aluraflixapi.controller;

import com.alura.aluraflixapi.controller.dto.ErrorDto;
import java.util.List;
import com.alura.aluraflixapi.controller.dto.ErrorVO;
import com.alura.aluraflixapi.infraestructure.exception.ErrorMessageVO;
import com.alura.aluraflixapi.infraestructure.exception.ResourceNotFoundException;
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.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;

@RestControllerAdvice
public class ControllerAdvice {

/**
* Handle Invalid fields
* @return List of ErrorDto with invalid fields
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<List<ErrorDto>> handleInvalidFields(
final MethodArgumentNotValidException ex) {
var errors = ex.getFieldErrors();
return ResponseEntity.badRequest().body(errors.stream().map(ErrorDto::new).toList());
}
/**
* Handle Invalid fields
*
* @return List of ErrorDto with invalid fields
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<List<ErrorVO>> handleInvalidFields(
final MethodArgumentNotValidException ex) {
var errors = ex.getFieldErrors();
return ResponseEntity.badRequest().body(errors.stream().map(ErrorVO::new).toList());
}

/**
* handle invalid credentials whe user atempt to login
* @param ex HttpMessageNotReadableException
* @return ResponseEntity status bad_request
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<String> handleLoginException(
final HttpMessageNotReadableException ex) {
return ResponseEntity.badRequest().body("Invalid Credentials");
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorMessageVO> handlerResourceNotFoundException(final ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorMessageVO(ex.getMessage(), HttpStatus.NOT_FOUND));
}

}
27 changes: 13 additions & 14 deletions src/main/java/com/alura/aluraflixapi/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,36 @@
import com.alura.aluraflixapi.domain.user.dto.UserDto;
import com.alura.aluraflixapi.infraestructure.service.UserService;
import jakarta.validation.Valid;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

private static final String PREFIX_LOGGIN = "[UserController]";
private static final String PREFIX_LOGGING = "[UserController]";
private final UserService service;

@Autowired
private UserService service;
public UserController(UserService service) {
this.service = service;
}

@PostMapping
@Secured("ROLE_ADMIN")
@PreAuthorize("hasRole('Admin')")
public ResponseEntity<UserDto> saveUser(@RequestBody @Valid UserDto userDto) {
log.info("{} Saving new User: {}", PREFIX_LOGGIN, userDto.toString());
log.info("{} Saving new User: {}", PREFIX_LOGGING, userDto.toString());
var newUser = this.service.saveUser(userDto);
return ResponseEntity.ok().body(newUser);
}

@GetMapping
@PreAuthorize("hasRole('Admin')")
public ResponseEntity<List<UserDto>> getUsers() {
log.info("{} Retrieving Users", PREFIX_LOGGIN);
log.info("{} Retrieving Users", PREFIX_LOGGING);
var users = this.service.getUsers();
return ResponseEntity.ok(users);
}
Expand Down
138 changes: 69 additions & 69 deletions src/main/java/com/alura/aluraflixapi/controller/VideoController.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;

import java.util.List;
import java.util.Optional;

import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -34,80 +39,75 @@
@SecurityRequirement(name = "bearer-key")
public class VideoController {

private static final String LOGGING_PREFIX = "[VideoController]";
private static final String LOGGING_PREFIX = "[VideoController]";

private final VideoService service;

@Autowired
public VideoController(final VideoService service) {
this.service = service;
}

@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<VideoDto>> getVideos(@ParameterObject Pageable pageable) {
log.info("{} Request to get All videos", LOGGING_PREFIX);
final Page<VideoDto> videos = this.service.getVideos(pageable);
log.info("{} Response {}: ", LOGGING_PREFIX, videos.getContent());
return ResponseEntity.ok().body(new PageImpl<>(videos.getContent(), pageable, pageable.getPageSize()));
}


private final VideoService service;
@GetMapping("/{id}")
public ResponseEntity<VideoDto> getById(@NotBlank @PathVariable final String id) {
log.info("{} Request to get a video by ID: {}", LOGGING_PREFIX, id);
return Optional.ofNullable(service.getById(id))
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());

@Autowired
public VideoController(final VideoService service) {
this.service = service;
}
}

@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<VideoDto>> getVideos(Pageable pageable) {
log.info("{} Request to get All videos", LOGGING_PREFIX);
final Page<VideoDto> videos = this.service.getVideos(pageable);
if (videos.hasContent()) {
log.info("{} Response {}: ", LOGGING_PREFIX, videos);
return ResponseEntity.ok().body(videos);
} else {
return ResponseEntity.noContent().build();
@PostMapping
public ResponseEntity<VideoDto> save(@Valid @RequestBody final VideoDto dto,
final UriComponentsBuilder uriBuilder) {
log.info("{} Request to Save a new video: {}", LOGGING_PREFIX, dto);
final VideoDto videoDto = this.service.save(dto);
//good practices to return the Location in the Header to be searched by Id
//return Http code 201 and Location with Id
return ResponseEntity.created(uriBuilder.path("/videos/{id}").buildAndExpand(videoDto.id())
.toUri()).body(videoDto);
}
}


@GetMapping("/{id}")
public ResponseEntity<VideoDto> getById(@NotBlank @PathVariable final String id) {
log.info("{} Request to get a video by ID: {}", LOGGING_PREFIX, id);
return Optional.ofNullable(service.getById(id))
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());

}

@PostMapping
public ResponseEntity<VideoDto> save(@Valid @RequestBody final VideoDto dto,
final UriComponentsBuilder uriBuilder) {
log.info("{} Request to Save a new video: {}", LOGGING_PREFIX, dto);
final VideoDto videoDto = this.service.save(dto);
//good practices to return the Location in the Header to be search by Id
//return Http code 201 and Localtion with Id
return ResponseEntity.created(uriBuilder.path("/videos/{id}").buildAndExpand(videoDto.id())
.toUri()).body(videoDto);
}

@PutMapping
public ResponseEntity<UpdateVideoDto> update(@Valid @RequestBody final UpdateVideoDto dto,
final UriComponentsBuilder uriBuilder) {
log.info("{} Request to update a video: {}", LOGGING_PREFIX, dto);
final var videoDto = this.service.updateMovie(dto);
//good practices to return the Location in the Header to be search by Id
//return Http code 201 and Location with Id
return ResponseEntity.created(uriBuilder.path("/videos/{id}")
.buildAndExpand(videoDto.id())
.toUri()).body(videoDto);
}

@DeleteMapping("/{id}")
@Secured("ROLE_ADMIN")
public ResponseEntity<VideoDto> delete(@NotBlank @PathVariable final String id) {
log.info("{} Request to Delete a video by ID: {}", LOGGING_PREFIX, id);
final Optional<VideoDto> dto = this.service.delete(id);
return dto.map(videoDto -> ResponseEntity.status(HttpStatus.NO_CONTENT).body(videoDto))
.orElseGet(() -> ResponseEntity.noContent().build());
}

@GetMapping("/title")
public ResponseEntity<List<VideoDto>> getVideosByTitle(
@NotBlank @RequestParam("title") final String title) {
log.info("{} Request to get a video by title: {}", LOGGING_PREFIX, title);
final var videosByTitle = this.service.getVideosByTitle(title);
if (videosByTitle.isEmpty()) {
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.status(HttpStatus.FOUND).body(videosByTitle);

@PutMapping
public ResponseEntity<UpdateVideoDto> update(@Valid @RequestBody final UpdateVideoDto dto,
final UriComponentsBuilder uriBuilder) {
log.info("{} Request to update a video: {}", LOGGING_PREFIX, dto);
final var videoDto = this.service.updateMovie(dto);
//good practices to return the Location in the Header to be search by Id
//return Http code 201 and Location with Id
return ResponseEntity.created(uriBuilder.path("/videos/{id}")
.buildAndExpand(videoDto.id())
.toUri()).body(videoDto);
}

}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('Admin')")
public ResponseEntity<VideoDto> delete(@NotBlank @PathVariable final String id) {
log.info("{} Request to Delete a video by ID: {}", LOGGING_PREFIX, id);
final var dto = this.service.delete(id);
return ResponseEntity.ok(dto);
}

@GetMapping("/title")
public ResponseEntity<List<VideoDto>> getVideosByTitle(
@NotBlank @RequestParam("title") final String title) {
log.info("{} Request to get a video by title: {}", LOGGING_PREFIX, title);
final var videosByTitle = this.service.getVideosByTitle(title);
if (videosByTitle.isEmpty()) {
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.status(HttpStatus.FOUND).body(videosByTitle);
}

}

}
11 changes: 0 additions & 11 deletions src/main/java/com/alura/aluraflixapi/controller/dto/ErrorDto.java

This file was deleted.

10 changes: 10 additions & 0 deletions src/main/java/com/alura/aluraflixapi/controller/dto/ErrorVO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.alura.aluraflixapi.controller.dto;

import org.springframework.validation.FieldError;

public record ErrorVO(String field, String message) {

public ErrorVO(FieldError fieldError) {
this(fieldError.getField(), fieldError.getDefaultMessage());
}
}
4 changes: 3 additions & 1 deletion src/main/java/com/alura/aluraflixapi/domain/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
Expand Down Expand Up @@ -39,7 +41,7 @@ public class User implements Serializable, UserDetails {
private String password;

@DBRef
private List<Roles> roles;
private Set<Roles> roles;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

import com.alura.aluraflixapi.domain.user.roles.Roles;
import jakarta.validation.constraints.NotBlank;
import java.util.List;

import java.util.Set;

public record UserDto(
String id,
@NotBlank
String username,
@NotBlank
String password,
List<Roles> roles) {
Set<Roles> roles) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
import com.alura.aluraflixapi.domain.category.dto.CategoryDto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import org.hibernate.validator.constraints.URL;

@Builder
public record VideoDto(
String id,
@NotBlank
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.alura.aluraflixapi.infraestructure.exception;

public class CategoryServiceException extends RuntimeException {
public CategoryServiceException(String message) {
super(message);
}

public CategoryServiceException(String message, Throwable cause) {
super(message, cause);
}
}
Loading

0 comments on commit f27aa66

Please sign in to comment.