Skip to content

Commit

Permalink
[feat] : 경매 검색 API (#33)
Browse files Browse the repository at this point in the history
* [feat] : 엔티티 생성 정적 메서드 추가

* [rename] : 파일명 변경 및 경로 수정

* [chore] : querydsl 라이브러리 추가

* [fix] : reference null 예외 해결

* [feat] query repository 추가

* [refactor] 생성자 대신 builder 사용

* [test] : 카테고리 필터 테스트 코드 작성

* [feat] : respository support class 추가

* [feat] 검색 필터링 추가

* [test] : 검색 필터링 테스트 추가

* [feat] : 키워드 검색 필터링 추가

* [test] : 키워드 검색 필터링 테스트 추가

* [feat] : 정렬 기능 구현

* [feat] : pageResponse 추가

* [refactor] : 엔티티 builder 수정

* [feat] : dto 통합 및 request builder 삭제

* [feat] : 정렬 기능 추가

* [feat] : 검색 API 추가

* [test] : 서비스 테스트 추가

* [style] : 함수명 수정

* [test] : 검색 통합 테스트 작성

* [style] : 코드 리포멧팅

* [fix] : request 정적 팩토리 메서드 추가
  • Loading branch information
hyun2371 authored Feb 22, 2024
1 parent e613742 commit 5dc8713
Show file tree
Hide file tree
Showing 39 changed files with 927 additions and 286 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ out/
core/src/main/resources/application.yml
application-core.yml
/api/src/main/resources/application.yml
/core/src/main/generated/
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package dev.handsup.auction.controller;

import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import dev.handsup.auction.dto.request.AuctionSearchCondition;
import dev.handsup.auction.dto.request.RegisterAuctionRequest;
import dev.handsup.auction.dto.response.AuctionResponse;
import dev.handsup.auction.service.AuctionService;
import dev.handsup.auth.annotation.NoAuth;
import dev.handsup.common.dto.PageResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Tag(name = "경매 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/auctions")
public class AuctionApiController {

private final AuctionService auctionService;

@NoAuth
@Operation(summary = "경매 등록 API", description = "경매를 등록한다")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping
public ResponseEntity<AuctionResponse> registerAuction(@Valid @RequestBody RegisterAuctionRequest request) {
AuctionResponse response = auctionService.registerAuction(request);
return ResponseEntity.ok(response);
}

@NoAuth
@Operation(summary = "경매 검색 API", description = "경매를 검색한다")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/search")
public ResponseEntity<PageResponse<AuctionResponse>> searchAuctions(@RequestBody AuctionSearchCondition condition,
Pageable pageable) {
PageResponse<AuctionResponse> response = auctionService.searchAuctions(condition, pageable);
return ResponseEntity.ok(response);
}
}

This file was deleted.

25 changes: 0 additions & 25 deletions api/src/main/java/dev/handsup/auction/dto/ApiAuctionMapper.java

This file was deleted.

1 change: 0 additions & 1 deletion api/src/main/java/dev/handsup/auth/dto/AuthApiRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.AccessLevel;
import lombok.Builder;

@Builder(access = PRIVATE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JwtAuthorization {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import org.springframework.web.bind.annotation.RestController;

import dev.handsup.auth.annotation.NoAuth;
import dev.handsup.user.dto.UserApiMapper;
import dev.handsup.user.dto.JoinUserApiRequest;
import dev.handsup.user.dto.UserApiMapper;
import dev.handsup.user.dto.request.JoinUserRequest;
import dev.handsup.user.dto.response.JoinUserResponse;
import dev.handsup.user.service.UserService;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package dev.handsup.auction.controller;

import static org.springframework.http.MediaType.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.time.LocalDate;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;

import dev.handsup.auction.domain.Auction;
import dev.handsup.auction.domain.auction_field.PurchaseTime;
import dev.handsup.auction.domain.auction_field.TradeMethod;
import dev.handsup.auction.domain.product.ProductStatus;
import dev.handsup.auction.domain.product.product_category.ProductCategory;
import dev.handsup.auction.dto.request.AuctionSearchCondition;
import dev.handsup.auction.dto.request.RegisterAuctionRequest;
import dev.handsup.auction.repository.auction.AuctionRepository;
import dev.handsup.auction.repository.product.ProductCategoryRepository;
import dev.handsup.common.support.ApiTestSupport;
import dev.handsup.fixture.AuctionFixture;
import dev.handsup.fixture.ProductFixture;

class AuctionApiControllerTest extends ApiTestSupport {

private final String DIGITAL_DEVICE = "디지털 기기";
private ProductCategory productCategory;

@Autowired
private AuctionRepository auctionRepository;
@Autowired
private ProductCategoryRepository productCategoryRepository;

@BeforeEach
void setUp() {
productCategory = ProductFixture.productCategory(DIGITAL_DEVICE);
productCategoryRepository.save(productCategory);
}

@DisplayName("[경매를 등록할 수 있다.]")
@Test
void registerAuction() throws Exception {
RegisterAuctionRequest request = RegisterAuctionRequest.of(
"거의 새상품 버즈 팔아요",
DIGITAL_DEVICE,
10000,
LocalDate.parse("2022-10-18"),
ProductStatus.NEW.getLabel(),
PurchaseTime.UNDER_ONE_MONTH.getLabel(),
"거의 새상품이에요",
TradeMethod.DELIVER.getLabel()
);

mockMvc.perform(post("/api/auctions")
.contentType(APPLICATION_JSON)
.content(toJson(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.title").value(request.title()))
.andExpect(jsonPath("$.description").value(request.description()))
.andExpect(jsonPath("$.productStatus").value(request.productStatus()))
.andExpect(jsonPath("$.tradeMethod").value(request.tradeMethod()))
.andExpect(jsonPath("$.endDate").value(request.endDate().toString()))
.andExpect(jsonPath("$.initPrice").value(request.initPrice()))
.andExpect(jsonPath("$.purchaseTime").value(request.purchaseTime()))
.andExpect(jsonPath("$.productCategory").value(request.productCategory()))
.andExpect(jsonPath("$.si").isEmpty())
.andExpect(jsonPath("$.gu").isEmpty())
.andExpect(jsonPath("$.dong").isEmpty());
}

@DisplayName("[경매를 등록 시 상품 카테고리가 DB에 없으면 예외가 발생한다.]")
@Test
void registerAuctionFails() throws Exception {
final String NOT_EXIST_CATEGORY = "아";
RegisterAuctionRequest request = RegisterAuctionRequest.of(
"거의 새상품 버즈 팔아요",
NOT_EXIST_CATEGORY,
10000,
LocalDate.parse("2022-10-18"),
ProductStatus.NEW.getLabel(),
PurchaseTime.UNDER_ONE_MONTH.getLabel(),
"거의 새상품이에요",
TradeMethod.DELIVER.getLabel(),
"서울시",
"성북구",
"동선동"
);

mockMvc.perform(post("/api/auctions")
.contentType(APPLICATION_JSON)
.content(toJson(request)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value("존재하지 않는 상품 카테고리입니다."))
.andExpect(jsonPath("$.code").value("AU_004"));
}

@DisplayName("[경매를 검색해서 조회할 수 있다. 정렬 조건이 없을 경우 최신순으로 정렬한다.]")
@Test
void searchAuction() throws Exception {
Auction auction1 = AuctionFixture.auction(productCategory, "버즈 이어폰 팔아요");
Auction auction2 = AuctionFixture.auction(productCategory, "에버어즈팟");
Auction auction3 = AuctionFixture.auction(productCategory, "버즈 이어폰 팔아요");
AuctionSearchCondition condition = AuctionSearchCondition.builder()
.keyword("버즈").build();
auctionRepository.saveAll(List.of(auction1, auction2, auction3));

mockMvc.perform(post("/api/auctions/search")
.contentType(APPLICATION_JSON)
.content(toJson(condition)))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.content[0].title").value(auction1.getTitle()))
.andExpect(jsonPath("$.content[0].description").value(auction1.getProduct().getDescription()))
.andExpect(jsonPath("$.content[0].productStatus").value(auction1.getProduct().getStatus().getLabel()))
.andExpect(jsonPath("$.content[0].tradeMethod").value(auction1.getTradeMethod().getLabel()))
.andExpect(jsonPath("$.content[0].endDate").value(auction1.getEndDate().toString()))
.andExpect(jsonPath("$.content[0].initPrice").value(auction1.getInitPrice()))
.andExpect(jsonPath("$.content[0].purchaseTime").value(auction1.getProduct().getPurchaseTime().getLabel()))
.andExpect(jsonPath("$.content[0].productCategory").value(
auction1.getProduct().getProductCategory().getCategoryValue()))
.andExpect(jsonPath("$.content[0].si").value(auction1.getTradingLocation().getSi()))
.andExpect(jsonPath("$.content[0].gu").value(auction1.getTradingLocation().getGu()))
.andExpect(jsonPath("$.content[0].dong").value(auction1.getTradingLocation().getDong()))
.andExpect(jsonPath("$.content[1].title").value(auction3.getTitle()));
}

@DisplayName("[경매를 북마크 순으로 정렬할 수 있다.]")
@Test
void searchAuctionSort() throws Exception {
Auction auction1 = AuctionFixture.auction(productCategory);
Auction auction2 = AuctionFixture.auction(productCategory);
Auction auction3 = AuctionFixture.auction(productCategory);
ReflectionTestUtils.setField(auction2, "bookmarkCount", 5);
ReflectionTestUtils.setField(auction3, "bookmarkCount", 3);
auctionRepository.saveAll(List.of(auction1, auction2, auction3));
AuctionSearchCondition condition = AuctionSearchCondition.builder()
.keyword(null).build();

mockMvc.perform(post("/api/auctions/search")
.content(toJson(condition))
.contentType(APPLICATION_JSON)
.param("sort", "bookmarkCount,desc"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.size").value(3))
.andExpect(jsonPath("$.content[0].auctionId").value(auction2.getId()))
.andExpect(jsonPath("$.content[1].auctionId").value(auction3.getId()))
.andExpect(jsonPath("$.content[2].auctionId").value(auction1.getId()));
}

@DisplayName("[검색된 경매를 필터링할 수 있다.]")
@Test
void searchAuctionFilter() throws Exception {
Auction auction1 = AuctionFixture.auction(productCategory, "버즈", 15000);
Auction auction2 = AuctionFixture.auction(productCategory, "에어팟", 15000);
Auction auction3 = AuctionFixture.auction(productCategory, "버즈 팔아요", 25000);
ReflectionTestUtils.setField(auction2, "bookmarkCount", 5);
ReflectionTestUtils.setField(auction3, "bookmarkCount", 3);
auctionRepository.saveAll(List.of(auction1, auction2, auction3));
AuctionSearchCondition condition = AuctionSearchCondition.builder()
.keyword("버즈")
.minPrice(10000)
.maxPrice(20000)
.build();

mockMvc.perform(post("/api/auctions/search")
.content(toJson(condition))
.contentType(APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.size").value(1))
.andExpect(jsonPath("$.content[0].auctionId").value(auction1.getId()));
}
}
Loading

0 comments on commit 5dc8713

Please sign in to comment.