Skip to content

Commit

Permalink
Merge pull request #8 from neuefische/4-add-post-restaurant
Browse files Browse the repository at this point in the history
4 add post restaurant
  • Loading branch information
josch87 authored Jun 4, 2024
2 parents a4955a1 + a645b22 commit 9898c60
Show file tree
Hide file tree
Showing 26 changed files with 530 additions and 35 deletions.
6 changes: 4 additions & 2 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring3x</artifactId>
<version>4.13.0</version>
<scope>test</scope>
</dependency>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.neuefische.team2.backend.exceptions;

import com.neuefische.team2.backend.exceptions.domain.ExceptionResponse;
import com.neuefische.team2.backend.exceptions.domain.ExceptionResponseMessage;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;


@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ExceptionResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {

List<ExceptionResponseMessage> errors = exception.getFieldErrors()
.stream()
.map(error -> new ExceptionResponseMessage(error.getField(), error.getDefaultMessage()))
.toList();

return new ExceptionResponse(errors);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.neuefische.team2.backend.exceptions.domain;

import java.util.List;

public record ExceptionResponse(
List<ExceptionResponseMessage> errors
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.neuefische.team2.backend.exceptions.domain;

public record ExceptionResponseMessage(
String field,
String message
) {
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.neuefische.team2.backend.restaurant;

import com.neuefische.team2.backend.restaurant.domain.NewRestaurantDTO;
import com.neuefische.team2.backend.restaurant.domain.Restaurant;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;

Expand All @@ -18,7 +19,14 @@ public RestaurantController(RestaurantService restaurantService) {
}

@GetMapping
List<Restaurant> getRestaurants() {
public List<Restaurant> getRestaurants() {
return restaurantService.getRestaurants();
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Restaurant addRestaurant(@RequestBody @Valid NewRestaurantDTO newRestaurantDTO) {
Restaurant restaurant = new Restaurant(null, newRestaurantDTO.title(), newRestaurantDTO.city());
return restaurantService.addRestaurant(restaurant);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ public RestaurantService(RestaurantRepository restaurantRepository) {
public List<Restaurant> getRestaurants() {
return restaurantRepository.findAll();
}

public Restaurant addRestaurant(Restaurant restaurant) {
return restaurantRepository.save(restaurant);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.neuefische.team2.backend.restaurant.domain;

import jakarta.validation.constraints.NotBlank;

public record NewRestaurantDTO(
@NotBlank(message="Title must not be empty")
String title,
@NotBlank(message="City must not be empty")
String city
) {
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.neuefische.team2.backend.restaurant;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.neuefische.team2.backend.restaurant.domain.Restaurant;
import org.junit.jupiter.api.Test;
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.http.MediaType;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

Expand All @@ -23,5 +28,157 @@ void getAllProducts_whenNoProductInDB_thenReturnEmptyList() throws Exception {
.andExpect(MockMvcResultMatchers.content().json("[]"));
}

// TODO: Write a test to receive one restaurant as soon as POST endpoint is implemented.
@DirtiesContext
@Test
void getAllProducts_whenOneProductInDB_thenReturnListOfOne() throws Exception {
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/api/restaurants")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"title": "The Mockingbird",
"city": "New York"
}
"""))
.andReturn();

ObjectMapper mapper = new ObjectMapper();
Restaurant restaurant = mapper.readValue(result.getResponse().getContentAsString(), Restaurant.class);

mockMvc.perform(MockMvcRequestBuilders.get("/api/restaurants"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json("""
[
{
"title": "The Mockingbird",
"city": "New York"
}
]
"""))
.andExpect(MockMvcResultMatchers.jsonPath("$.[0].id").value(restaurant.id()));
}

@DirtiesContext
@Test
void addRestaurant_whenNewRestaurantDTO_thenReturnSavedRestaurantWithId() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/api/restaurants")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"title": "The Mockingbird",
"city": "New York"
}
"""))
.andExpect(MockMvcResultMatchers.status().isCreated())
.andExpect(MockMvcResultMatchers.content().json("""
{
"title": "The Mockingbird",
"city": "New York"
}
"""))
.andExpect(MockMvcResultMatchers.jsonPath("$.id").exists());
}

@DirtiesContext
@Test
void addRestaurant_whenTitleEmptyString_thenReturnException() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/api/restaurants")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"title": "",
"city": "New York"
}
"""))
.andExpect(MockMvcResultMatchers.status().isBadRequest())
.andExpect(MockMvcResultMatchers.content().json("""
{
"errors": [
{
"field": "title",
"message": "Title must not be empty"
}
]
}
"""));
}

@DirtiesContext
@Test
void addRestaurant_whenCityEmptyString_thenReturnException() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/api/restaurants")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"title": "The Mockingbird",
"city": ""
}
"""))
.andExpect(MockMvcResultMatchers.status().isBadRequest())
.andExpect(MockMvcResultMatchers.content().json("""
{
"errors": [
{
"field": "city",
"message": "City must not be empty"
}
]
}
"""));
}

@DirtiesContext
@Test
void addRestaurant_whenTitleAndCityEmptyString_thenReturnException() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/api/restaurants")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"title": "",
"city": ""
}
"""))
.andExpect(MockMvcResultMatchers.status().isBadRequest())
.andExpect(MockMvcResultMatchers.content().json("""
{
"errors": [
{
"field": "city",
"message": "City must not be empty"
},
{
"field": "title",
"message": "Title must not be empty"
}
]
}
"""));
}

@DirtiesContext
@Test
void addRestaurant_whenTitleAndCityContainOnlySpaces_thenReturnException() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/api/restaurants")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"title": " ",
"city": " "
}
"""))
.andExpect(MockMvcResultMatchers.status().isBadRequest())
.andExpect(MockMvcResultMatchers.content().json("""
{
"errors": [
{
"field": "city",
"message": "City must not be empty"
},
{
"field": "title",
"message": "Title must not be empty"
}
]
}
"""));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ void getRestaurants_whenNoRestaurantsInDB_thenReturnEmptyList() {
}

@Test
void getRestaurants_whenOneRestaurantsInDB_thenReturnListOfOne() {
void getRestaurants_whenOneRestaurantInDB_thenReturnListOfOne() {
//GIVEN
Restaurant restaurant = new Restaurant("1", "The Mockingbird", "New York");
when(mockRestaurantRepository.findAll()).thenReturn(Collections.singletonList(restaurant));
Expand All @@ -42,4 +42,19 @@ void getRestaurants_whenOneRestaurantsInDB_thenReturnListOfOne() {
List<Restaurant> expected = List.of(restaurant);
assertEquals(expected, actual);
}

@Test
void addRestaurant_whenRestaurantToSave_thenReturnSavedRestaurantWithId() {
//GIVEN
Restaurant restaurantToSave = new Restaurant(null, "The Mockingbird", "New York");
Restaurant savedRestaurant = new Restaurant("1", "The Mockingbird", "New York");
when(mockRestaurantRepository.save(restaurantToSave)).thenReturn(savedRestaurant);

//WHEN
Restaurant actual = restaurantService.addRestaurant(restaurantToSave);

//THEN
verify(mockRestaurantRepository).save(restaurantToSave);
assertEquals(savedRestaurant, actual);
}
}
39 changes: 39 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.2.1",
"react-router-dom": "^6.23.1",
"styled-components": "^6.1.11"
},
"devDependencies": {
Expand Down
Loading

0 comments on commit 9898c60

Please sign in to comment.