diff --git a/README.md b/README.md index 9e2b5c9f1..882e59ba3 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,14 @@ - 특히 시크릿 키는 GitHub나 클라이언트 코드 등 외부에서 볼 수 있는 곳에 추가하지 않는다. 6. 오류 처리 레퍼런스와 문제 해결을 참고하여 발생할 수 있는 다양한 오류를 처리한다. + +# 2단계 - 주문하기 + +## 요구사항 +### 기능 요구사항 +1. 카카오톡 메시지 API를 사용하여 주문하기 기능을 구현한다. +2. 주문할 때 수령인에게 보낼 메시지를 작성할 수 있다. +3. 상품 옵션과 해당 수량을 선택하여 주문하면 해당 상품 옵션의 수량이 차감된다. +4. 해당 상품이 위시 리스트에 있는 경우 위시 리스트에서 삭제한다. +5. 나에게 보내기를 읽고 주문 내역을 카카오톡 메시지로 전송한다. +6. 메시지는 메시지 템플릿의 기본 템플릿이나 사용자 정의 템플릿을 사용하여 자유롭게 작성한다. diff --git a/src/main/java/gift/Controller/OptionController.java b/src/main/java/gift/Controller/OptionController.java index 6f1f0d438..0ec247b62 100644 --- a/src/main/java/gift/Controller/OptionController.java +++ b/src/main/java/gift/Controller/OptionController.java @@ -1,11 +1,15 @@ package gift.Controller; +import gift.Annotation.LoginMemberResolver; +import gift.Model.MemberDto; import gift.Model.OptionDto; +import gift.Model.OrderRequestDto; +import gift.Service.KakaoTalkService; import gift.Service.OptionService; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; @@ -16,10 +20,12 @@ public class OptionController { private final OptionService optionService; + private final KakaoTalkService kakaoTalkService; @Autowired - public OptionController(OptionService optionService) { + public OptionController(OptionService optionService, KakaoTalkService kakaoTalkService) { this.optionService = optionService; + this.kakaoTalkService = kakaoTalkService; } @GetMapping("/api/products/options/{productId}") @@ -80,4 +86,35 @@ public ResponseEntity deleteOption(@PathVariable long optionId) { return ResponseEntity.badRequest().body("Error deleting option"); } } + + @PostMapping("/option/purchase") + public ResponseEntity purchaseWishlist(@LoginMemberResolver MemberDto memberDto, @RequestBody List orderRequestDtoList, HttpServletRequest request) { + for (OrderRequestDto orderRequestDto : orderRequestDtoList) { + orderRequestDto.setMemberId(memberDto.getId()); + } + optionService.subtractOption(orderRequestDtoList); + + //카카오 토큰 가져오기 + String token = null; + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("accessToken".equals(cookie.getName())) { + token = cookie.getValue(); + break; + } + } + } + + //토큰으로 메시지 보내기 + if (token != null) { // + try { + kakaoTalkService.sendMessageToMe(token, orderRequestDtoList); + } catch (Exception e) { + return ResponseEntity.badRequest().body("Error sending message"); + } + } + + return ResponseEntity.ok("Purchase successful"); + } } diff --git a/src/main/java/gift/Controller/WishlistController.java b/src/main/java/gift/Controller/WishlistController.java index bf7e9c741..7e4059ec3 100644 --- a/src/main/java/gift/Controller/WishlistController.java +++ b/src/main/java/gift/Controller/WishlistController.java @@ -3,13 +3,13 @@ import gift.Annotation.LoginMemberResolver; import gift.Entity.Wishlist; import gift.Model.MemberDto; -import gift.Model.ProductDto; import gift.Model.WishlistDto; import gift.Service.WishlistService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; @@ -63,4 +63,11 @@ public String removeWishlistItem(@LoginMemberResolver MemberDto memberDto, @Requ return "redirect:/wishlist"; } + + @PostMapping("/wishlist/clear") + public ResponseEntity clearWishlist(@LoginMemberResolver MemberDto memberDto) { + wishlistService.clearWishlist(memberDto.getId()); + return ResponseEntity.ok().build(); + } + } diff --git a/src/main/java/gift/Entity/Member.java b/src/main/java/gift/Entity/Member.java index a4be68ed4..520f06ad8 100644 --- a/src/main/java/gift/Entity/Member.java +++ b/src/main/java/gift/Entity/Member.java @@ -7,7 +7,7 @@ public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private long userId; + private long memberId; private String email; private String name; private String password; @@ -17,8 +17,8 @@ public class Member { protected Member() { } - public Member(long userId, String email, String name, String password, boolean isAdmin) { - this.userId = userId; + public Member(long memberId, String email, String name, String password, boolean isAdmin) { + this.memberId = memberId; this.email = email; this.name = name; this.password = password; @@ -27,11 +27,11 @@ public Member(long userId, String email, String name, String password, boolean i // Getters and setters public long getId() { - return userId; + return memberId; } - public void setId(Long userId) { - this.userId = userId; + public void setId(Long memberId) { + this.memberId = memberId; } public String getName() { diff --git a/src/main/java/gift/Entity/Wishlist.java b/src/main/java/gift/Entity/Wishlist.java index 0f4bb58fc..8fc88f8bb 100644 --- a/src/main/java/gift/Entity/Wishlist.java +++ b/src/main/java/gift/Entity/Wishlist.java @@ -12,8 +12,8 @@ public class Wishlist { private WishlistId id; @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) - @MapsId("userId") - @JoinColumn(name = "userId", referencedColumnName = "userId") + @MapsId("memberId") + @JoinColumn(name = "memberId", referencedColumnName = "memberId") private Member member; @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) @@ -25,16 +25,21 @@ public class Wishlist { private int count; private int price; + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) + @JoinColumn(name = "optionId", referencedColumnName = "id") + private Option option; + protected Wishlist() { } - public Wishlist(WishlistId id, Member member, Product product, String productName, int count, int price) { + public Wishlist(WishlistId id, Member member, Product product, String productName, int count, int price, Option option) { this.id = id; this.member = member; this.product = product; this.productName = productName; this.count = count; this.price = price; + this.option = option; } public WishlistId getId() { @@ -45,12 +50,12 @@ public void setId(WishlistId id) { this.id = id; } - public long getUserId() { - return id.getUserId(); + public long getMemberId() { + return id.getMemberId(); } - public void setUserId(long userId) { - id.setUserId(userId); + public void setMemberId(long memberId) { + id.setMemberId(memberId); } public long getProductId() { @@ -84,4 +89,8 @@ public int getPrice() { public void setPrice(int price) { this.price = price; } + + public Option getOption() { + return option; + } } \ No newline at end of file diff --git a/src/main/java/gift/Entity/WishlistId.java b/src/main/java/gift/Entity/WishlistId.java index 6aa003aa5..e5cc5e5fe 100644 --- a/src/main/java/gift/Entity/WishlistId.java +++ b/src/main/java/gift/Entity/WishlistId.java @@ -7,23 +7,23 @@ @Embeddable public class WishlistId implements Serializable { - private long userId; + private long memberId; private long productId; protected WishlistId() { } - public WishlistId(long userId, long productId) { - this.userId = userId; + public WishlistId(long memberId, long productId) { + this.memberId = memberId; this.productId = productId; } - public long getUserId() { - return userId; + public long getMemberId() { + return memberId; } - public void setUserId(long userId) { - this.userId = userId; + public void setMemberId(long memberId) { + this.memberId = memberId; } public long getProductId() { diff --git a/src/main/java/gift/Mapper/Mapper.java b/src/main/java/gift/Mapper/Mapper.java index c9c05567b..b9c7f83fd 100644 --- a/src/main/java/gift/Mapper/Mapper.java +++ b/src/main/java/gift/Mapper/Mapper.java @@ -2,10 +2,7 @@ import gift.Entity.*; import gift.Model.*; -import gift.Service.CategoryService; -import gift.Service.ProductService; -import gift.Service.MemberService; -import gift.Service.WishlistService; +import gift.Service.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -19,13 +16,15 @@ public class Mapper { private final MemberService memberService; private final WishlistService wishlistService; private final CategoryService categoryService; + private final OptionService optionService; @Autowired - public Mapper(@Lazy ProductService productService, @Lazy MemberService memberService, @Lazy WishlistService wishListService, @Lazy CategoryService categoryService) { + public Mapper(@Lazy ProductService productService, @Lazy MemberService memberService, @Lazy WishlistService wishListService, @Lazy CategoryService categoryService, @Lazy OptionService optionService) { this.productService = productService; this.memberService = memberService; this.wishlistService = wishListService; this.categoryService = categoryService; + this.optionService = optionService; } @@ -37,13 +36,15 @@ public Wishlist wishlistDtoToEntity(WishlistDto wishlistDto) { ProductDto productDto = productDtoOptional.get(); productDto.setCategoryId(productDtoOptional.get().getCategoryId()); + OptionDto optionDto = optionService.getOptionById(wishlistDto.getOptionId()); + WishlistId id = new WishlistId(memberDto.getId(), productDto.getId()); - return new Wishlist(id, memberDtoToEntity(memberDto), productDtoToEntity(productDto), wishlistDto.getProductName(), wishlistDto.getCount(), wishlistDto.getPrice()); + return new Wishlist(id, memberDtoToEntity(memberDto), productDtoToEntity(productDto), wishlistDto.getProductName(), wishlistDto.getCount(), wishlistDto.getPrice(), optionDtoToEntity(optionDto)); } public WishlistDto wishlistToDto(Wishlist wishlist) { - return new WishlistDto(wishlist.getUserId(), wishlist.getProductId(), wishlist.getCount(), 0, wishlist.getProductName(), wishlist.getPrice()); + return new WishlistDto(wishlist.getMemberId(), wishlist.getProductId(), wishlist.getCount(), 0, wishlist.getProductName(), wishlist.getPrice(), wishlist.getOption().getId()); } public Category categoryDtoToEntity(CategoryDto categoryDto) { diff --git a/src/main/java/gift/Model/Link.java b/src/main/java/gift/Model/Link.java new file mode 100644 index 000000000..edba562e2 --- /dev/null +++ b/src/main/java/gift/Model/Link.java @@ -0,0 +1,32 @@ +package gift.Model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Link { + + private String web_url; + private String mobile_web_url; + + public Link(String web_url, String mobile_web_url) { + this.web_url = web_url; + this.mobile_web_url = mobile_web_url; + } + + + public String getWeb_url() { + return web_url; + } + + public void setWeb_url(String web_url) { + this.web_url = web_url; + } + + public String getMobile_web_url() { + return mobile_web_url; + } + + public void setMobile_web_url(String mobile_web_url) { + this.mobile_web_url = mobile_web_url; + } +} diff --git a/src/main/java/gift/Model/TextObject.java b/src/main/java/gift/Model/TextObject.java new file mode 100644 index 000000000..1bb30de46 --- /dev/null +++ b/src/main/java/gift/Model/TextObject.java @@ -0,0 +1,41 @@ +package gift.Model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class TextObject { + + private String object_type; + private String text; + private Link link; + + public TextObject(String object_type, String text, Link link) { + this.object_type = object_type; + this.text = text; + this.link = link; + } + + public String getObject_type() { + return object_type; + } + + public void setObject_type(String object_type) { + this.object_type = object_type; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Link getLink() { + return link; + } + + public void setLink(Link link) { + this.link = link; + } +} diff --git a/src/main/java/gift/Model/WishlistDto.java b/src/main/java/gift/Model/WishlistDto.java index 1bfd0f486..c3cd6bd7e 100644 --- a/src/main/java/gift/Model/WishlistDto.java +++ b/src/main/java/gift/Model/WishlistDto.java @@ -7,17 +7,19 @@ public class WishlistDto { private int quantity;//뺄 개수 private String productName; private int price; + private long optionId; public WishlistDto() { } - public WishlistDto(long userId, long productId, int count, int quantity, String productName, int price) { + public WishlistDto(long userId, long productId, int count, int quantity, String productName, int price, long optionId) { this.userId = userId; this.productId = productId; this.count = count; this.quantity = quantity; this.productName = productName; this.price = price; + this.optionId = optionId; } public long getUserId() { @@ -67,4 +69,12 @@ public int getQuantity() { public void setQuantity(int quantity) { this.quantity = quantity; } + + public long getOptionId() { + return optionId; + } + + public void setOptionId(long optionId) { + this.optionId = optionId; + } } diff --git a/src/main/java/gift/Repository/WishlistJpaRepository.java b/src/main/java/gift/Repository/WishlistJpaRepository.java index 9296bad2d..e69c9eee7 100644 --- a/src/main/java/gift/Repository/WishlistJpaRepository.java +++ b/src/main/java/gift/Repository/WishlistJpaRepository.java @@ -14,11 +14,13 @@ public interface WishlistJpaRepository extends JpaRepository { - List findByIdUserId(long userId); + List findByIdMemberId(long memberId); - @Query("SELECT w FROM Wishlist w WHERE w.id.userId = :userId AND w.id.productId = :productId") - Optional findByWishlistId(@Param("userId") long userId, @Param("productId") long productId); + @Query("SELECT w FROM Wishlist w WHERE w.id.memberId = :memberId AND w.id.productId = :productId") + Optional findByWishlistId(@Param("memberId") long memberId, @Param("productId") long productId); Page findByMember(Member member, Pageable pageable); + void deleteByIdMemberId(long memberId); + } diff --git a/src/main/java/gift/Service/KakaoTalkService.java b/src/main/java/gift/Service/KakaoTalkService.java new file mode 100644 index 000000000..b81808e35 --- /dev/null +++ b/src/main/java/gift/Service/KakaoTalkService.java @@ -0,0 +1,99 @@ +package gift.Service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import gift.Entity.Option; +import gift.Entity.Product; +import gift.Model.Link; +import gift.Model.OrderRequestDto; +import gift.Model.TextObject; +import gift.Repository.OptionJpaRepository; +import gift.Repository.ProductJpaRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +@Service +public class KakaoTalkService { + + private final RestTemplate restTemplate; + + @Autowired + private ProductJpaRepository productJpaRepository; + + @Autowired + private OptionJpaRepository optionJpaRepository; + + public KakaoTalkService() { + this.restTemplate = new RestTemplate(); + } + + public void sendMessageToMe(String token, List orderRequestDtoList) throws Exception { + // 카카오톡 메시지 전송 + StringBuilder message = new StringBuilder(); + message.append("주문 내역\n"); + for (OrderRequestDto orderRequestDto : orderRequestDtoList) { + Product product = productJpaRepository.findById(orderRequestDto.getProductId()).orElseThrow(); + Option option = optionJpaRepository.findById(orderRequestDto.getOptionId()).orElseThrow(); + message.append("상품 이름 : ") + .append(product.getName()) + .append("\n") + .append("옵션 이름 : ") + .append(option.getName()) + .append("\n") + .append("수량 : ") + .append(orderRequestDto.getQuantity()) + .append("개\n\n"); + } + + long totalPrice = orderRequestDtoList.stream() + .mapToLong(orderRequestDto -> { + Option option = optionJpaRepository.findById(orderRequestDto.getOptionId()).orElseThrow(); + int price = option.getPrice(); + int quantity = orderRequestDto.getQuantity(); + + return (long) price * quantity; + }) + .sum(); + + message.append("총 주문 금액 : ").append(totalPrice).append("원\n"); + message.append("정상적으로 주문이 완료되었습니다."); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + token); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + TextObject textObject = new TextObject( + "text", + message.toString(), + new Link("https://developers.kakao.com", "https://developers.kakao.com") + ); + + ObjectMapper objectMapper = new ObjectMapper(); + String jsonTextObject = objectMapper.writeValueAsString(textObject); + + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("template_object", jsonTextObject); + + HttpEntity> entity = new HttpEntity<>(body, headers); + try { + ResponseEntity response = restTemplate.exchange( + "https://kapi.kakao.com/v2/api/talk/memo/default/send", + HttpMethod.POST, + entity, + String.class + ); + System.out.println("Response: " + response.getBody()); + } catch (HttpStatusCodeException e) { + System.err.println("HTTP Status Code: " + e.getStatusCode()); + System.err.println("Response Body: " + e.getResponseBodyAsString()); + } + + } + +} diff --git a/src/main/java/gift/Service/OptionService.java b/src/main/java/gift/Service/OptionService.java index 623118ab6..ecebd190e 100644 --- a/src/main/java/gift/Service/OptionService.java +++ b/src/main/java/gift/Service/OptionService.java @@ -4,6 +4,7 @@ import gift.Mapper.Mapper; import gift.Model.OptionDto; import gift.Model.OrderRequestDto; +import gift.Model.WishlistDto; import gift.Repository.OptionJpaRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -55,4 +56,13 @@ public void subtractOption(OrderRequestDto orderRequestDto) { option.setQuantity(option.getQuantity() - orderRequestDto.getQuantity()); //기존 수량에서 주문 수량을 빼준다. optionJpaRepository.save(option); // 뺀 이후 update } + + public void subtractOption(List orderRequestDtoList) { + for (OrderRequestDto orderRequestDto : orderRequestDtoList) { + if(orderRequestDto.getQuantity() <= 0) continue; // 주문 수량이 0 이하인 경우 continue (다음 주문으로 넘어감 + Option option = optionJpaRepository.findById(orderRequestDto.getOptionId()).orElseThrow(() -> new RuntimeException("Option not found")); + option.setQuantity(option.getQuantity() - orderRequestDto.getQuantity()); + optionJpaRepository.save(option); + } + } } diff --git a/src/main/java/gift/Service/WishlistService.java b/src/main/java/gift/Service/WishlistService.java index 40e4ce5a7..96d9856c0 100644 --- a/src/main/java/gift/Service/WishlistService.java +++ b/src/main/java/gift/Service/WishlistService.java @@ -6,6 +6,7 @@ import gift.Model.MemberDto; import gift.Model.WishlistDto; import gift.Repository.WishlistJpaRepository; +import jakarta.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -25,8 +26,8 @@ public WishlistService(WishlistJpaRepository wishlistJpaRepository, Mapper mappe this.mapper = mapper; } - public List getWishlist(long userId) { - return wishlistJpaRepository.findByIdUserId(userId); + public List getWishlist(long memberId) { + return wishlistJpaRepository.findByIdMemberId(memberId); } public Page getWishlistByPage(MemberDto memberDto, Pageable pageable) { @@ -55,4 +56,9 @@ public void removeWishlistItem(WishlistDto wishListItem, Wishlist wishlistOption } + @Transactional + public void clearWishlist(long memberId) { + wishlistJpaRepository.deleteByIdMemberId(memberId); + } + } diff --git a/src/main/resources/templates/user_products.html b/src/main/resources/templates/user_products.html index 25c851cc7..ef7e3ecff 100644 --- a/src/main/resources/templates/user_products.html +++ b/src/main/resources/templates/user_products.html @@ -4,6 +4,45 @@ 상품 관리 +

