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

UserCocktail service, controller를 구현한다. #265

Merged
merged 16 commits into from
Feb 25, 2021
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
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
package com.cocktailpick.api.config.security;

import com.cocktailpick.api.security.*;
import com.cocktailpick.api.security.oauth2.CustomOAuth2UserService;
import com.cocktailpick.api.security.oauth2.HttpCookieOAuth2AuthorizationRequestRepository;
import com.cocktailpick.api.security.oauth2.OAuth2AuthenticationFailureHandler;
import com.cocktailpick.api.security.oauth2.OAuth2AuthenticationSuccessHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
Expand All @@ -21,6 +15,19 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.cocktailpick.api.security.CustomUserDetailsService;
import com.cocktailpick.api.security.LoginFilter;
import com.cocktailpick.api.security.LoginSuccessHandler;
import com.cocktailpick.api.security.RestAuthenticationEntryPoint;
import com.cocktailpick.api.security.TokenAuthenticationFilter;
import com.cocktailpick.api.security.TokenProvider;
import com.cocktailpick.api.security.oauth2.CustomOAuth2UserService;
import com.cocktailpick.api.security.oauth2.HttpCookieOAuth2AuthorizationRequestRepository;
import com.cocktailpick.api.security.oauth2.OAuth2AuthenticationFailureHandler;
import com.cocktailpick.api.security.oauth2.OAuth2AuthenticationSuccessHandler;

