Skip to content
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

Add exception handling #20

Merged
merged 3 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/main/java/com/ypm/dto/request/MergePlayListsRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@

public record MergePlayListsRequest(String mergedPlayListTitle,
List<String> playListsIds,
boolean deleteAfterMerge) {
}
boolean deleteAfterMerge) { }
6 changes: 6 additions & 0 deletions src/main/java/com/ypm/dto/response/ExceptionResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.ypm.dto.response;

import java.time.Instant;

public record ExceptionResponse(int code, String message, Instant date) {
}
56 changes: 56 additions & 0 deletions src/main/java/com/ypm/error/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.ypm.error;

import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.ypm.dto.response.ExceptionResponse;
import com.ypm.exception.PlayListNotFoundException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.io.IOException;
import java.time.Instant;

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

public GlobalExceptionHandler() {
super();
}

@ExceptionHandler({GoogleJsonResponseException.class})
public ResponseEntity<?> handleBadRequest(final GoogleJsonResponseException ex,
final WebRequest request) {

ExceptionResponse exceptionResponse = new ExceptionResponse(
ex.getStatusCode(), ex.getDetails().getMessage(), Instant.now());

return handleExceptionInternal(ex, exceptionResponse,
new HttpHeaders(), HttpStatus.valueOf(ex.getStatusCode()), request);
}

@ExceptionHandler({PlayListNotFoundException.class})
public ResponseEntity<?> handleBadRequest(final PlayListNotFoundException ex,
final WebRequest request) {

ExceptionResponse exceptionResponse = new ExceptionResponse(
HttpStatus.NOT_FOUND.value(), ex.getMessage(), Instant.now());

return handleExceptionInternal(ex, exceptionResponse,
new HttpHeaders(), HttpStatus.NOT_FOUND, request);
}

@ExceptionHandler({IOException.class})
public ResponseEntity<?> handleInternal(final IOException ex,
final WebRequest request) {

ExceptionResponse exceptionResponse = new ExceptionResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage(), Instant.now());

return handleExceptionInternal(ex, exceptionResponse,
new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.ypm.exception;

public class PlayListNotFoundException extends RuntimeException {

public PlayListNotFoundException(String identifier, String message) {
super(String.format("Playlist with the '%s' identifier was not found. %s", identifier, message));
}
}
5 changes: 4 additions & 1 deletion src/main/java/com/ypm/service/PlayListService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.google.api.services.youtube.model.Playlist;
import com.google.api.services.youtube.model.PlaylistItem;
import com.google.api.services.youtube.model.PlaylistSnippet;
import com.ypm.exception.PlayListNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -85,7 +86,9 @@ public Playlist getPlayListById(String accessToken, String playListId) throws IO
.getItems()
.stream()
.findFirst()
.orElseThrow();
.orElseThrow(() ->
new PlayListNotFoundException(playListId,
"At some stage we were not able to find one of the request-related playlists."));
}

public void deletePlayList(String accessToken, String playListId) throws IOException {
Expand Down
63 changes: 61 additions & 2 deletions src/test/java/com/ypm/controller/PlayListControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.google.api.services.youtube.model.PlaylistItemSnippet;
import com.google.api.services.youtube.model.PlaylistSnippet;
import com.ypm.dto.request.MergePlayListsRequest;
import com.ypm.dto.response.ExceptionResponse;
import com.ypm.exception.PlayListNotFoundException;
import com.ypm.service.PlayListService;
import com.ypm.service.VideoService;
import org.junit.jupiter.api.Test;
Expand All @@ -17,15 +19,19 @@
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.test.web.servlet.MockMvc;

import java.io.IOException;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
Expand Down Expand Up @@ -193,4 +199,57 @@ void givenCorrectRequest_whenDeletePlayList_thenResponseIsOk() throws Exception
verify(playListService, times(1))
.deletePlayList(any(), any());
}

@Test
void givenPlayListServiceThrowsPlayListNotFoundException_whenUpdatePlayListTitle_thenThrowsException()
throws Exception {

var login = oauth2Login()
.clientRegistration(this.clientRegistrationRepository.findByRegistrationId("google"));
String expectedErrorMessage =
"Playlist with the 'someId' identifier was not found. Playlist not found";

Playlist playlist = new Playlist();
playlist.setSnippet(new PlaylistSnippet().setTitle("New Title"));

when(playListService.updatePlayListTitle(any(), any(), any()))
.thenThrow(new PlayListNotFoundException("someId", "Playlist not found"));

mockMvc.perform(put("/playlists/{playlistId}", "someId")
.with(login)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(playlist)))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.code").value(404))
.andExpect(jsonPath("$.message").value(expectedErrorMessage))
.andExpect(jsonPath("$.date").isString())
.andExpect(result ->
assertInstanceOf(PlayListNotFoundException.class, result.getResolvedException()))
.andExpect(result -> assertEquals(expectedErrorMessage,
Objects.requireNonNull(result.getResolvedException()).getMessage()));

verify(playListService, times(1))
.updatePlayListTitle(any(), any(), any());
}

@Test
void givenPlayListServiceThrowsIOException_whenGetPlayLists_thenThrowsIOException()
throws Exception {

var login = oauth2Login()
.clientRegistration(this.clientRegistrationRepository.findByRegistrationId("google"));

ExceptionResponse response = new ExceptionResponse(500, "IO error occurred",
Instant.now());

when(playListService.getPlayLists(any())).thenThrow(new IOException("IO error occurred"));

mockMvc.perform(get("/playlists").with(login))
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.code").value(response.code()))
.andExpect(jsonPath("$.message").value(response.message()))
.andExpect(jsonPath("$.date").isString());

verify(playListService, times(1)).getPlayLists(any());
}
}
33 changes: 32 additions & 1 deletion src/test/java/com/ypm/controller/VideoControllerTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.ypm.controller;

import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpResponseException;
import com.ypm.service.VideoService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -10,9 +14,10 @@
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
Expand All @@ -38,4 +43,30 @@ void givenCorrectRequest_whenDeleteVideos_thenNoContent() throws Exception {
mockMvc.perform(delete("/videos/{videoId}", "someId").with(login))
.andExpect(status().isNoContent());
}

@Test
void givenVideoServiceThrowsGoogleJsonResponseException_whenDeleteVideos_thenThrowsException()
throws Exception {

var login = oauth2Login()
.clientRegistration(this.clientRegistrationRepository.findByRegistrationId("google"));

GoogleJsonError error = new GoogleJsonError();
error.setCode(400);
error.setMessage("Something went wrong!");
GoogleJsonResponseException googleJsonResponseException = new GoogleJsonResponseException(
new HttpResponseException.Builder(400, "Something went wrong!",
new HttpHeaders()), error);

doThrow(googleJsonResponseException).when(videosService).deleteVideo(any(), any());

mockMvc.perform(delete("/videos/{videoId}", "someId").with(login))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code").value(400))
.andExpect(jsonPath("$.message").value("Something went wrong!"))
.andExpect(jsonPath("$.date").isString());

verify(videosService, times(1)).deleteVideo(any(), any());
}

}