Product Management

@@ -20,7 +59,7 @@

Product Management

Name Price Image - Actions + Options @@ -29,16 +68,14 @@

Product Management

Name Price Product Image + +
+
+
-
- - - -
-
@@ -53,6 +90,43 @@

Product Management

diff --git a/src/test/java/gift/ControllerTest/KakaoOAuthControllerTest.java b/src/test/java/gift/ControllerTest/KakaoOAuthControllerTest.java index 93e981406..16fcc8c6f 100644 --- a/src/test/java/gift/ControllerTest/KakaoOAuthControllerTest.java +++ b/src/test/java/gift/ControllerTest/KakaoOAuthControllerTest.java @@ -1,20 +1,19 @@ package gift.ControllerTest; -import gift.Controller.KakaoOAuthController; -import gift.Service.MemberService; +import org.junit.jupiter.api.BeforeEach; 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.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.client.RestTemplate; -import static net.bytebuddy.matcher.ElementMatchers.is; import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; @@ -32,6 +31,13 @@ public class KakaoOAuthControllerTest { @MockBean private RestTemplate restTemplate; + private MockRestServiceServer mockServer; + + @BeforeEach + void setUp() { + mockServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build(); + } + @Test void testAuthorize() throws Exception { mockMvc.perform(get("/oauth/authorize")) @@ -39,25 +45,60 @@ void testAuthorize() throws Exception { } @Test - void testCallBack() throws Exception { - MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate); - // 인가 코드로 액세스 토큰을 요청하는 부분 + void testCallBackSuccess() throws Exception { + String code = "unique-success-code"; + + // 액세스 토큰 요청을 모킹 mockServer.expect(requestTo("https://kauth.kakao.com/oauth/token")) - .andExpect(method(org.springframework.http.HttpMethod.POST)) - .andExpect(MockRestRequestMatchers.content().string(containsString("code=mock-code"))) - .andRespond(withSuccess("{\"access_token\": \"valid-access-token\", \"token_type\": \"bearer\"}", MediaType.APPLICATION_JSON)); + .andExpect(method(HttpMethod.POST)) + .andExpect(MockRestRequestMatchers.content().string(containsString("code=" + code))) + .andRespond(withSuccess("{\"token_type\": \"bearer\", \"access_token\": \"valid-access-token\"}", MediaType.APPLICATION_JSON)); - // 사용자 정보 요청 + // 사용자 정보 요청을 모킹 mockServer.expect(requestTo("https://kapi.kakao.com/v2/user/me")) - .andExpect(method(org.springframework.http.HttpMethod.GET)) + .andExpect(method(HttpMethod.GET)) .andRespond(withSuccess("{\"id\": 12345}", MediaType.APPLICATION_JSON)); - mockMvc.perform(get("/auth/kakao/callback").param("code", "mock-code")) - .andExpect(status().isSeeOther()) // 3xx인지 확인 - .andExpect(header().string("Location", "/products")) // 리다이렉트 주소 확인 - .andExpect(cookie().exists("accessToken")) // accessToken 쿠키가 있는지 확인 - .andExpect(request().sessionAttribute("kakaoId", is(12345L))) // 세션에 kakaoId가 12345인지 확인 - .andExpect(content().string(containsString("Successfully logged in"))); // 성공 메시지가 있는지 확인 + mockMvc.perform(get("/auth/kakao/callback").param("code", code)) + .andExpect(status().isSeeOther()) + .andExpect(header().string("Location", "/products")) + .andExpect(cookie().exists("accessToken")) + .andExpect(request().sessionAttribute("kakaoId", 12345L)) + .andExpect(content().string(containsString("Successfully logged in"))); + + mockServer.verify(); + } + + @Test + void testCallBackUsedCode() throws Exception { + String code = "unique-used-code"; + + // 액세스 토큰 요청을 모킹 + mockServer.expect(requestTo("https://kauth.kakao.com/oauth/token")) + .andExpect(method(HttpMethod.POST)) + .andExpect(MockRestRequestMatchers.content().string(containsString("code=" + code))) + .andRespond(MockRestResponseCreators.withBadRequest().body("{\"error\":\"invalid_grant\",\"error_description\":\"Authorization code is already used.\"}")); + + mockMvc.perform(get("/auth/kakao/callback").param("code", code)) + .andExpect(status().isBadRequest()) + .andExpect(content().string(containsString("Authorization code is already used."))); + + mockServer.verify(); + } + + @Test + void testCallBackCommunicationFailure() throws Exception { + String code = "unique-failure-code"; + + // 액세스 토큰 요청을 모킹 + mockServer.expect(requestTo("https://kauth.kakao.com/oauth/token")) + .andExpect(method(HttpMethod.POST)) + .andExpect(MockRestRequestMatchers.content().string(containsString("code=" + code))) + .andRespond(MockRestResponseCreators.withServerError()); + + mockMvc.perform(get("/auth/kakao/callback").param("code", code)) + .andExpect(status().isInternalServerError()) + .andExpect(content().string(containsString("Internal server error occurred"))); mockServer.verify(); } diff --git a/src/test/java/gift/RepositoryTest/MemberDtoRepositoryTest.java b/src/test/java/gift/RepositoryTest/MemberDtoRepositoryTest.java index 7014d7203..a59712527 100644 --- a/src/test/java/gift/RepositoryTest/MemberDtoRepositoryTest.java +++ b/src/test/java/gift/RepositoryTest/MemberDtoRepositoryTest.java @@ -5,6 +5,7 @@ import gift.Model.MemberDto; import gift.Repository.MemberJpaRepository; import gift.Service.MemberService; +import gift.Service.OptionService; import gift.Service.ProductService; import gift.Service.WishlistService; import org.junit.jupiter.api.BeforeEach; @@ -31,9 +32,10 @@ void setUp() { ProductService productService = Mockito.mock(ProductService.class); MemberService memberService = Mockito.mock(MemberService.class); WishlistService wishListService = Mockito.mock(WishlistService.class); + OptionService optionService = Mockito.mock(OptionService.class); // Mapper 인스턴스 수동 생성 및 주입 - mapper = new Mapper(productService, memberService, wishListService, null); + mapper = new Mapper(productService, memberService, wishListService, null, optionService); } @Test diff --git a/src/test/java/gift/RepositoryTest/WishlistRepositoryTest.java b/src/test/java/gift/RepositoryTest/WishlistRepositoryTest.java index e1b74c658..abc846e16 100644 --- a/src/test/java/gift/RepositoryTest/WishlistRepositoryTest.java +++ b/src/test/java/gift/RepositoryTest/WishlistRepositoryTest.java @@ -1,25 +1,16 @@ package gift.RepositoryTest; -import gift.Entity.Category; -import gift.Entity.Member; -import gift.Entity.Product; -import gift.Entity.Wishlist; +import gift.Entity.*; import gift.Mapper.Mapper; import gift.Model.MemberDto; +import gift.Model.OptionDto; import gift.Model.ProductDto; import gift.Model.WishlistDto; -import gift.Repository.CategoryJpaRepository; -import gift.Repository.MemberJpaRepository; -import gift.Repository.ProductJpaRepository; -import gift.Repository.WishlistJpaRepository; -import gift.Service.CategoryService; -import gift.Service.MemberService; -import gift.Service.ProductService; -import gift.Service.WishlistService; +import gift.Repository.*; +import gift.Service.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.BDDMockito; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -28,7 +19,6 @@ import java.util.Optional; import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.BDDAssumptions.given; @DataJpaTest @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @@ -46,6 +36,9 @@ public class WishlistRepositoryTest { @Autowired private CategoryJpaRepository categoryJpaRepository; + @Autowired + private OptionJpaRepository optionJpaRepository; + private Mapper mapper; @BeforeEach @@ -54,20 +47,9 @@ void setUp() { MemberService memberService = Mockito.mock(MemberService.class); WishlistService wishlistService = Mockito.mock(WishlistService.class); CategoryService categoryService = Mockito.mock(CategoryService.class); + OptionService optionService = Mockito.mock(OptionService.class); - Category category = new Category(1L, "category1"); - BDDMockito.given(categoryService.getCategoryById(1L)).willReturn(Optional.of(category)); - - - // 예시 데이터 설정 - MemberDto mockMemberDto = new MemberDto(1L, "test@test.com", "testName", "testPassword", false); - ProductDto mockProductDto = new ProductDto(1L, "testProduct", 1L, 1000, "testUrl", false); - - // 목 객체에 대한 반환 값 설정 - Mockito.when(memberService.findByUserId(Mockito.anyLong())).thenReturn(Optional.of(mockMemberDto)); - Mockito.when(productService.getProductById(Mockito.anyLong())).thenReturn(Optional.of(mockProductDto)); - - mapper = new Mapper(productService, memberService, wishlistService, categoryService); + mapper = new Mapper(productService, memberService, wishlistService, categoryService, optionService); } @Test @@ -76,11 +58,15 @@ public void testGetWishlist() { Member member = mapper.memberDtoToEntity(member1); memberJpaRepository.save(member); - WishlistDto wishlistDto = new WishlistDto(1L, 1L, 1, 0, "test", 1000); + OptionDto optionDto = new OptionDto(1L, 1L, "option1", 1000, 1); + Option option = mapper.optionDtoToEntity(optionDto); + optionJpaRepository.save(option); + + WishlistDto wishlistDto = new WishlistDto(1L, 1L, 1, 0, "test", 1000, 1L); Wishlist wishlist = mapper.wishlistDtoToEntity(wishlistDto); Wishlist savedwishlist = wishlistJpaRepository.save(wishlist); - assertThat(savedwishlist.getUserId()).isEqualTo(wishlist.getUserId()); + assertThat(savedwishlist.getMemberId()).isEqualTo(wishlist.getMemberId()); assertThat(savedwishlist.getProductId()).isEqualTo(wishlist.getProductId()); assertThat(savedwishlist.getCount()).isEqualTo(wishlist.getCount()); assertThat(savedwishlist.getPrice()).isEqualTo(wishlist.getPrice()); @@ -89,11 +75,11 @@ public void testGetWishlist() { @Test public void testAddWishlist() { - WishlistDto wishlistDto = new WishlistDto(1L, 1L, 1, 0, "test", 1000); + WishlistDto wishlistDto = new WishlistDto(1L, 1L, 1, 0, "test", 1000, 1L); Wishlist wishlist = mapper.wishlistDtoToEntity(wishlistDto); Wishlist savedwishlist = wishlistJpaRepository.save(wishlist); - assertThat(savedwishlist.getUserId()).isEqualTo(wishlist.getUserId()); + assertThat(savedwishlist.getMemberId()).isEqualTo(wishlist.getMemberId()); assertThat(savedwishlist.getProductId()).isEqualTo(wishlist.getProductId()); assertThat(savedwishlist.getCount()).isEqualTo(wishlist.getCount()); assertThat(savedwishlist.getPrice()).isEqualTo(wishlist.getPrice()); @@ -114,13 +100,17 @@ public void testRemoveWishlist() { Product product = mapper.productDtoToEntity(productDto1); productJpaRepository.save(product); - WishlistDto wishlistDto = new WishlistDto(1L, 1L, 5, 0, "productDto1", 1000); + OptionDto optionDto = new OptionDto(1L, 1L, "option1", 1000, 1); + Option option = mapper.optionDtoToEntity(optionDto); + optionJpaRepository.save(option); + + WishlistDto wishlistDto = new WishlistDto(1L, 1L, 5, 0, "productDto1", 1000, 1L); Wishlist wishlist = mapper.wishlistDtoToEntity(wishlistDto); Wishlist savedwishlist = wishlistJpaRepository.save(wishlist); wishlistJpaRepository.delete(savedwishlist); - Optional foundWishlist = wishlistJpaRepository.findByWishlistId(savedwishlist.getUserId(), savedwishlist.getProductId()); + Optional foundWishlist = wishlistJpaRepository.findByWishlistId(savedwishlist.getMemberId(), savedwishlist.getProductId()); assertThat(foundWishlist).isEmpty(); @@ -140,19 +130,23 @@ public void testUpdateWishlistItem() { Product product = mapper.productDtoToEntity(productDto1); productJpaRepository.save(product); - WishlistDto wishlistDto = new WishlistDto(1L, 1L, 5, 0, "productDto1", 5000); + OptionDto optionDto = new OptionDto(1L, 1L, "option1", 1000, 1); + Option option = mapper.optionDtoToEntity(optionDto); + optionJpaRepository.save(option); + + WishlistDto wishlistDto = new WishlistDto(1L, 1L, 5, 0, "productDto1", 5000, 1L); Wishlist wishlist = mapper.wishlistDtoToEntity(wishlistDto); wishlistJpaRepository.save(wishlist); //수량을 5개에서 3개로 변경 - WishlistDto updateDto = new WishlistDto(1L, 1L, 3, 0, "test", 3000); + WishlistDto updateDto = new WishlistDto(1L, 1L, 3, 0, "test", 3000, 1L); Wishlist updateWishlist = mapper.wishlistDtoToEntity(updateDto); wishlistJpaRepository.save(updateWishlist); - Optional foundWishlistOptional = wishlistJpaRepository.findByWishlistId(updateWishlist.getUserId(), updateWishlist.getProductId()); + Optional foundWishlistOptional = wishlistJpaRepository.findByWishlistId(updateWishlist.getMemberId(), updateWishlist.getProductId()); Wishlist foundWishlist = foundWishlistOptional.get(); - assertThat(foundWishlist.getUserId()).isEqualTo(updateWishlist.getUserId()); + assertThat(foundWishlist.getMemberId()).isEqualTo(updateWishlist.getMemberId()); assertThat(foundWishlist.getProductId()).isEqualTo(updateWishlist.getProductId()); assertThat(foundWishlist.getCount()).isEqualTo(updateWishlist.getCount()); assertThat(foundWishlist.getProductName()).isEqualTo(updateWishlist.getProductName()); diff --git a/src/test/java/gift/ServiceTest/WishlistServiceTest.java b/src/test/java/gift/ServiceTest/WishlistServiceTest.java index 2c90ed784..86c8c1880 100644 --- a/src/test/java/gift/ServiceTest/WishlistServiceTest.java +++ b/src/test/java/gift/ServiceTest/WishlistServiceTest.java @@ -1,9 +1,6 @@ package gift.ServiceTest; -import gift.Entity.Member; -import gift.Entity.Product; -import gift.Entity.Wishlist; -import gift.Entity.WishlistId; +import gift.Entity.*; import gift.Mapper.Mapper; import gift.Model.MemberDto; import gift.Model.WishlistDto; @@ -40,13 +37,13 @@ void setUp() { @Test public void testGetWishlist() { // given - long userId = 1; + long memberId = 1; // when - wishlistService.getWishlist(userId); + wishlistService.getWishlist(memberId); // then - verify(wishlistJpaRepository, times(1)).findByIdUserId(userId); + verify(wishlistJpaRepository, times(1)).findByIdMemberId(memberId); } @Test @@ -72,9 +69,10 @@ public void testAddWishlistItem() { // given Member member = new Member(1, "1234@naver.com", "1234", "1234", false); Product product = new Product(1L, "product1", null, 1000, "http://localhost:8080/image1.jpg", false); - WishlistDto wishlistDto = new WishlistDto(1L, 1L, 5, 3, "product1", 0); + Option option = new Option(1L, product, "option1", 10, 1); + WishlistDto wishlistDto = new WishlistDto(1L, 1L, 5, 3, "product1", 0, 1L); WishlistId wishlistId = new WishlistId(1L, 1L); - Wishlist wishlist = new Wishlist(wishlistId, member, product, "product1", 2, 5000); + Wishlist wishlist = new Wishlist(wishlistId, member, product, "product1", 2, 5000, option); given(mapper.wishlistDtoToEntity(wishlistDto)).willReturn(wishlist); given(wishlistJpaRepository.save(wishlist)).willReturn(wishlist); @@ -92,9 +90,10 @@ public void testDescendWishlistItem() { // given Member member = new Member(1, "1234@naver.com", "1234", "1234", false); Product product = new Product(1L, "product1", null, 1000, "http://localhost:8080/image1.jpg", false); - WishlistDto wishlistDto = new WishlistDto(1L, 1L, 5, 3, "product1", 0); + Option option = new Option(1L, product, "option1", 10, 1); + WishlistDto wishlistDto = new WishlistDto(1L, 1L, 5, 3, "product1", 0, 1L); WishlistId wishlistId = new WishlistId(1L, 1L); - Wishlist wishlist = new Wishlist(wishlistId, member, product, "product1", 2, 5000); + Wishlist wishlist = new Wishlist(wishlistId, member, product, "product1", 2, 5000, option); given(wishlistJpaRepository.save(wishlist)).willReturn(wishlist); // when @@ -111,9 +110,10 @@ public void testRemoveWishlistItem() { // given Member member = new Member(1, "1234@naver.com", "1234", "1234", false); Product product = new Product(1L, "product1", null, 1000, "http://localhost:8080/image1.jpg", false); - WishlistDto wishlistDto = new WishlistDto(1L, 1L, 0, 6, "product1", 0); + Option option = new Option(1L, product, "option1", 10, 1); + WishlistDto wishlistDto = new WishlistDto(1L, 1L, 0, 6, "product1", 0, 1L); WishlistId wishlistId = new WishlistId(1L, 1L); - Wishlist wishlist = new Wishlist(wishlistId, member, product, "product1", 5, 5000); + Wishlist wishlist = new Wishlist(wishlistId, member, product, "product1", 5, 5000, option); given(wishlistJpaRepository.save(wishlist)).willReturn(wishlist); // when