import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
Expand Down Expand Up @@ -128,6 +135,12 @@ protected void configure(HttpSecurity http) throws Exception {
.hasRole(ADMIN)
.antMatchers(HttpMethod.DELETE, "/api/ingredients/**")
.hasRole(ADMIN)
.antMatchers(HttpMethod.POST, "/api/user-cocktails/**")
.hasAnyRole(USER, ADMIN)
.antMatchers(HttpMethod.GET, "/api/user-cocktails/**")
.hasAnyRole(USER, ADMIN)
.antMatchers(HttpMethod.PUT, "/api/user-cocktails/**")
.hasAnyRole(USER, ADMIN)
.anyRequest()
.permitAll()
.and()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.cocktailpick.api.userCocktail.controller;

import java.net.URI;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.cocktailpick.api.security.CurrentUser;
import com.cocktailpick.core.user.domain.User;
import com.cocktailpick.core.usercocktail.dto.UserCocktailRequest;
import com.cocktailpick.core.usercocktail.dto.UserCocktailResponse;
import com.cocktailpick.core.usercocktail.dto.UserCocktailResponses;
import com.cocktailpick.core.usercocktail.service.UserCocktailService;

import lombok.RequiredArgsConstructor;

@CrossOrigin("*")
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/user-cocktails")
public class UserCocktailController {
private final UserCocktailService userCocktailService;

@PostMapping
public ResponseEntity<Void> createUserCocktail(@CurrentUser User user,
@RequestBody UserCocktailRequest userCocktailRequest) {
Long saveId = userCocktailService.save(user, userCocktailRequest);
return ResponseEntity.created(URI.create("/api/user-cocktails/" + saveId)).build();
}

@GetMapping("/{id}")
public ResponseEntity<UserCocktailResponse> findUserCocktail(@PathVariable Long id) {
return ResponseEntity.ok(userCocktailService.findUserCocktail(id));
}

@GetMapping
public ResponseEntity<UserCocktailResponses> findUserCocktails() {
return ResponseEntity.ok(userCocktailService.findUserCocktails());
}

@PutMapping("/{id}")
public ResponseEntity<Void> updateUserCocktail(@CurrentUser User user, @PathVariable Long id,
@RequestBody UserCocktailRequest updateUserCocktailRequest) {
userCocktailService.updateUserCocktail(user, id, updateUserCocktailRequest);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUserCocktail(@CurrentUser User user, @PathVariable Long id) {
userCocktailService.deleteUserCocktail(user, id);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.cocktailpick.api.userCocktail.controller.controller;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.BDDMockito.any;
import static org.mockito.BDDMockito.*;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.Collections;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.web.context.WebApplicationContext;

import com.cocktailpick.api.common.WithMockCustomUser;
import com.cocktailpick.api.common.documentation.DocumentationWithSecurity;
import com.cocktailpick.api.userCocktail.controller.UserCocktailController;
import com.cocktailpick.api.userCocktail.controller.docs.UserCocktailDocumentation;
import com.cocktailpick.core.usercocktail.dto.UserCocktailRequest;
import com.cocktailpick.core.usercocktail.dto.UserCocktailResponse;
import com.cocktailpick.core.usercocktail.dto.UserCocktailResponses;
import com.cocktailpick.core.usercocktail.dto.UserRecipeItemResponse;
import com.cocktailpick.core.usercocktail.service.UserCocktailService;
import com.cocktailpick.core.userrecipe.dto.UserRecipeItemRequest;
import com.fasterxml.jackson.databind.ObjectMapper;

@WebMvcTest(controllers = UserCocktailController.class)
class UserCocktailControllerTest extends DocumentationWithSecurity {
@MockBean
private UserCocktailService userCocktailService;

private UserCocktailRequest userCocktailRequest;

private UserRecipeItemRequest userRecipeItemRequest;

private UserCocktailResponse userCocktailResponse;

private UserRecipeItemResponse userRecipeItemResponse;

private ObjectMapper objectMapper;

@BeforeEach
public void setUp(WebApplicationContext webApplicationContext,
RestDocumentationContextProvider restDocumentationContextProvider) {
super.setUp(webApplicationContext, restDocumentationContextProvider);

userRecipeItemRequest = UserRecipeItemRequest.builder()
.ingredientId(1L)
.quantity(123.0)
.quantityUnit("SOJU").build();

userCocktailRequest = UserCocktailRequest.builder()
.name("name")
.description("description")
.userRecipeItemRequests(Collections.singletonList(userRecipeItemRequest))
.build();

userRecipeItemResponse = UserRecipeItemResponse.builder()
.ingredientId(1L)
.ingredientName("test ingredient")
.ingredientAbv(1.0)
.ingredientColor("#000000")
.quantity(1.5)
.quantityUnit("PAPER")
.build();

userCocktailResponse = new UserCocktailResponse(1L, "test", "test Description",
Collections.singletonList(userRecipeItemResponse));

objectMapper = new ObjectMapper();
}

@DisplayName("나만의 레시피를 생성한다.")
@WithMockCustomUser
@Test
void addUserCocktail() throws Exception {
given(userCocktailService.save(any(), any())).willReturn(1L);

mockMvc.perform(post("/api/user-cocktails")
.content(objectMapper.writeValueAsString(userCocktailRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(header().string("Location", "/api/user-cocktails/1"))
.andDo(print())
.andDo(UserCocktailDocumentation.createUserCocktail());
}

@DisplayName("나만의 레시피를 단일 조회한다.")
@WithMockCustomUser
@Test
void findUserCocktailById() throws Exception {
given(userCocktailService.findUserCocktail(anyLong())).willReturn(userCocktailResponse);

mockMvc.perform(RestDocumentationRequestBuilders.get("/api/user-cocktails/{id}", 1L)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andDo(UserCocktailDocumentation.findUserCocktailById());
}

@DisplayName("나만의 레시피를 전체 조회한다.")
@WithMockCustomUser
@Test
void findUserCocktails() throws Exception {
UserCocktailResponses userCocktailResponses = new UserCocktailResponses(
Collections.singletonList(userCocktailResponse));

given(userCocktailService.findUserCocktails()).willReturn(userCocktailResponses);

mockMvc.perform(RestDocumentationRequestBuilders.get("/api/user-cocktails")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andDo(UserCocktailDocumentation.findUserCocktails());
}

@DisplayName("나만의 레시피를 수정한다.")
@WithMockCustomUser
@Test
void updateUserCocktail() throws Exception {
UserCocktailRequest userCocktailUpdateRequest = UserCocktailRequest.builder()
.name("updateUserCocktail")
.description("해피해킹 없이는 살 수 없는 몸이 돼었어")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

들켰군

.userRecipeItemRequests(Collections.singletonList(userRecipeItemRequest))
.build();

mockMvc.perform(RestDocumentationRequestBuilders.put("/api/user-cocktails/{id}", 1L)
.content(objectMapper.writeValueAsString(userCocktailUpdateRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andDo(UserCocktailDocumentation.updateUserCocktails());
}

@DisplayName("나만의 레시피를 삭제한다.")
@WithMockCustomUser
@Test
void deleteUserCocktail() throws Exception {
doNothing().when(userCocktailService).deleteUserCocktail(any(), anyLong());

mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/user-cocktails/{id}", 1L)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andDo(print())
.andDo(UserCocktailDocumentation.deleteUserCocktail());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.cocktailpick.api.userCocktail.controller.docs;

import static org.springframework.restdocs.headers.HeaderDocumentation.*;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.restdocs.request.RequestDocumentation.*;

import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.payload.JsonFieldType;

public class UserCocktailDocumentation {
public static RestDocumentationResultHandler createUserCocktail() {
return document("UserCocktails/create",
requestFields(
fieldWithPath("name").type(JsonFieldType.STRING).description("나만의 레시피 이름"),
fieldWithPath("description").type(JsonFieldType.STRING).description("나만의 레시피 설명"),
fieldWithPath("userRecipeItemRequests").type(JsonFieldType.ARRAY).description("재료 목록"),
fieldWithPath("userRecipeItemRequests.[].ingredientId").type(JsonFieldType.NUMBER).description("재료 ID"),
fieldWithPath("userRecipeItemRequests.[].quantity").type(JsonFieldType.NUMBER).description("재료 양"),
fieldWithPath("userRecipeItemRequests.[].quantityUnit").type(JsonFieldType.STRING)
.description("재료 양 단위")
),
responseHeaders(
headerWithName("Location").description("생성된 칵테일 id")
));
}

public static RestDocumentationResultHandler findUserCocktailById() {
return document("UserCocktails/find",
pathParameters(
parameterWithName("id").description("수정할 칵테일 ID")
),
responseFields(
fieldWithPath("id").type(JsonFieldType.NUMBER).description("나만의 레시피 아이디"),
fieldWithPath("name").type(JsonFieldType.STRING).description("나만의 레시피 이름"),
fieldWithPath("description").type(JsonFieldType.STRING).description("나만의 레시피 설명"),
fieldWithPath("userRecipeItemResponses").type(JsonFieldType.ARRAY).description("재료 목록"),
fieldWithPath("userRecipeItemResponses.[].ingredientId").type(JsonFieldType.NUMBER)
.description("재료 ID"),
fieldWithPath("userRecipeItemResponses.[].ingredientName").type(JsonFieldType.STRING)
.description("재료 이름"),
fieldWithPath("userRecipeItemResponses.[].ingredientColor").type(JsonFieldType.STRING)
.description("재료 색"),
fieldWithPath("userRecipeItemResponses.[].ingredientAbv").type(JsonFieldType.NUMBER)
.description("재료 도수"),
fieldWithPath("userRecipeItemResponses.[].quantity").type(JsonFieldType.NUMBER).description("재료 양"),
fieldWithPath("userRecipeItemResponses.[].quantityUnit").type(JsonFieldType.STRING)
.description("재료 양 단위")
));
}

public static RestDocumentationResultHandler findUserCocktails() {
return document("UserCocktails/findAll",
responseFields(
fieldWithPath("userCocktailResponses").type(JsonFieldType.ARRAY).description("나만의 레시피 목록"),
fieldWithPath("userCocktailResponses.[].id").type(JsonFieldType.NUMBER).description("나만의 레시피 아이디"),
fieldWithPath("userCocktailResponses.[].name").type(JsonFieldType.STRING).description("나만의 레시피 이름"),
fieldWithPath("userCocktailResponses.[].description").type(JsonFieldType.STRING)
.description("나만의 레시피 설명"),
fieldWithPath("userCocktailResponses.[].userRecipeItemResponses").type(JsonFieldType.ARRAY)
.description("재료 목록"),
fieldWithPath("userCocktailResponses.[].userRecipeItemResponses.[].ingredientId").type(
JsonFieldType.NUMBER)
.description("재료 ID"),
fieldWithPath("userCocktailResponses.[].userRecipeItemResponses.[].ingredientName").type(
JsonFieldType.STRING)
.description("재료 이름"),
fieldWithPath("userCocktailResponses.[].userRecipeItemResponses.[].ingredientColor").type(
JsonFieldType.STRING)
.description("재료 색"),
fieldWithPath("userCocktailResponses.[].userRecipeItemResponses.[].ingredientAbv").type(
JsonFieldType.NUMBER)
.description("재료 도수"),
fieldWithPath("userCocktailResponses.[].userRecipeItemResponses.[].quantity").type(JsonFieldType.NUMBER)
.description("재료 양"),
fieldWithPath("userCocktailResponses.[].userRecipeItemResponses.[].quantityUnit").type(
JsonFieldType.STRING)
.description("재료 양 단위")
));
}

public static RestDocumentationResultHandler updateUserCocktails() {
return document("UserCocktails/update",
pathParameters(
parameterWithName("id").description("수정할 칵테일 ID")
),
requestFields(
fieldWithPath("name").type(JsonFieldType.STRING).description("업데이트될 나만의 레시피 이름"),
fieldWithPath("description").type(JsonFieldType.STRING).description("업데이트 될 나만의 레시피 설명"),
fieldWithPath("userRecipeItemRequests").type(JsonFieldType.ARRAY).description("업데이트 될 재료 목록"),
fieldWithPath("userRecipeItemRequests.[].ingredientId").type(JsonFieldType.NUMBER)
.description("업데이트 될 재료 ID"),
fieldWithPath("userRecipeItemRequests.[].quantity").type(JsonFieldType.NUMBER)
.description("업데이트 될 재료 양"),
fieldWithPath("userRecipeItemRequests.[].quantityUnit").type(JsonFieldType.STRING)
.description("업데이트 될 재료 양 단위")
)
);
}

public static RestDocumentationResultHandler deleteUserCocktail() {
return document("UserCocktails/delete",
pathParameters(
parameterWithName("id").description("수정할 칵테일 ID")
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ public enum ErrorCode {
UNAUTHORIZED_REDIRECT_URI(400, "AU_003", "인증되지 않은 REDIRECT_URI입니다."),
BAD_LOGIN(400, "AU_004", "잘못된 아이디 또는 패스워드입니다."),

INGREDIENT_NOT_FOUND(400, "IN_001", "재료를 찾을 수 없습니다.");
INGREDIENT_NOT_FOUND(400, "IN_001", "재료를 찾을 수 없습니다."),
USERCOCKTAIL_NOT_FOUND(400, "UC_001", "나만의 레시피를 찾을 수 없습니다."),
USERCOCKTAIL_UNAUTHORIZED(403, "UC_002", "나만의 레시피를 수정할 수 있는 권한이 없습니다.");

private final String code;
private final String code;
private final String message;
private final int status;

Expand Down
Loading