Home

0

레스토랑 예약 사이트 만들기 13 - 인가

JWT로부터 데이터 가져오기public class JwtUtil { private Key key; public JwtUtil(String secret) { this.key = Keys.hmacShaKeyFor(secret.getBytes()); } public String createToken(Long userId, String name) { String token = Jwts.builder() .claim("userId", 1004L) .claim("name", name) .signWith(key, SignatureAlgorithm.HS256) .compact(); return token; } public Claims getClaims(String token){ Claims claims = Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); return claims; }} @Test@DisplayName("Claims를 가져온다.")public void getClaims(){ String token = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEwMDQsIm5hbWUiOiJUZXN0ZXIifQ.I4DNdunio2m54tfUEaXHC_E-gvCQo6ZhHO15Ewkat6U"; Claims claims = jwtUtil.getClaims(token); assertThat(claims.get("userId", Long.class)).isEqualTo(1004L); assertThat(claims.get("name")).isEqualTo("Tester");} JWTFilter추가@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Value("${jwt.secret}") private String secret; @Override protected void configure(HttpSecurity http) throws Exception { Filter filter = new JwtAuthenticationFilter(authenticationManager(), jwtUtil()); http .cors().disable() .csrf().disable() .formLogin().disable() .headers().frameOptions().disable(); http .addFilter(filter) .sessionManagement() // Session을 사용하지 않음 .sessionCreationPolicy(SessionCreationPolicy.STATELESS); ; } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean public JwtUtil jwtUtil(){ return new JwtUtil(secret); }} JWT 인증 Filterpublic class JwtAuthenticationFilter extends BasicAuthenticationFilter { private JwtUtil jwtUtil; public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtUtil jwtUtil) { super(authenticationManager); this.jwtUtil = jwtUtil; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { Authentication authentication = getAuthentication(request); if(authentication != null){ SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(authentication); } chain.doFilter(request, response); } private Authentication getAuthentication(HttpServletRequest request){ // Header에서 Data를 얻어야 한다. String token = request.getHeader("Authorization"); if(token == null){ return null; } Claims claims = jwtUtil.getClaims(token.substring("Bearer ".length())); Authentication authentication = new UsernamePasswordAuthenticationToken(claims, null); return authentication; }} 요청시 Token값을 가지고 있는지 확인@RestController@RequiredArgsConstructorpublic class ReviewController { private final ReviewService reviewService; @PostMapping("/restaurants/{restaurantId}/reviews") public ResponseEntity<?> create( Authentication authentication, @PathVariable Long restaurantId, @Valid @RequestBody Review resource ) throws URISyntaxException { Claims claims = (Claims) authentication.getPrincipal(); String name = claims.get("name", String.class); Review review = reviewService.addReview(restaurantId, resource); String url = "/restaurants/" + restaurantId + "/reviews/" + review.getId(); return ResponseEntity.created(new URI (url)).body("{}"); }}

0

레스토랑 예약 사이트 만들기 12 - JWT

JWT를 이용해 AccesToken관리하기JwtUtil 클래스에서는 JWT를 생성하기 위한 createToken메서드가 존재한다. JWT에는 userId와 name을 넣을 것이고 HMAC-SHA256 해싱 알고리즘을 사용해 토큰의 유효성을 검사할 것이다. public class JwtUtil { private String secret; public JwtUtil(String secret) { this.secret = secret; } public String createToken(Long userId, String name) { Key key = Keys.hmacShaKeyFor(secret.getBytes()); String token = Jwts.builder() .claim("userId", 1004L) .claim("name", name) .signWith(key, SignatureAlgorithm.HS256) .compact(); return token; }} JWT가 올바르게 생성됐는지 확인하는 테스트 코드class JwtUtilTest { @Test public void createToken(){ String secret = "12345678901234567890123456789012"; JwtUtil jwtUtil = new JwtUtil(secret); String token = jwtUtil.createToken(1004L, ""); assertThat(token).contains("."); }} JwtUtil을 Bean으로 등록@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { // application.yml에 정의된 JWT Secret값을 가져온다. @Value("${jwt.secret}") private String secret; @Override protected void configure(HttpSecurity http) throws Exception { http .cors().disable() .csrf().disable() .formLogin().disable() .headers().frameOptions().disable(); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean public JwtUtil jwtUtil(){ return new JwtUtil(secret); }} 사용자 요청 결과로 JWT반환하는 로직을 구현/session 경로로 사용자 정보를 받으면 우선 사용자 유효성을 확인한 뒤, 등록된 사용자인 경우 사용자 정보를 JWT에 일부 담아 Response Body에 넣어 사용자에게 반환한다.

0

레스토랑 예약 사이트 만들기 11 - 인증

AccessToken을 이용한 인증@RestController@RequiredArgsConstructorpublic class SessionController { private final UserService userService; @PostMapping("/session") public ResponseEntity<SessionResponseDto> create(@RequestBody SessionRequestDto resource){ String email = resource.getEmail(); String password = resource.getPassword(); User user = userService.authenticate(email, password); String url = "/session"; String accessToken = user.getAccessToken(); SessionResponseDto sessionResponseDto = SessionResponseDto.builder() .accessToken(accessToken) .build(); return ResponseEntity.created(URI.create(url)).body(sessionResponseDto); }} @WebMvcTest(SessionController.class)class SessionControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test @DisplayName("AccessToken을 반환하는지 확인한다.") public void create() throws Exception { String email = "tester@example.com"; String password = "test"; User mockUser = User.builder().password("ACCESSTOKEN").build(); given(userService.authenticate(email, password)).willReturn(mockUser); ResultActions resultActions = mockMvc.perform(post("/session") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\":\"tester@example.com\",\"password\":\"test\"}")); resultActions .andExpect(status().isCreated()) .andExpect(header().string("location", "/session")) // AccessToken을 사용하는지 확인한다. .andExpect(content().string("{\"accessToken\":\"ACCESSTOKE\"}")); verify(userService).authenticate(eq(email), eq(password)); } @Test @DisplayName("올바르지 않은 이메일을 이용한 요청을 시도") public void createWithNotExistedEmail() throws Exception { String email = "x@example.com"; String password = "test"; given(userService.authenticate(email, password)).willThrow(EmailNotExistedException.class); ResultActions resultActions = mockMvc.perform(post("/session") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\":\"x@example.com\",\"password\":\"test\"}")); resultActions .andExpect(status().isBadRequest()); verify(userService).authenticate(eq(email), eq(password)); } @Test @DisplayName("올바르지 않은 패스워드를 이용한 요청을 시도") public void createWithInvalidAttributes() throws Exception { String email = "tester@example.com"; String password = "x"; given(userService.authenticate(email, password)).willThrow(PasswordWrongException.class); ResultActions resultActions = mockMvc.perform(post("/session") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\":\"tester@example.com\",\"password\":\"x\"}")); resultActions .andExpect(status().isBadRequest()); verify(userService).authenticate(eq(email), eq(password)); }} public class EmailNotExistedException extends RuntimeException{ public EmailNotExistedException(){ super("Email is Not registered"); }} public class PasswordWrongException extends RuntimeException{ public PasswordWrongException(){ super("Password is Wrong"); }} @ControllerAdvicepublic class SessionErrorAdvice { @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(PasswordWrongException.class) public String handlePasswordWrong(){ return "{}"; } @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(EmailNotExistedException.class) public String handleEmailNotExisted(){ return "{}"; }} @Service@RequiredArgsConstructorpublic class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; public User registerUser(String email, String name, String password) { // 회원이 이미 등록되어 있는지 Email을 통해 유효성 검사 Optional<User> optional = userRepository.findByEmail(email); // 회원이 이미 존재하는 경우 예외처리를 한다. if(optional.isPresent()){ throw new EmailExistedException(email); }// // 패스워드를 암호화해서 저장한다.// // 암호화 방식은 BCrypt방식을 이용해 암호화를 진행// BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String encodedPassword = passwordEncoder.encode(password); User user = User.builder() .email(email) .name(name) .password(encodedPassword) .level(1L) .build(); userRepository.save(user); return user; } public User authenticate(String email, String password) { User user = userRepository.findByEmail(email) .orElseThrow(() -> new EmailNotExistedException()); String encodedPassword = passwordEncoder.encode(password); if(!passwordEncoder.matches(password, user.getPassword())){ throw new PasswordWrongException(); } return user; }} @Test@DisplayName("올바른 파라미터를 이용해 사용자 유효성 검사를 한다.")public void authenticateWithValidAttributes(){ String email = "test@example.com"; String name = "Tester"; String password = "test"; User mockUser = User.builder() .Id(1004L) .email(email) .name(name) .password(password) .build(); given(userRepository.findByEmail(email)).willReturn(Optional.of(mockUser)); given(passwordEncoder.matches(any(), any())).willReturn(true); User user = userService.authenticate(email, password); assertThat(user.getEmail()).isEqualTo(email);}@Test@DisplayName("올바르지 않은 이메일을 이용해 사용자 유효성 검사를 한다.")public void authenticateNotValidEmail(){ String email = "x@example.com"; String password = "test"; given(userRepository.findByEmail(email)).willThrow(EmailNotExistedException.class); assertThatThrownBy(()->{ User user = userService.authenticate(email, password); }).isInstanceOf(EmailNotExistedException.class);}@Test@DisplayName("올바르지 않은 이메일을 이용해 사용자 유효성 검사를 한다.")public void authenticateNotValidPassword(){ String email = "test@example.com"; String name = "Tester"; String password = "x"; User mockUser = User.builder() .Id(1004L) .email(email) .name(name) .password(password) .build(); given(userRepository.findByEmail(email)).willReturn(Optional.of(mockUser)); given(passwordEncoder.matches(password, mockUser.getPassword())).willReturn(false); assertThatThrownBy(()->{ User user = userService.authenticate(email, password); }).isInstanceOf(PasswordWrongException.class);} @Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .cors().disable() .csrf().disable() .formLogin().disable() .headers().frameOptions().disable(); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }}

0

레스토랑 예약 사이트 만들기 10 - 회원 가입

Security 설정하기@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .cors().disable() .csrf().disable() .formLogin().disable() .headers().frameOptions().disable(); }} @Service@RequiredArgsConstructorpublic class UserService { private final UserRepository userRepository; public User registerUser(String email, String name, String password) { User user = User.builder() .email(email) .name(name) .password(encodedPassword) .level(1L) .build(); userRepository.save(user); return user; }} 패스워드에 암호화 하기@Service@RequiredArgsConstructorpublic class UserService { private final UserRepository userRepository; public User registerUser(String email, String name, String password) { // 패스워드를 암호화해서 저장한다. BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String encodedPassword = passwordEncoder.encode(password); User user = User.builder() .email(email) .name(name) .password(encodedPassword) .level(1L) .build(); userRepository.save(user); return user; }} class UserServiceTest { @Mock private UserRepository userRepository; private UserService userService; @BeforeEach public void setUp(){ MockitoAnnotations.openMocks(this); userService = new UserService(userRepository); } @Test @DisplayName("사용자를 등록한다.") public void registerUser(){ String email = "test@example.com"; String name = "Tester"; String password = "test"; User user = userService.registerUser(email, name, password); assertThat(user.getEmail()).isEqualTo(email); assertThat(user.getName()).isEqualTo(name); assertThat(user.getPassword()).isEqualTo(password); verify(userRepository).save(any()); }} @RestController@RequiredArgsConstructorpublic class UserController { private final UserService userService; @PostMapping("/users") public ResponseEntity<?> create(@RequestBody User resource){ String email = resource.getEmail(); String name = resource.getName(); String password = resource.getPassword(); User user = userService.registerUser(email, name, password); String url = "/users/" + user.getId(); return ResponseEntity.created(URI.create(url)).body("{}"); }} @WebMvcTest(UserController.class)class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test @DisplayName("사용자를 생성한다.") public void create() throws Exception { String email = "tester@example.com"; String name = "Tester"; String password = "test"; User mockUser = User.builder() .Id(1004L) .email(email) .password(password) .name(name) .build(); given(userService.registerUser(email, name, password)) .willReturn(mockUser); ResultActions resultActions = mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\" : \"tester@example.com\", \"name\" : \"Tester\", \"password\" : \"test\"}")); resultActions .andExpect(status().isCreated()) .andExpect(header().string("location", "/users/1004")); verify(userService).registerUser(any(), any(), any()); }} 존재하는 회원에 대한 예외처리

0

레스토랑 예약 사이트 만들기 9 - 사용자 관리

@Entity@Getter@Setter@NoArgsConstructor@AllArgsConstructor@Builderpublic class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long Id; @NotEmpty private String email; @NotEmpty private String name; @NotNull private Long level; public boolean isAdmin() { return level > 2L; } public void deactivate(){ level = 0L; } public boolean isActive() { return level > 0L; }} @Repositorypublic interface UserRepository extends JpaRepository<User, Long> {} @Service@RequiredArgsConstructorpublic class UserService { private final UserRepository userRepository; public List<User> getUsers() { return userRepository.findAll(); } public User addUser(String email, String name) { User user = User.builder() .email(email) .name(name) .level(1L) .build(); userRepository.save(user); return user; } public User updateUser(Long id, String email, String name, Long level ) { User user = userRepository.findById(id).orElse(null); user.setEmail(email); user.setName(name); user.setLevel(level); return user; } public User deactivateUser(Long userId) { User user = userRepository.findById(userId).orElse(null); user.deactivate(); return user; }} class UserServiceTest { @Mock private UserRepository userRepository; private UserService userService; @BeforeEach private void setUp() { MockitoAnnotations.openMocks(this); userService = new UserService(userRepository); } @Test @DisplayName("유저목록을 가져온다.") public void getUsers() { List<User> mockUsers = new ArrayList<>(); mockUsers.add(User.builder() .name("tester") .email("test@example.com") .level(3L) .build()); given(userRepository.findAll()).willReturn(mockUsers); List<User> users = userService.getUsers(); User user = users.get(0); assertThat(user.getName()).isEqualTo("tester"); assertThat(user.getEmail()).isEqualTo("test@example.com"); } @Test @DisplayName("유저를 추가한다.") public void addUser(){ String email = "test@example.com"; String name = "tester"; User mockUser = User.builder() .name(name) .email(email) .build(); given(userRepository.save(any())).willReturn(mockUser); User user = userService.addUser(email, name); assertThat(user.getName()).isEqualTo(name); assertThat(user.getEmail()).isEqualTo(email); } @Test @DisplayName("유저를 업데이트 한다.") public void updateUser(){ Long id = 1004L; Long level = 3L; String email = "test@example.com"; String name = "Superman"; User mockUser = User.builder() .Id(id) .name("Administrator") .level(1L) .email(email) .build(); given(userRepository.findById(id)).willReturn(Optional.of(mockUser)); User user = userService.updateUser(id, email, name, level); verify(userRepository).findById(id); assertThat(user.getName()).isEqualTo("Superman"); assertThat(user.isAdmin()).isEqualTo(true); } @Test @DisplayName("유저를 삭제한다.") public void deactiveUser(){ Long id = 1004L; Long level = 2L; String email = "test@example.com"; String name = "Superman"; User mockUser = User.builder() .Id(id) .level(level) .email(email) .name(name) .build(); given(userRepository.findById(id)).willReturn(Optional.of(mockUser)); User user = userService.deactivateUser(1004L); verify(userRepository).findById(1004L); assertThat(user.isAdmin()).isEqualTo(false); assertThat(user.isActive()).isEqualTo(false); }} @RestController@RequiredArgsConstructorpublic class UserController { // 1. User list // 2. User create -> 회원가입 // 3. User update // 4. User delete -> level: 0 => 아무것도 못 함. // (1: customer 2: restaurant owner 3: admin) private final UserService userService; @GetMapping("/users") public List<User> list(){ return userService.getUsers(); } @PostMapping("/users") public ResponseEntity<?> create(@RequestBody User resource) throws URISyntaxException { User user = userService.addUser(resource.getEmail(), resource.getName()); String url = "/users" + user.getId(); return ResponseEntity.created(URI.create(url)).body("{}"); } @PatchMapping("/users/{userId}") public void udpate(@PathVariable("userId") Long id, @RequestBody User user){ userService.updateUser(id, user.getEmail(), user.getName(), user.getLevel()); } @DeleteMapping("/users/{userId}") public String deactivate(@PathVariable("userId")Long userId){ userService.deactivateUser(userId); return "{}"; }} @WebMvcTest(UserController.class)class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test @DisplayName("유저목록을_가져온다") public void list() throws Exception { List<User> users = new ArrayList<>(); users.add(User.builder() .email("tester@example.com") .name("tester") .level(1L) .build()); given(userService.getUsers()).willReturn(users); mockMvc.perform(get("/users")) .andExpect(status().isOk()) .andExpect(content().string(containsString("tester"))); } @Test @DisplayName("유저를 추가한다.") public void create() throws Exception { String email = "admin@example.com"; String name = "Administrator"; User user = User.builder() .email(email) .name(name) .build(); given(userService.addUser(email, name)).willReturn(user); ResultActions resultActions = mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\" : \"admin@example.com\", \"name\" : \"Administrator\"}")); verify(userService).addUser(email, name) ; resultActions .andExpect(status().isCreated()); } @Test @DisplayName("유저상태를 업데이트 한다") public void update() throws Exception { Long id = 1004L; String email = "admin@example.com"; String name = "Administrator"; Long level = 3L; User user = User.builder() .email(email) .name(name) .level(level) .build(); given(userService.updateUser(id, email, name, level)).willReturn(user); ResultActions resultActions = mockMvc.perform(patch("/users/1004") .contentType(MediaType.APPLICATION_JSON) .content("{\"level\": 3" + ",\"email\" : \"admin@example.com\"" + ", \"name\" : \"Administrator\"}")); verify(userService).updateUser(eq(id), eq(email), eq(name), eq(level)); resultActions .andExpect(status().isOk()); } @Test @DisplayName("유저를 삭제한다.") public void deactivate() throws Exception { mockMvc.perform(delete("/users/1004")) .andExpect(status().isOk()); verify(userService).deactivateUser(1004L); }}

0

레스토랑 예약 사이트 만들기 8 - 가게목록 필터링

@Entity@Getter@Setter@NoArgsConstructor@AllArgsConstructor@Builderpublic class Region { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name;} @Repositorypublic interface RegionRepository extends JpaRepository<Region,Long> {} @Service@RequiredArgsConstructorpublic class RegionService { private final RegionRepository regionRepository; public List<Region> getRegions() { return regionRepository.findAll(); }} @Service@RequiredArgsConstructorpublic class RegionService { private final RegionRepository regionRepository; public List<Region> getRegions() { return regionRepository.findAll(); }} class RegionServiceTest { private RegionService regionService; @Mock private RegionRepository regionRepository; @BeforeEach public void setUp(){ MockitoAnnotations.openMocks(this ); regionService = new RegionService(regionRepository); } @Test public void 지역정보들을_가져온다() { List<Region> mockRegions = new ArrayList<>(); mockRegions.add(Region.builder() .name("Seoul") .build()); given(regionRepository.findAll()).willReturn(mockRegions); List<Region> regions = regionService.getRegions(); Region region = regions.get(0); assertThat(region.getName()).isEqualTo("Seoul"); }} @RestController@RequiredArgsConstructorpublic class RegionController { private final RegionService regionService; @GetMapping("/regions") public List<Region> list(){ List<Region> regions = regionService.getRegions(); return regions; }} @WebMvcTest(RegionController.class)class RegionControllerTest { @Autowired private MockMvc mockMvc; @MockBean private RegionService regionService; @Test public void 지역목록들을_가져온다() throws Exception { List<Region> regions = new ArrayList<>(); regions.add(Region.builder().name("Seoul").build()); given(regionService.getRegions()).willReturn(regions); mockMvc.perform(get("/regions")) .andExpect(status().isOk()) .andExpect(content().string(containsString("Seoul"))); }} 카테고리 만들기@Entity@Getter@Setter@NoArgsConstructor@AllArgsConstructor@Builderpublic class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Long id; public String name;}

0

레스토랑 예약 사이트 만들기 7 - 멀티모듈

dependencies { implementation project(':eatgo-common') implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test'} jar { enabled = true}bootJar { enabled = false} @Testpublic void 레스토랑에_해당되는_메뉴들을_반환한다() { List<MenuItem> mockMenuItems = new ArrayList<>(); mockMenuItems.add(MenuItem.builder() .name("Kimchi") .build()); given(menuItemRepository.findAllByRestaurantId(1004L)).willReturn(mockMenuItems); List<MenuItem> menuItems = menuItemService.getMenuItems(1004L); MenuItem menuItem = menuItems.get(0); assertThat(menuItem.getName()).isEqualTo("Kimchi");} public List<MenuItem> getMenuItems(Long restaurantId) { return menuItemRepository.findAllByRestaurantId(restaurantId);} 리뷰 정보 가져오기@GetMapping("/reviews")public List<Review> list(){ List<Review> reviews = reivewService.getReviews(); return reviews;} @Testpublic void 레스토랑에_해당되는_리뷰들을_가져온다() throws Exception { List<Review> mockReviews = new ArrayList<>(); mockReviews.add(Review.builder() .description("Cool!") .build()); given(reviewService.getReviews()).willReturn(mockReviews); mockMvc.perform(get("/reviews")) .andExpect(status().isOk()) .andExpect(content().string(containsString("Cool!")));} public List<Review> getReviews() { return reviewRepository.findAll();} @Testpublic void 리뷰들을_가져온다(){ List<Review> mockReviews = new ArrayList<>(); mockReviews.add(Review.builder() .description("Cool!") .build()); given(reviewRepository.findAll()).willReturn(mockReviews); List<Review> reviews = reviewService.getReviews(); Review review = reviews.get(0); assertThat(review.getDescription()).isEqualTo("Cool!");}

0

레스토랑 예약 사이트 만들기 6 - 리뷰 기능

새로운 리뷰 생성 오청을 처리하기 위한 control@RestController@RequiredArgsConstructorpublic class ReviewController { private final ReviewService reivewService; @PostMapping("/restaurants/{restaurantId}/reviews") public ResponseEntity<?> create( @PathVariable Long restaurantId, @Valid @RequestBody Review resource ) throws URISyntaxException { Review review = reivewService.addReview(restaurantId, resource); String url = "/restaurants/" + restaurantId + "/reviews/" + review.getId(); return ResponseEntity.created(new URI (url)).body("{}"); }} @WebMvcTest(ReviewController.class)class ReviewControllerTest { @Autowired private MockMvc mockMvc; @MockBean private ReviewService reviewService; @Test public void 리뷰를_생성한다() throws Exception { given(reviewService.addReview(eq(1L), any())).willReturn( Review.builder() .id(1004L) .name("JOKER") .score(3) .description("Mat-it-da") .build()); mockMvc.perform(post("/restaurants/1/reviews") .contentType(MediaType.APPLICATION_JSON) .content("{\"name\":\"JOKER\", \"score\":3,\"description\" : \"Mat-it-da\"}")) .andExpect(status().isCreated()) .andExpect(header().string("location", "/restaurants/1/reviews/1004")); verify(reviewService).addReview(eq(1L), any()); }} Review 도메인 객체를 생성@Entity@Builder@NoArgsConstructor@AllArgsConstructor@Getter@Setterpublic class Review { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotNull private Long restaurantId; @NotEmpty private String name; @NotNull private Integer score; @NotEmpty private String description;} Review를 저장하기 위한 Repository 생성@Repositorypublic interface ReviewRepository extends JpaRepository<Review, Long> { List<Review> findAllByRestaurantId(Long restaurantId);} Review Service 클래스를 생성한다.@Service@RequiredArgsConstructorpublic class ReviewService { private final ReviewRepository reviewRepository; public Review addReview(Long restaurantId, Review review) { review.setRestaurantId(restaurantId); reviewRepository.save(review); return review; }}

0

레스토랑 예약 사이트 만들기 5 - 메뉴 관리

메뉴를 추가하는 요청을 처리하기 위한 control@RestController@RequiredArgsConstructorpublic class MenuItemController { private final MenuItemService menuItemService; @PatchMapping("/restaurants/{restaurantId}/menuitems") public String buikUpdate(){ List<MenuItem> menuItems = new ArrayList<>(); menuItemService.bulkUpdate(menuItems); return ""; }} @WebMvcTest(MenuItemController.class)class MenuItemControllerTest { @Autowired private MockMvc mockMvc; @MockBean private MenuItemService menuItemService; @Test public void bulkUpdate() throws Exception { ResultActions resultActions = mockMvc.perform(patch("/restaurants/1/menuitems") .contentType(MediaType.APPLICATION_JSON) .content("[]")); resultActions .andExpect(status().isOk()); verify(menuItemService).bulkUpdate(any()); }} @Service@RequiredArgsConstructorpublic class MenuItemService { private final MenuItemRepository menuItemRepository; public void bulkUpdate(Long restaurantId, List<MenuItem> menuItems) { for(MenuItem menuItem : menuItems){ menuItem.setRestaurantId(restaurantId); menuItemRepository.save(menuItem); } }} class MenuItemServiceTest { private MenuItemService menuItemService; @Mock private MenuItemRepository menuItemRepository; @BeforeEach public void setUp(){ MockitoAnnotations.initMocks(this); menuItemService = new MenuItemService(menuItemRepository); } @Test public void bulkUpdate() { List<MenuItem> menuItems = new ArrayList<>(); menuItems.add(MenuItem.builder() .name("Kimchi") .build()); menuItems.add(MenuItem.builder() .name("Gukbob") .build()); menuItemService.bulkUpdate(1L, menuItems); verify(menuItemRepository, times(2)).save(any());; }} 메뉴 삭제 요청@Builder@Entity@Setter@Getter@NoArgsConstructor@AllArgsConstructorpublic class MenuItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long restaurantId; private String name; @Transient private boolean destroy; public MenuItem(String name){ this.name = name; }} @Service@RequiredArgsConstructorpublic class MenuItemService { private final MenuItemRepository menuItemRepository; public void bulkUpdate(Long restaurantId, List<MenuItem> menuItems) { for(MenuItem menuItem : menuItems){ updateOrDeleteMenuItems(restaurantId, menuItem); } } private void updateOrDeleteMenuItems(Long restaurantId, MenuItem menuItem) { if(menuItem.isDestroy()){ menuItemRepository.deleteById(menuItem.getId()); }else { menuItem.setRestaurantId(restaurantId); menuItemRepository.save(menuItem); } }} @Testpublic void 메뉴를_삭제한다() { List<MenuItem> menuItems = new ArrayList<>(); menuItems.add(MenuItem.builder() .name("Kimchi") .build()); menuItems.add(MenuItem.builder() .name("Gukbob") .build()); menuItems.add(MenuItem.builder() .id(1004L) .destroy(true) .build()); menuItemService.bulkUpdate(1L, menuItems); verify(menuItemRepository, times(2)).save(any()); verify(menuItemRepository, times(1)).deleteById(eq(1004L));}

0

레스토랑 예약 사이트 만들기 4 - 예외 처리

없는 페이지 요청에 대한 예외처리@Testpublic void 없는_페이지에대한_예외처리를_한다() throws Exception{ given(restaurantService.getRestaurant(404L)).willThrow(new RestaurantNotFoundException(404L)); ResultActions resultActions = mockMvc.perform(get("/restaurants/404")); resultActions .andExpect(status().isNotFound()) .andExpect(content().string("{}"));} public class RestaurantNotFoundException extends RuntimeException{ public RestaurantNotFoundException(Long id) { super("Could not find Restaurant" + id ); }} @ControllerAdvicepublic class RestaurantErrorAdvice { @ResponseBody @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler(RestaurantNotFoundException.class) public String handleNotFound(){ return "{}"; }} Service 계층에서의 예외처리@Testpublic void 없는_레스토랑을_가져온다() { assertThatThrownBy(() -> { Restaurant restaurant = restaurantService.getRestaurant(404L); }).isInstanceOf(RestaurantNotFoundException.class);// Restaurant restaurant = restaurantService.getRestaurant(404L);} public Restaurant getRestaurant(Long id) { Optional<Restaurant> optional = restaurantRepository.findById(id); if (optional.isPresent()) { List<MenuItem> menuItems = menuItemRepository.findAllByRestaurantId(id); Restaurant restaurant = new Restaurant(); restaurant.setMenuItems(menuItems); return restaurant; }else{ throw new RestaurantNotFoundException(id); }}