diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 335c4ed..b77c14a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,9 @@ name: Test Coverage + on: pull_request: - branches: + branches: - 'dev' paths: - '**.java' @@ -12,6 +13,7 @@ permissions: pull-requests: write env: + APPLICATION: ${{ secrets.APPLICATION_TEST }} wd: ./backend jobs: @@ -20,12 +22,19 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' + - name: Copy application.yml + working-directory: ${{ env.wd }} + run: | + touch ./src/test/resources/application.yml + echo "${{ secrets.APPLICATION_TEST }}" > ./src/test/resources/application.yml + - name: Setup Gradle uses: gradle/gradle-build-action@v2 diff --git a/backend-recommend/recommend.py b/backend-recommend/recommend.py index 10e63cf..0460a72 100644 --- a/backend-recommend/recommend.py +++ b/backend-recommend/recommend.py @@ -25,7 +25,7 @@ def recByApriori(body): optionList = body['options'] for i in range(len(optionList)): - input.append(optionList[i]['subOptionId']) + input.append(str(optionList[i])) input = set(input) dataset = [] @@ -65,6 +65,9 @@ def recByApriori(body): response = [] for item in top_items: consequent = item[0] - response.append({"salesOptions": ",".join(consequent)}) + subResponse = [] + for option in consequent: + subResponse.append(int(option)) + response.append(subResponse) return jsonify(response) \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore index 6c3f256..f2a5319 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -37,4 +37,5 @@ out/ .vscode/ ### yml files ### -src/main/resources/application.yml \ No newline at end of file +src/main/resources/application.yml +src/test/resources/application.yml \ No newline at end of file diff --git a/backend/src/main/java/autoever2/cartag/controller/CarController.java b/backend/src/main/java/autoever2/cartag/controller/CarController.java index 4538b1a..503cd12 100644 --- a/backend/src/main/java/autoever2/cartag/controller/CarController.java +++ b/backend/src/main/java/autoever2/cartag/controller/CarController.java @@ -1,10 +1,7 @@ package autoever2.cartag.controller; -import autoever2.cartag.domain.car.BoughtCarDto; import autoever2.cartag.domain.car.CarDefaultDto; import autoever2.cartag.domain.car.CarDto; -import autoever2.cartag.domain.share.QuoteIdList; -import autoever2.cartag.domain.share.QuoteInfoDto; import autoever2.cartag.domain.car.CarTypeDto; import autoever2.cartag.service.CarService; import io.swagger.v3.oas.annotations.Operation; @@ -15,7 +12,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -56,24 +52,4 @@ public List carTrimInfo(@Parameter(description = "선택한 car_type") @ public CarDefaultDto carDefaultDto(@Parameter(description = "선택한 car_id") @RequestParam("carid") int carId) { return service.findCarDefaultDtoByCarId(carId); } - - @Operation(summary = "차량 구매 정보 반환 api", description = "차량 구매 정보 조회 method") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = BoughtCarDto.class))), - }) - @GetMapping("bought/infos") - public List boughtCarDtos() { - return service.findAllBoughInfos(); - } - - @Operation(summary = "차량 공유하기를 위한 api", description = "차량 공유를 위한 정보 반환") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = QuoteInfoDto.class))), - }) - @PostMapping("/infos/shares") - public QuoteInfoDto boughtCarDtos(@Parameter(description = "선택한 id 리스트") @RequestBody QuoteIdList idList) { - return service.findShareInfoDto(idList); - } - - } diff --git a/backend/src/main/java/autoever2/cartag/controller/ModelController.java b/backend/src/main/java/autoever2/cartag/controller/ModelController.java index ce55de9..0a9fcac 100644 --- a/backend/src/main/java/autoever2/cartag/controller/ModelController.java +++ b/backend/src/main/java/autoever2/cartag/controller/ModelController.java @@ -3,7 +3,6 @@ import autoever2.cartag.domain.model.ModelDetailMappedDto; import autoever2.cartag.domain.model.ModelEfficiencyDataDto; import autoever2.cartag.domain.model.ModelShortDataDto; -import autoever2.cartag.domain.model.PowerTrainMappedDto; import autoever2.cartag.service.ModelService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -37,7 +36,8 @@ public class ModelController { }) @GetMapping("/list") public List getTrimModelType(@Parameter(description = "선택한 차량 트림ID") @RequestParam("carid") int carId) { - return modelTypeService.getModelTypeData(carId); + List result = modelTypeService.getModelTypeData(carId); + return result; } @Operation(summary = "모델타입 상세 데이터 조회", description = "모델명과 설명, 이미지 반환하는 api") diff --git a/backend/src/main/java/autoever2/cartag/controller/QuoteController.java b/backend/src/main/java/autoever2/cartag/controller/QuoteController.java new file mode 100644 index 0000000..8ab4177 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/controller/QuoteController.java @@ -0,0 +1,56 @@ +package autoever2.cartag.controller; + +import autoever2.cartag.domain.car.BoughtCarDto; +import autoever2.cartag.domain.quote.HistoryShortDto; +import autoever2.cartag.domain.quote.QuoteDataDto; +import autoever2.cartag.domain.quote.QuoteInfoDto; +import autoever2.cartag.service.CarService; +import autoever2.cartag.service.QuoteService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/quote") +@RequiredArgsConstructor +public class QuoteController { + + private final CarService carService; + private final QuoteService quoteService; + + @Operation(summary = "추천 견적 그래프 제공 API", description = "유사견적(최대 4개)와 판매횟수 제공") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = HistoryShortDto.class))) + }) + @PostMapping("/list") + public List getRecommendedList(@RequestBody QuoteDataDto quoteDataDto) { + List result = quoteService.findTopHistory(quoteDataDto); + return result; + } + + @Operation(summary = "차량 구매 정보 반환 api", description = "차량 구매 정보 조회 method") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = BoughtCarDto.class))), + }) + @GetMapping("bought/infos") + public List getAllHistorySum() { + return carService.findAllBoughInfos(); + } + + @Operation(summary = "차량 공유하기를 위한 api", description = "차량 공유를 위한 정보 반환") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = QuoteInfoDto.class))), + }) + @PostMapping("/infos/shares") + public QuoteInfoDto getQuoteDetail(@Parameter(description = "선택한 id 리스트") @RequestBody QuoteDataDto idList) { + QuoteInfoDto data = carService.findShareInfoDto(idList); + return data; + } +} diff --git a/backend/src/main/java/autoever2/cartag/controller/RecommendController.java b/backend/src/main/java/autoever2/cartag/controller/RecommendController.java deleted file mode 100644 index 3d56738..0000000 --- a/backend/src/main/java/autoever2/cartag/controller/RecommendController.java +++ /dev/null @@ -1,21 +0,0 @@ -package autoever2.cartag.controller; - -import autoever2.cartag.service.RecommendService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/recommend") -@RequiredArgsConstructor -public class RecommendController { - - private final RecommendService recommendService; - - @PostMapping("/list") - public ResponseEntity getRecommendedList() { - return ResponseEntity.ok(recommendService.getList()); - } -} diff --git a/backend/src/main/java/autoever2/cartag/domain/model/PowerTrainMappedDto.java b/backend/src/main/java/autoever2/cartag/domain/model/PowerTrainMappedDto.java deleted file mode 100644 index 93b7a55..0000000 --- a/backend/src/main/java/autoever2/cartag/domain/model/PowerTrainMappedDto.java +++ /dev/null @@ -1,31 +0,0 @@ -package autoever2.cartag.domain.model; - -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.Objects; - -@Setter -@Getter -@NoArgsConstructor -public class PowerTrainMappedDto { - - private String maxPs; - private String maxKgfm; - - @Builder - public PowerTrainMappedDto(String maxPs, String maxKgfm) { - this.maxPs = maxPs; - this.maxKgfm = maxKgfm; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PowerTrainMappedDto that = (PowerTrainMappedDto) o; - return Objects.equals(maxPs, that.maxPs) && Objects.equals(maxKgfm, that.maxKgfm); - } -} diff --git a/backend/src/main/java/autoever2/cartag/domain/quote/HistorySearchDto.java b/backend/src/main/java/autoever2/cartag/domain/quote/HistorySearchDto.java new file mode 100644 index 0000000..42da0ec --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/quote/HistorySearchDto.java @@ -0,0 +1,57 @@ +package autoever2.cartag.domain.quote; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@NoArgsConstructor +@Getter +public class HistorySearchDto { + + private int carId; + private int powerTrainId; + private int bodyTypeId; + private int operationId; + private List optionIds; + + @Builder + public HistorySearchDto(int carId, int powerTrainId, int bodyTypeId, int operationId, List optionIds) { + this.carId = carId; + this.powerTrainId = powerTrainId; + this.bodyTypeId = bodyTypeId; + this.operationId = operationId; + this.optionIds = new ArrayList<>(); + + this.optionIds.addAll(optionIds); + } + + public void addAllOption(List optionId) { + optionIds.addAll(optionId); + } + + public String getOptionIds() { + if(optionIds.isEmpty()) { + return ""; + } + + StringBuilder stringBuilder = new StringBuilder(); + for(int index = 0; index < optionIds.size() - 1; index++) { + stringBuilder.append(optionIds.get(index)).append(","); + } + stringBuilder.append(optionIds.get(optionIds.size()-1)); + + return stringBuilder.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + HistorySearchDto that = (HistorySearchDto) o; + return carId == that.carId && powerTrainId == that.powerTrainId && bodyTypeId == that.bodyTypeId && operationId == that.operationId && Objects.equals(optionIds, that.optionIds); + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/quote/HistoryShortDto.java b/backend/src/main/java/autoever2/cartag/domain/quote/HistoryShortDto.java new file mode 100644 index 0000000..7ad1535 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/domain/quote/HistoryShortDto.java @@ -0,0 +1,31 @@ +package autoever2.cartag.domain.quote; + +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Schema(description = "실제 판매견적 간략 데이터") +@Getter +@Setter +@NoArgsConstructor +public class HistoryShortDto { + + @Schema(description = "견적의 ID, 또는 내 견적의 ID") + private Long historyId; + @Schema(description = "견적의 판매 횟수") + private int soldCount; + @ArraySchema(schema = @Schema(implementation = HistoryShortDto.class, description = "유사견적 상위 4개 반환")) + List histories; + + @Builder + public HistoryShortDto(Long historyId, int soldCount, List histories) { + this.historyId = historyId; + this.soldCount = soldCount; + this.histories = histories; + } +} diff --git a/backend/src/main/java/autoever2/cartag/domain/share/QuoteIdList.java b/backend/src/main/java/autoever2/cartag/domain/quote/QuoteDataDto.java similarity index 81% rename from backend/src/main/java/autoever2/cartag/domain/share/QuoteIdList.java rename to backend/src/main/java/autoever2/cartag/domain/quote/QuoteDataDto.java index 9faec2d..03b287a 100644 --- a/backend/src/main/java/autoever2/cartag/domain/share/QuoteIdList.java +++ b/backend/src/main/java/autoever2/cartag/domain/quote/QuoteDataDto.java @@ -1,4 +1,4 @@ -package autoever2.cartag.domain.share; +package autoever2.cartag.domain.quote; import lombok.Builder; import lombok.Getter; @@ -11,7 +11,7 @@ @Getter @Setter @NoArgsConstructor -public class QuoteIdList { +public class QuoteDataDto { private int carId; private int powerTrainId; private int bodyTypeId; @@ -21,7 +21,7 @@ public class QuoteIdList { private List optionIdList = new ArrayList<>(); @Builder - public QuoteIdList(int carId, int powerTrainId, int bodyTypeId, int operationId, int outerColorId, int innerColorId, List optionIdList) { + public QuoteDataDto(int carId, int powerTrainId, int bodyTypeId, int operationId, int outerColorId, int innerColorId, List optionIdList) { this.carId = carId; this.powerTrainId = powerTrainId; this.bodyTypeId = bodyTypeId; @@ -35,7 +35,7 @@ public QuoteIdList(int carId, int powerTrainId, int bodyTypeId, int operationId, public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - QuoteIdList that = (QuoteIdList) o; + QuoteDataDto that = (QuoteDataDto) o; return carId == that.carId && powerTrainId == that.powerTrainId && bodyTypeId == that.bodyTypeId && operationId == that.operationId && outerColorId == that.outerColorId && innerColorId == that.innerColorId && Objects.equals(optionIdList, that.optionIdList); } } diff --git a/backend/src/main/java/autoever2/cartag/domain/share/QuoteInfoDto.java b/backend/src/main/java/autoever2/cartag/domain/quote/QuoteInfoDto.java similarity index 99% rename from backend/src/main/java/autoever2/cartag/domain/share/QuoteInfoDto.java rename to backend/src/main/java/autoever2/cartag/domain/quote/QuoteInfoDto.java index a5bd89a..c831fdc 100644 --- a/backend/src/main/java/autoever2/cartag/domain/share/QuoteInfoDto.java +++ b/backend/src/main/java/autoever2/cartag/domain/quote/QuoteInfoDto.java @@ -1,4 +1,4 @@ -package autoever2.cartag.domain.share; +package autoever2.cartag.domain.quote; import autoever2.cartag.domain.car.TrimInfoDto; import autoever2.cartag.domain.color.InnerColorDto; diff --git a/backend/src/main/java/autoever2/cartag/exception/ErrorCode.java b/backend/src/main/java/autoever2/cartag/exception/ErrorCode.java index d86cdb0..3432fdd 100644 --- a/backend/src/main/java/autoever2/cartag/exception/ErrorCode.java +++ b/backend/src/main/java/autoever2/cartag/exception/ErrorCode.java @@ -7,9 +7,11 @@ @Getter @RequiredArgsConstructor public enum ErrorCode { - INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "Invalid parameter included"), - RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "Resource not exists"), - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Unknown error"); + INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "잘못된 파라미터 형식입니다."), + INVALID_OPTIONS_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 옵션으로 접근하였습니다."), + DATA_NOT_EXISTS(HttpStatus.NOT_FOUND, "데이터가 존재하지 않습니다."), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버오류입니다. 관리자에게 문의하세요."), + PARSE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "데이터 파싱에 실패했습니다."); private final HttpStatus httpStatus; private final String message; diff --git a/backend/src/main/java/autoever2/cartag/exception/GlobalExceptionHandler.java b/backend/src/main/java/autoever2/cartag/exception/GlobalExceptionHandler.java index 5868423..85a1962 100644 --- a/backend/src/main/java/autoever2/cartag/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/autoever2/cartag/exception/GlobalExceptionHandler.java @@ -1,10 +1,12 @@ package autoever2.cartag.exception; +import lombok.extern.log4j.Log4j2; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice +@Log4j2 public class GlobalExceptionHandler { @ExceptionHandler(EmptyDataException.class) @@ -15,4 +17,22 @@ public ResponseEntity handleEmptyDataException(EmptyDataException .message(errorCode.getMessage()) .build()); } + + @ExceptionHandler(InvalidDataException.class) + public ResponseEntity handleInvalidDataException(InvalidDataException e) { + ErrorCode errorCode = e.getErrorCode(); + return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.builder() + .code(errorCode.toString()) + .message(errorCode.getMessage()) + .build()); + } + + @ExceptionHandler(ServerException.class) + public ResponseEntity handleServerException(ServerException e) { + ErrorCode errorCode = e.getErrorCode(); + log.error("Server Error: " + errorCode.getMessage()); + return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.builder() + .code(errorCode.toString()) + .build()); + } } diff --git a/backend/src/main/java/autoever2/cartag/exception/InvalidDataException.java b/backend/src/main/java/autoever2/cartag/exception/InvalidDataException.java new file mode 100644 index 0000000..997122d --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/exception/InvalidDataException.java @@ -0,0 +1,11 @@ +package autoever2.cartag.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class InvalidDataException extends RuntimeException { + + private final ErrorCode errorCode; +} diff --git a/backend/src/main/java/autoever2/cartag/exception/ServerException.java b/backend/src/main/java/autoever2/cartag/exception/ServerException.java new file mode 100644 index 0000000..f7d5cd1 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/exception/ServerException.java @@ -0,0 +1,19 @@ +package autoever2.cartag.exception; + +import lombok.Getter; + +@Getter +public class ServerException extends RuntimeException { + private final ErrorCode errorCode; + private final String message; + + public ServerException(ErrorCode errorCode) { + this.errorCode = errorCode; + this.message = null; + } + + public ServerException(ErrorCode errorCode, String cause) { + this.errorCode = errorCode; + this.message = cause; + } +} diff --git a/backend/src/main/java/autoever2/cartag/recommend/RecommendConnector.java b/backend/src/main/java/autoever2/cartag/recommend/RecommendConnector.java new file mode 100644 index 0000000..c14d001 --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/recommend/RecommendConnector.java @@ -0,0 +1,81 @@ +package autoever2.cartag.recommend; + +import autoever2.cartag.domain.quote.QuoteDataDto; +import autoever2.cartag.exception.ErrorCode; +import autoever2.cartag.exception.ServerException; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; + +@Component +public class RecommendConnector { + + @Value("${python.url}") + private String requestURL; + + public List> request(QuoteDataDto quoteDataDto) { + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(requestURL)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(getJsonFromEstimate(quoteDataDto))).build(); + + HttpResponse response = null; + try { + response = client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (InterruptedException e) { + throw new ServerException(ErrorCode.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (IOException e) { + throw new ServerException(ErrorCode.INTERNAL_SERVER_ERROR, e.getMessage()); + } + + return parseResponse(response.body()); + } + + + public String getJsonFromEstimate(QuoteDataDto quoteDataDto) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("carId", quoteDataDto.getCarId()); + jsonObject.put("powerTrain", quoteDataDto.getPowerTrainId()); + jsonObject.put("bodyType", quoteDataDto.getBodyTypeId()); + jsonObject.put("operation", quoteDataDto.getOperationId()); + jsonObject.put("options", quoteDataDto.getOptionIdList()); + + return jsonObject.toJSONString(); + } + + private List> parseResponse(String responseBody) { + JSONParser parser = new JSONParser(); + JSONArray resultArray = null; + try { + resultArray = (JSONArray) parser.parse(responseBody); + } catch (ParseException e) { + throw new ServerException(ErrorCode.PARSE_ERROR, e.getMessage()); + } + + List> result = new ArrayList<>(); + for (Object o : resultArray) { + JSONArray optionArray = (JSONArray) o; + List options = new ArrayList<>(); + + for (Object option : optionArray) { + options.add(Math.toIntExact((Long) option)); + } + + result.add(options); + } + return result; + } +} diff --git a/backend/src/main/java/autoever2/cartag/repository/OptionRepository.java b/backend/src/main/java/autoever2/cartag/repository/OptionRepository.java index 079a4ab..d4db237 100644 --- a/backend/src/main/java/autoever2/cartag/repository/OptionRepository.java +++ b/backend/src/main/java/autoever2/cartag/repository/OptionRepository.java @@ -150,4 +150,15 @@ private RowMapper shareSubOptionRowMapper() { return BeanPropertyRowMapper.newInstance(QuoteSubOptionDto.class); } + public Long countExistOptions(int carId, List optionIds) { + String sql = "select count(*) as totalCount " + + "from SubOptionData " + + "where car_id = :carId and option_id IN (:optionIds)"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("carId", carId) + .addValue("optionIds", optionIds); + + return template.queryForObject(sql, param, Long.class); + } } diff --git a/backend/src/main/java/autoever2/cartag/repository/QuoteRepository.java b/backend/src/main/java/autoever2/cartag/repository/QuoteRepository.java new file mode 100644 index 0000000..27f1a0a --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/repository/QuoteRepository.java @@ -0,0 +1,46 @@ +package autoever2.cartag.repository; + +import autoever2.cartag.domain.quote.HistorySearchDto; +import autoever2.cartag.domain.quote.HistoryShortDto; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.util.Optional; + +@Repository +public class QuoteRepository { + + private final NamedParameterJdbcTemplate template; + + public QuoteRepository(DataSource dataSource) { + this.template = new NamedParameterJdbcTemplate(dataSource); + } + + public Optional findShortData(HistorySearchDto historySearchDto) { + String sql = "select sh.history_id, sh.sold_count, sh.sold_options_id " + + "from SalesHistory sh " + + "inner join HistoryModelMapper hm " + + "on hm.history_id = sh.history_id " + + "where sh.car_id = :carId and sh.sold_options_id = :optionIds and hm.model_id in (:powerTrainId, :bodyTypeId, :operationId) " + + "group by sh.history_id having count(hm.model_id) = 3"; + + SqlParameterSource param = new MapSqlParameterSource() + .addValue("carId", historySearchDto.getCarId()) + .addValue("optionIds", historySearchDto.getOptionIds()) + .addValue("powerTrainId", historySearchDto.getPowerTrainId()) + .addValue("bodyTypeId", historySearchDto.getBodyTypeId()) + .addValue("operationId", historySearchDto.getOperationId()); + + return Optional.ofNullable(DataAccessUtils.singleResult(template.query(sql, param, historyShortRowMapper()))); + } + + private RowMapper historyShortRowMapper() { + return BeanPropertyRowMapper.newInstance(HistoryShortDto.class); + } +} diff --git a/backend/src/main/java/autoever2/cartag/service/CarService.java b/backend/src/main/java/autoever2/cartag/service/CarService.java index 4f7462e..9959aaa 100644 --- a/backend/src/main/java/autoever2/cartag/service/CarService.java +++ b/backend/src/main/java/autoever2/cartag/service/CarService.java @@ -5,8 +5,8 @@ import autoever2.cartag.domain.color.OuterColorDto; import autoever2.cartag.domain.model.ModelDefaultDto; import autoever2.cartag.domain.option.QuoteSubOptionDto; -import autoever2.cartag.domain.share.QuoteIdList; -import autoever2.cartag.domain.share.QuoteInfoDto; +import autoever2.cartag.domain.quote.QuoteDataDto; +import autoever2.cartag.domain.quote.QuoteInfoDto; import autoever2.cartag.exception.EmptyDataException; import autoever2.cartag.exception.ErrorCode; import autoever2.cartag.repository.CarRepository; @@ -41,7 +41,7 @@ public List getAllCarTypes() { public List findCarByCarType(int carType) { List carInfos = carRepository.findCarByCarType(carType); if (carInfos.isEmpty()) { - throw new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND); + throw new EmptyDataException(ErrorCode.DATA_NOT_EXISTS); } return carInfos.stream() @@ -54,12 +54,12 @@ public CarDefaultDto findCarDefaultDtoByCarId(int carId) { List innerColorList = colorRepository.findInnerColorCarByCarId(carId); List modelList = modelRepository.findModelDefaultDtoByCarId(carId); if (outerColorList.isEmpty() || innerColorList.isEmpty() || modelList.isEmpty()) { - throw new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND); + throw new EmptyDataException(ErrorCode.DATA_NOT_EXISTS); } int colorId = outerColorList.get(0).getColorId(); Optional colorCarOuterImage = colorRepository.findOuterColorImagesByColorId(colorId); if (colorCarOuterImage.isEmpty()) { - throw new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND); + throw new EmptyDataException(ErrorCode.DATA_NOT_EXISTS); } String value = colorCarOuterImage.get(); String outerImageUrl = value.substring(0, value.indexOf("*")) + 1 + value.substring(value.indexOf("*") + 1, value.length()); @@ -94,7 +94,7 @@ public List findAllBoughInfos() { .collect(Collectors.toList()); } - public QuoteInfoDto findShareInfoDto(QuoteIdList idList) { + public QuoteInfoDto findShareInfoDto(QuoteDataDto idList) { int carId = idList.getCarId(); int powerTrainId = idList.getPowerTrainId(); int bodyTypeId = idList.getBodyTypeId(); @@ -115,7 +115,7 @@ public QuoteInfoDto findShareInfoDto(QuoteIdList idList) { optionInfos.add(optionInfo.get()); continue; } - throw new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND); + throw new EmptyDataException(ErrorCode.DATA_NOT_EXISTS); } } OuterColorDto outerColorDto = outerColorInfo.get(); diff --git a/backend/src/main/java/autoever2/cartag/service/ColorService.java b/backend/src/main/java/autoever2/cartag/service/ColorService.java index 0e4d343..b508afe 100644 --- a/backend/src/main/java/autoever2/cartag/service/ColorService.java +++ b/backend/src/main/java/autoever2/cartag/service/ColorService.java @@ -10,7 +10,6 @@ import autoever2.cartag.repository.ColorRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; import java.util.ArrayList; import java.util.List; @@ -30,7 +29,7 @@ public class ColorService { public List changeImageToImages(int colorId) { Optional images = colorRepository.findOuterColorImagesByColorId(colorId); if (images.isEmpty()) { - throw new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND); + throw new EmptyDataException(ErrorCode.DATA_NOT_EXISTS); } List outerColorCarImages = new ArrayList<>(); String value = images.get(); @@ -44,7 +43,7 @@ public List changeImageToImages(int colorId) { public List findOuterColorByCarId(int carId) { List outerColors = colorRepository.findOuterColorCarByCarId(carId); if (outerColors.isEmpty()) { - throw new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND); + throw new EmptyDataException(ErrorCode.DATA_NOT_EXISTS); } Optional totalCount = carRepository.findCarBoughtCountByCarId(carId); return outerColors.stream() @@ -56,7 +55,7 @@ public List findOuterColorByCarId(int carId) { public List findInnerColorByCarId(int carId) { List innerColors = colorRepository.findInnerColorCarByCarId(carId); if (innerColors.isEmpty()) { - throw new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND); + throw new EmptyDataException(ErrorCode.DATA_NOT_EXISTS); } Optional totalCount = carRepository.findCarBoughtCountByCarId(carId); return innerColors.stream() diff --git a/backend/src/main/java/autoever2/cartag/service/ModelService.java b/backend/src/main/java/autoever2/cartag/service/ModelService.java index d2aca4d..bc6bf3b 100644 --- a/backend/src/main/java/autoever2/cartag/service/ModelService.java +++ b/backend/src/main/java/autoever2/cartag/service/ModelService.java @@ -21,7 +21,7 @@ public class ModelService { public List getModelTypeData(int carId) { List modelData = modelRepository.findAllModelTypeData(carId); if (modelData.isEmpty()) { - throw new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND); + throw new EmptyDataException(ErrorCode.DATA_NOT_EXISTS); } Long carBoughtCount = carRepository.findCarBoughtCountByCarId(carId).orElse(0L); List powerTrainData = modelData.stream().filter(modelShortMappedDto -> modelShortMappedDto.getModelTypeId() == 1).collect(Collectors.toList()); @@ -114,7 +114,7 @@ private Double calculateHmgString(String input) { //TODO: RuntimeException 처리 public ModelDetailMappedDto getModelDetail(int modelId) { - return modelRepository.findModelDetailData(modelId).orElseThrow(() -> new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND)); + return modelRepository.findModelDetailData(modelId).orElseThrow(() -> new EmptyDataException(ErrorCode.DATA_NOT_EXISTS)); } //TODO: RuntimeException 처리 diff --git a/backend/src/main/java/autoever2/cartag/service/OptionService.java b/backend/src/main/java/autoever2/cartag/service/OptionService.java index 9b3240a..11d5520 100644 --- a/backend/src/main/java/autoever2/cartag/service/OptionService.java +++ b/backend/src/main/java/autoever2/cartag/service/OptionService.java @@ -57,7 +57,7 @@ public List getDefaultOptionList(int carId) { //TODO: RuntimeException 처리 public OptionDetailDto getOptionDetailData(int carId, int optionId, boolean isDefault) { - OptionDetailMappedDto detail = optionRepository.findOptionDetail(carId, optionId, isDefault).orElseThrow(() -> new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND)); + OptionDetailMappedDto detail = optionRepository.findOptionDetail(carId, optionId, isDefault).orElseThrow(() -> new EmptyDataException(ErrorCode.DATA_NOT_EXISTS)); List packageSubOptions = optionRepository.findPackageSubOptions(optionId); diff --git a/backend/src/main/java/autoever2/cartag/service/QuoteService.java b/backend/src/main/java/autoever2/cartag/service/QuoteService.java new file mode 100644 index 0000000..585fe5e --- /dev/null +++ b/backend/src/main/java/autoever2/cartag/service/QuoteService.java @@ -0,0 +1,51 @@ +package autoever2.cartag.service; + +import autoever2.cartag.domain.quote.HistorySearchDto; +import autoever2.cartag.domain.quote.HistoryShortDto; +import autoever2.cartag.domain.quote.QuoteDataDto; +import autoever2.cartag.exception.ErrorCode; +import autoever2.cartag.exception.InvalidDataException; +import autoever2.cartag.recommend.RecommendConnector; +import autoever2.cartag.repository.OptionRepository; +import autoever2.cartag.repository.QuoteRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class QuoteService { + + private final OptionRepository optionRepository; + private final QuoteRepository quoteRepository; + private final RecommendConnector recommendConnector; + + public List findTopHistory(QuoteDataDto quoteDataDto) { + + List optionIds = quoteDataDto.getOptionIdList(); + if(optionIds.size() != optionRepository.countExistOptions(quoteDataDto.getCarId(), optionIds)) { + throw new InvalidDataException(ErrorCode.INVALID_OPTIONS_REQUEST); + } + + List> result = recommendConnector.request(quoteDataDto); + + List historyList = result.stream().map(options -> { + HistorySearchDto historyData = HistorySearchDto.builder() + .carId(quoteDataDto.getCarId()) + .powerTrainId(quoteDataDto.getPowerTrainId()) + .bodyTypeId(quoteDataDto.getBodyTypeId()) + .operationId(quoteDataDto.getOperationId()) + .optionIds(optionIds) + .build(); + historyData.addAllOption(options); + + return historyData; + }).collect(Collectors.toList()); + + return historyList.stream().map(historySearchDto -> + quoteRepository.findShortData(historySearchDto).orElse(null) + ).collect(Collectors.toList()); + } +} diff --git a/backend/src/main/java/autoever2/cartag/service/RecommendService.java b/backend/src/main/java/autoever2/cartag/service/RecommendService.java deleted file mode 100644 index 3860e10..0000000 --- a/backend/src/main/java/autoever2/cartag/service/RecommendService.java +++ /dev/null @@ -1,59 +0,0 @@ -package autoever2.cartag.service; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.PropertySource; -import org.springframework.stereotype.Service; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; - -@Service -public class RecommendService { - - @Value("${python.url}") - private String requestURL; - - //TODO: 응답 존재 안할 시 예외처리 - public String getList() { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(requestURL)) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(getJsonFromEstimate())).build(); - - try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - String body = response.body(); - return body; - } catch (Exception e) { - e.getMessage(); - } - - return null; - } - - public String getJsonFromEstimate() { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("carId", 1); - jsonObject.put("powerTrain", 1); - jsonObject.put("bodyType", 3); - jsonObject.put("operation", 5); - - JSONArray jsonArray = new JSONArray(); - JSONObject subOption = new JSONObject(); - subOption.put("subOptionId", "69"); - jsonArray.add(subOption); - subOption = new JSONObject(); - subOption.put("subOptionId", "70"); - jsonArray.add(subOption); - - jsonObject.put("options", jsonArray); - - - return jsonObject.toJSONString(); - } -} diff --git a/backend/src/test/java/autoever2/cartag/controller/CarControllerTest.java b/backend/src/test/java/autoever2/cartag/controller/CarControllerTest.java index c79e94d..7ea364e 100644 --- a/backend/src/test/java/autoever2/cartag/controller/CarControllerTest.java +++ b/backend/src/test/java/autoever2/cartag/controller/CarControllerTest.java @@ -4,11 +4,10 @@ import autoever2.cartag.domain.car.CarTypeDto; import autoever2.cartag.domain.car.TrimDefaultOptionDto; import autoever2.cartag.domain.option.QuoteSubOptionDto; -import autoever2.cartag.domain.share.QuoteIdList; -import autoever2.cartag.domain.share.QuoteInfoDto; +import autoever2.cartag.domain.quote.QuoteDataDto; +import autoever2.cartag.domain.quote.QuoteInfoDto; import autoever2.cartag.service.CarService; -import org.json.JSONArray; -import org.json.JSONObject; +import autoever2.cartag.service.QuoteService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,10 +19,8 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import java.awt.image.AreaAveragingScaleFilter; import java.util.ArrayList; import java.util.List; -import java.util.Stack; import static org.mockito.BDDMockito.given; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -31,6 +28,7 @@ @WebMvcTest(CarController.class) class CarControllerTest { + @Autowired MockMvc mockMvc; @@ -39,8 +37,6 @@ class CarControllerTest { private List carDtoList; private List defaultOptions; - private List quoteSubOptionDtoList; - private QuoteInfoDto quoteInfoDto; @BeforeEach void setup() { @@ -112,70 +108,6 @@ void setup() { .build() ); - quoteSubOptionDtoList = new ArrayList<>(); - - quoteSubOptionDtoList.add(QuoteSubOptionDto - .builder() - .optionId(69) - .optionPrice(123400L) - .optionTitle("상세품목") - .optionImage("image_1.jpg") - .optionName("3열 열선시트") - .build()); - - quoteSubOptionDtoList.add(QuoteSubOptionDto - .builder() - .optionId(70) - .optionPrice(100L) - .optionTitle("휠") - .optionImage("iamge_2.jpg") - .optionName("20인치 다크 스퍼터링") - .build()); - - quoteSubOptionDtoList.add(QuoteSubOptionDto - .builder() - .optionId(71) - .optionPrice(8977L) - .optionTitle("악세사리") - .optionImage("image_3.jpg") - .optionName("후진가이드램프") - .build()); - - quoteInfoDto = QuoteInfoDto - .builder() - .carId(1) - .trim("Le Blanc") - .carDefaultPrice(40000000) - .powerTrainId(1) - .powerTrainTitle("파워트레인") - .powerTrainImage("image_1.jpg") - .powerTrainName("디젤2.2") - .powerTrainPrice(0L) - .bodyTypeId(5) - .bodyTypeTitle("바디타입") - .bodyTypeImage("image_2.jpg") - .bodyTypeName("7인승") - .bodyTypePrice(1500L) - .operationTitle("구동방식") - .operationId(3) - .operationImage("image_3.jpg") - .operationName("2WD") - .operationPrice(8900L) - .colorOuterTitle("외장색상") - .colorOuterId(4) - .colorOuterImage("red_1.jpg") - .colorOuterPrice(1500L) - .colorOuterImageName("퍼플 펄") - .colorCarOuterImage("outer_red.jpg") - .colorInnerTitle("내장색상") - .colorInnerId(1) - .colorInnerImage("blue_1.jpg") - .colorInnerPrice(1220L) - .colorInnerImageName("퀄팅 천연(파랑)") - .colorCarInnerImage("inner_red.jpg") - .optionList(quoteSubOptionDtoList) - .build(); - } @Test @@ -225,45 +157,4 @@ void getCarTypeList() throws Exception { .andExpect(jsonPath("$[1].carTypeImage").value("/cartype/santafe.png")) .andExpect(jsonPath("$[2].carTypeName").value("디 올 뉴 코나 Hybrid")); } - - @Test - @DisplayName("id에 따른 공유 데이터 반환") - void getShareInfo() throws Exception { - ArrayList optionIds = new ArrayList<>(); - optionIds.add(69); - optionIds.add(70); - optionIds.add(71); - QuoteIdList quoteIdList = QuoteIdList - .builder() - .carId(1) - .powerTrainId(1) - .operationId(3) - .bodyTypeId(5) - .innerColorId(1) - .outerColorId(4) - .optionIdList(optionIds) - .build(); - - given(service.findShareInfoDto(quoteIdList)).willReturn(quoteInfoDto); - - ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.post("/api/cars/infos/shares") - .content("{\n" + - " \"carId\": 1,\n" + - " \"powerTrainId\": 1,\n" + - " \"bodyTypeId\": 5,\n" + - " \"operationId\": 3,\n" + - " \"outerColorId\": 4,\n" + - " \"innerColorId\": 1,\n" + - " \"optionIdList\": [69, 70, 71]\n" + - "}").contentType(MediaType.APPLICATION_JSON)); - resultActions.andExpect(status().isOk()) - .andExpect(jsonPath("$.carDefaultPrice").value(40000000)) - .andExpect(jsonPath("$.trim").value("Le Blanc")) - .andExpect(jsonPath("$.powerTrainImage").value("image_1.jpg")) - .andExpect(jsonPath("$.bodyTypeName").value("7인승")) - .andExpect(jsonPath("$.operationTitle").value("구동방식")) - .andExpect(jsonPath("$.colorOuterPrice").value(1500L)) - .andExpect(jsonPath("$.colorCarInnerImage").value("inner_red.jpg")) - .andExpect(jsonPath("$.optionList.size()").value(3)); - } } \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/controller/ModelControllerTest.java b/backend/src/test/java/autoever2/cartag/controller/ModelControllerTest.java index 6d24ebf..da67b29 100644 --- a/backend/src/test/java/autoever2/cartag/controller/ModelControllerTest.java +++ b/backend/src/test/java/autoever2/cartag/controller/ModelControllerTest.java @@ -3,9 +3,7 @@ import autoever2.cartag.domain.model.ModelDetailMappedDto; import autoever2.cartag.domain.model.ModelEfficiencyDataDto; import autoever2.cartag.domain.model.ModelShortDataDto; -import autoever2.cartag.domain.model.PowerTrainMappedDto; import autoever2.cartag.service.ModelService; -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; diff --git a/backend/src/test/java/autoever2/cartag/controller/OptionControllerTest.java b/backend/src/test/java/autoever2/cartag/controller/OptionControllerTest.java index 43ad7c8..3fa8cb8 100644 --- a/backend/src/test/java/autoever2/cartag/controller/OptionControllerTest.java +++ b/backend/src/test/java/autoever2/cartag/controller/OptionControllerTest.java @@ -2,9 +2,6 @@ import autoever2.cartag.domain.option.*; import autoever2.cartag.service.OptionService; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Schema; -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; @@ -15,7 +12,6 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import static org.mockito.BDDMockito.given; diff --git a/backend/src/test/java/autoever2/cartag/controller/QuoteControllerTest.java b/backend/src/test/java/autoever2/cartag/controller/QuoteControllerTest.java new file mode 100644 index 0000000..7c3c285 --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/controller/QuoteControllerTest.java @@ -0,0 +1,191 @@ +package autoever2.cartag.controller; + +import autoever2.cartag.domain.option.QuoteSubOptionDto; +import autoever2.cartag.domain.quote.HistoryShortDto; +import autoever2.cartag.domain.quote.QuoteDataDto; +import autoever2.cartag.domain.quote.QuoteInfoDto; +import autoever2.cartag.service.CarService; +import autoever2.cartag.service.QuoteService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +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.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(QuoteController.class) +public class QuoteControllerTest { + + @Autowired + MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private QuoteService quoteService; + + @MockBean + private CarService carService; + + @Test + @DisplayName("유사견적 간략 데이터 제공 API 테스트") + void getRecommendedList() throws Exception { + //given + QuoteDataDto quoteDataDto = QuoteDataDto.builder() + .carId(1) + .powerTrainId(1) + .bodyTypeId(3) + .operationId(5) + .optionIdList(List.of(69)) + .build(); + + List expected = new ArrayList<>(); + expected.add(HistoryShortDto.builder() + .historyId(129L) + .soldCount(155) + .build()); + expected.add(HistoryShortDto.builder() + .historyId(161L) + .soldCount(140) + .build()); + expected.add(HistoryShortDto.builder() + .historyId(137L) + .soldCount(162) + .build()); + expected.add(HistoryShortDto.builder() + .historyId(169L) + .soldCount(140) + .build()); + + String content = objectMapper.writeValueAsString(quoteDataDto); + given(quoteService.findTopHistory(quoteDataDto)).willReturn(expected); + //when + ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.post("/api/quote/list").content(content) + .contentType(MediaType.APPLICATION_JSON)); + + //then + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$[0].historyId").value(129)) + .andExpect(jsonPath("$[1].soldCount").value(140)) + .andExpect(jsonPath("$[2].soldCount").value(162)) + .andExpect(jsonPath("$[3].historyId").value(169)); + } + @Test + @DisplayName("id에 따른 공유 데이터 반환") + void getShareInfo() throws Exception { + + List quoteSubOptionDtoList = new ArrayList<>(); + + quoteSubOptionDtoList.add(QuoteSubOptionDto + .builder() + .optionId(69) + .optionPrice(123400L) + .optionTitle("상세품목") + .optionImage("image_1.jpg") + .optionName("3열 열선시트") + .build()); + + quoteSubOptionDtoList.add(QuoteSubOptionDto + .builder() + .optionId(70) + .optionPrice(100L) + .optionTitle("휠") + .optionImage("iamge_2.jpg") + .optionName("20인치 다크 스퍼터링") + .build()); + + quoteSubOptionDtoList.add(QuoteSubOptionDto + .builder() + .optionId(71) + .optionPrice(8977L) + .optionTitle("악세사리") + .optionImage("image_3.jpg") + .optionName("후진가이드램프") + .build()); + + ArrayList optionIds = new ArrayList<>(); + optionIds.add(69); + optionIds.add(70); + optionIds.add(71); + QuoteDataDto quoteIdList = QuoteDataDto + .builder() + .carId(1) + .powerTrainId(1) + .operationId(3) + .bodyTypeId(5) + .innerColorId(1) + .outerColorId(4) + .optionIdList(optionIds) + .build(); + + QuoteInfoDto quoteInfoDto = QuoteInfoDto + .builder() + .carId(1) + .trim("Le Blanc") + .carDefaultPrice(40000000) + .powerTrainId(1) + .powerTrainTitle("파워트레인") + .powerTrainImage("image_1.jpg") + .powerTrainName("디젤2.2") + .powerTrainPrice(0L) + .bodyTypeId(5) + .bodyTypeTitle("바디타입") + .bodyTypeImage("image_2.jpg") + .bodyTypeName("7인승") + .bodyTypePrice(1500L) + .operationTitle("구동방식") + .operationId(3) + .operationImage("image_3.jpg") + .operationName("2WD") + .operationPrice(8900L) + .colorOuterTitle("외장색상") + .colorOuterId(4) + .colorOuterImage("red_1.jpg") + .colorOuterPrice(1500L) + .colorOuterImageName("퍼플 펄") + .colorCarOuterImage("outer_red.jpg") + .colorInnerTitle("내장색상") + .colorInnerId(1) + .colorInnerImage("blue_1.jpg") + .colorInnerPrice(1220L) + .colorInnerImageName("퀄팅 천연(파랑)") + .colorCarInnerImage("inner_red.jpg") + .optionList(quoteSubOptionDtoList) + .build(); + + + given(carService.findShareInfoDto(quoteIdList)).willReturn(quoteInfoDto); + + ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.post("/api/quote/infos/shares") + .content("{\n" + + " \"carId\": 1,\n" + + " \"powerTrainId\": 1,\n" + + " \"bodyTypeId\": 5,\n" + + " \"operationId\": 3,\n" + + " \"outerColorId\": 4,\n" + + " \"innerColorId\": 1,\n" + + " \"optionIdList\": [69, 70, 71]\n" + + "}").contentType(MediaType.APPLICATION_JSON)); + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$.carDefaultPrice").value(40000000)) + .andExpect(jsonPath("$.trim").value("Le Blanc")) + .andExpect(jsonPath("$.powerTrainImage").value("image_1.jpg")) + .andExpect(jsonPath("$.bodyTypeName").value("7인승")) + .andExpect(jsonPath("$.operationTitle").value("구동방식")) + .andExpect(jsonPath("$.colorOuterPrice").value(1500L)) + .andExpect(jsonPath("$.colorCarInnerImage").value("inner_red.jpg")) + .andExpect(jsonPath("$.optionList.size()").value(3)); + } +} diff --git a/backend/src/test/java/autoever2/cartag/integration/CarTest.java b/backend/src/test/java/autoever2/cartag/integration/CarTest.java index b44d145..70d1116 100644 --- a/backend/src/test/java/autoever2/cartag/integration/CarTest.java +++ b/backend/src/test/java/autoever2/cartag/integration/CarTest.java @@ -1,19 +1,9 @@ package autoever2.cartag.integration; import autoever2.cartag.controller.CarController; -import autoever2.cartag.domain.car.CarDefaultDto; import autoever2.cartag.domain.car.CarDto; import autoever2.cartag.domain.car.CarTypeDto; -import autoever2.cartag.domain.share.QuoteIdList; -import autoever2.cartag.domain.share.QuoteInfoDto; import autoever2.cartag.exception.EmptyDataException; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -21,8 +11,6 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; import java.util.ArrayList; import java.util.List; diff --git a/backend/src/test/java/autoever2/cartag/integration/ModelTest.java b/backend/src/test/java/autoever2/cartag/integration/ModelTest.java index 9eccd3e..313e187 100644 --- a/backend/src/test/java/autoever2/cartag/integration/ModelTest.java +++ b/backend/src/test/java/autoever2/cartag/integration/ModelTest.java @@ -41,6 +41,9 @@ void getModelList() { PowerTrainDataDto hmgData = trimModels.get(0).getHmgData(); assertEquals("45.0/1750~2750", hmgData.getMaxKgfm()); + assertEquals("202/3800", hmgData.getMaxPs()); + assertEquals(1.0, hmgData.getRatioKgfm()); + assertEquals(1.0, hmgData.getRatioPs()); assertEquals(1.0, hmgData.getRatioKgfm()); } diff --git a/backend/src/test/java/autoever2/cartag/integration/QuoteTest.java b/backend/src/test/java/autoever2/cartag/integration/QuoteTest.java index 50c84c0..3a03adf 100644 --- a/backend/src/test/java/autoever2/cartag/integration/QuoteTest.java +++ b/backend/src/test/java/autoever2/cartag/integration/QuoteTest.java @@ -1,15 +1,19 @@ package autoever2.cartag.integration; -import autoever2.cartag.controller.CarController; -import autoever2.cartag.domain.option.QuoteSubOptionDto; -import autoever2.cartag.domain.share.QuoteIdList; -import autoever2.cartag.domain.share.QuoteInfoDto; -import autoever2.cartag.exception.EmptyDataException; -import org.assertj.core.api.Assertions; +import autoever2.cartag.controller.QuoteController; +import autoever2.cartag.domain.quote.QuoteDataDto; +import autoever2.cartag.domain.quote.QuoteInfoDto; +import autoever2.cartag.recommend.RecommendConnector; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import autoever2.cartag.controller.CarController; +import autoever2.cartag.domain.option.QuoteSubOptionDto; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.transaction.annotation.Transactional; @@ -17,26 +21,28 @@ import java.util.ArrayList; import java.util.List; -import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; @SpringBootTest @Transactional @ActiveProfiles("test") -@Sql({"classpath:insert/insert-quote-h2.sql"}) public class QuoteTest { + + @Autowired + QuoteController quoteController; + @Autowired - CarController controller; + CarController carController; @Test - @DisplayName("/api/cars//infos/shares") + @DisplayName("/api/quote/infos/shares") + @Sql({"classpath:insert/insert-quote-h2.sql"}) void testShare() { ArrayList optionIds = new ArrayList<>(); optionIds.add(1); optionIds.add(2); optionIds.add(3); - QuoteIdList quoteIdList = QuoteIdList + QuoteDataDto quoteIdList = QuoteDataDto .builder() .carId(1) .powerTrainId(1) @@ -47,7 +53,7 @@ void testShare() { .optionIdList(optionIds) .build(); - QuoteInfoDto quoteInfoDto = controller.boughtCarDtos(quoteIdList); + QuoteInfoDto quoteInfoDto = quoteController.getQuoteDetail(quoteIdList); assertEquals("르블랑", quoteInfoDto.getTrim()); assertEquals(41980000, quoteInfoDto.getCarDefaultPrice()); diff --git a/backend/src/test/java/autoever2/cartag/repository/OptionRepositoryTest.java b/backend/src/test/java/autoever2/cartag/repository/OptionRepositoryTest.java index 9080aca..5883f4e 100644 --- a/backend/src/test/java/autoever2/cartag/repository/OptionRepositoryTest.java +++ b/backend/src/test/java/autoever2/cartag/repository/OptionRepositoryTest.java @@ -247,4 +247,13 @@ void getOptionInfo(){ Optional subOptionInfoV2 = optionRepository.findSubOptionByOptionId(100); assertTrue(subOptionInfoV2.isEmpty()); } + + @Test + @DisplayName("옵션이 실제로 존재하는지 검증") + void countExistOptions() { + int carId = 1; + List optionIds = List.of(69); + + assertEquals(1, optionRepository.countExistOptions(carId, optionIds)); + } } \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/repository/QuoteRepositoryTest.java b/backend/src/test/java/autoever2/cartag/repository/QuoteRepositoryTest.java new file mode 100644 index 0000000..e2e4ae9 --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/repository/QuoteRepositoryTest.java @@ -0,0 +1,68 @@ +package autoever2.cartag.repository; + +import autoever2.cartag.domain.quote.HistorySearchDto; +import autoever2.cartag.domain.quote.HistoryShortDto; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +import javax.sql.DataSource; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ActiveProfiles("test") +@JdbcTest +@Sql({"classpath:insert/insert-sales-h2.sql"}) +@ExtendWith(SoftAssertionsExtension.class) +class QuoteRepositoryTest { + + @InjectSoftAssertions + SoftAssertions softAssertions; + + private final QuoteRepository quoteRepository; + + @Autowired + public QuoteRepositoryTest(DataSource dataSource) { + quoteRepository = new QuoteRepository(dataSource); + } + + @Test + void findShortData() { + HistorySearchDto search1 = HistorySearchDto.builder() + .carId(1) + .powerTrainId(1) + .bodyTypeId(3) + .operationId(5) + .optionIds(List.of(69, 70)) + .build(); + HistorySearchDto search2 = HistorySearchDto.builder() + .carId(1) + .powerTrainId(1) + .bodyTypeId(3) + .operationId(5) + .optionIds(List.of(69, 74)) + .build(); + + HistoryShortDto expected1 = HistoryShortDto.builder() + .historyId(129L) + .soldCount(155) + .build(); + HistoryShortDto expected2 = HistoryShortDto.builder() + .historyId(161L) + .soldCount(140) + .build(); + + assertTrue(quoteRepository.findShortData(search1).isPresent()); + assertTrue(quoteRepository.findShortData(search2).isPresent()); + softAssertions.assertThat(quoteRepository.findShortData(search1).get()).usingRecursiveComparison().isEqualTo(expected1); + softAssertions.assertThat(quoteRepository.findShortData(search2).get()).usingRecursiveComparison().isEqualTo(expected2); + } +} \ No newline at end of file diff --git a/backend/src/test/java/autoever2/cartag/service/CarServiceTest.java b/backend/src/test/java/autoever2/cartag/service/CarServiceTest.java index 23005db..7ee721d 100644 --- a/backend/src/test/java/autoever2/cartag/service/CarServiceTest.java +++ b/backend/src/test/java/autoever2/cartag/service/CarServiceTest.java @@ -5,8 +5,8 @@ import autoever2.cartag.domain.color.OuterColorDto; import autoever2.cartag.domain.model.ModelDefaultDto; import autoever2.cartag.domain.option.QuoteSubOptionDto; -import autoever2.cartag.domain.share.QuoteIdList; -import autoever2.cartag.domain.share.QuoteInfoDto; +import autoever2.cartag.domain.quote.QuoteDataDto; +import autoever2.cartag.domain.quote.QuoteInfoDto; import autoever2.cartag.exception.EmptyDataException; import autoever2.cartag.exception.ErrorCode; import autoever2.cartag.repository.CarRepository; @@ -129,7 +129,7 @@ void getCarType() { when(carRepository.findCarByCarType(carType)).thenReturn(carInfoDtoList); when(optionRepository.findDefaultOptionByCarId(carId)).thenReturn(trimDefaultOptionDtoList); - when(carRepository.findCarByCarType(2)).thenThrow(new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND)); + when(carRepository.findCarByCarType(2)).thenThrow(new EmptyDataException(ErrorCode.DATA_NOT_EXISTS)); List carByCarType = carService.findCarByCarType(carType); @@ -236,7 +236,7 @@ void integrateAllInfo() { when(optionRepository.findSubOptionByOptionId(1)).thenReturn(Optional.of(subOption)); - QuoteInfoDto shareInfoDto = carService.findShareInfoDto(QuoteIdList.builder() + QuoteInfoDto shareInfoDto = carService.findShareInfoDto(QuoteDataDto.builder() .carId(1) .powerTrainId(1) .bodyTypeId(3) diff --git a/backend/src/test/java/autoever2/cartag/service/ColorServiceTest.java b/backend/src/test/java/autoever2/cartag/service/ColorServiceTest.java index 3135bf6..95dd477 100644 --- a/backend/src/test/java/autoever2/cartag/service/ColorServiceTest.java +++ b/backend/src/test/java/autoever2/cartag/service/ColorServiceTest.java @@ -138,9 +138,9 @@ void getModelTypeData() { when(colorRepository.findOuterColorCarByCarId(carId)).thenReturn(outerColors); when(colorRepository.findOuterColorImagesByColorId(colorId)).thenReturn(Optional.of("red_image_*.jpg")); when(carRepository.findCarBoughtCountByCarId(carId)).thenReturn(Optional.of(10000L)); - when(colorRepository.findOuterColorImagesByColorId(2)).thenThrow(new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND)); - when(colorRepository.findOuterColorCarByCarId(2)).thenThrow(new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND)); - when(colorRepository.findInnerColorCarByCarId(2)).thenThrow(new EmptyDataException(ErrorCode.RESOURCE_NOT_FOUND)); + when(colorRepository.findOuterColorImagesByColorId(2)).thenThrow(new EmptyDataException(ErrorCode.DATA_NOT_EXISTS)); + when(colorRepository.findOuterColorCarByCarId(2)).thenThrow(new EmptyDataException(ErrorCode.DATA_NOT_EXISTS)); + when(colorRepository.findInnerColorCarByCarId(2)).thenThrow(new EmptyDataException(ErrorCode.DATA_NOT_EXISTS)); //when diff --git a/backend/src/test/java/autoever2/cartag/service/QuoteServiceTest.java b/backend/src/test/java/autoever2/cartag/service/QuoteServiceTest.java new file mode 100644 index 0000000..433b65d --- /dev/null +++ b/backend/src/test/java/autoever2/cartag/service/QuoteServiceTest.java @@ -0,0 +1,132 @@ +package autoever2.cartag.service; + +import autoever2.cartag.domain.quote.HistorySearchDto; +import autoever2.cartag.domain.quote.HistoryShortDto; +import autoever2.cartag.domain.quote.QuoteDataDto; +import autoever2.cartag.recommend.RecommendConnector; +import autoever2.cartag.repository.OptionRepository; +import autoever2.cartag.repository.QuoteRepository; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.mockito.Mockito.when; + +@ExtendWith({MockitoExtension.class, SoftAssertionsExtension.class}) +class QuoteServiceTest { + + @InjectSoftAssertions + private SoftAssertions softAssertions; + + @InjectMocks + private QuoteService quoteService; + + @Mock + private OptionRepository optionRepository; + + @Mock + private QuoteRepository quoteRepository; + + @Mock + private RecommendConnector recommendConnector; + + @Test + void findTopHistory() { + //given + QuoteDataDto quoteDataDto = QuoteDataDto.builder() + .carId(1) + .powerTrainId(1) + .bodyTypeId(3) + .operationId(5) + .optionIdList(List.of(69)) + .build(); + + List> response = new ArrayList<>(); + response.add(List.of(70)); + response.add(List.of(74)); + response.add(List.of(71)); + response.add(List.of(84)); + + List expected = new ArrayList<>(); + expected.add(HistoryShortDto.builder() + .historyId(129L) + .soldCount(155) + .build()); + expected.add(HistoryShortDto.builder() + .historyId(161L) + .soldCount(140) + .build()); + expected.add(HistoryShortDto.builder() + .historyId(137L) + .soldCount(162) + .build()); + expected.add(HistoryShortDto.builder() + .historyId(169L) + .soldCount(140) + .build()); + + + when(optionRepository.countExistOptions(quoteDataDto.getCarId(), quoteDataDto.getOptionIdList())).thenReturn(1L); + when(recommendConnector.request(quoteDataDto)).thenReturn(response); + + when(quoteRepository.findShortData(HistorySearchDto.builder() + .carId(1) + .powerTrainId(1) + .bodyTypeId(3) + .operationId(5) + .optionIds(List.of(69, 70)) + .build())) + .thenReturn(Optional.of(HistoryShortDto.builder() + .historyId(129L) + .soldCount(155) + .build())); + when(quoteRepository.findShortData(HistorySearchDto.builder() + .carId(1) + .powerTrainId(1) + .bodyTypeId(3) + .operationId(5) + .optionIds(List.of(69, 74)) + .build())) + .thenReturn(Optional.of(HistoryShortDto.builder() + .historyId(161L) + .soldCount(140) + .build())); + when(quoteRepository.findShortData(HistorySearchDto.builder() + .carId(1) + .powerTrainId(1) + .bodyTypeId(3) + .operationId(5) + .optionIds(List.of(69, 71)) + .build())) + .thenReturn(Optional.of(HistoryShortDto.builder() + .historyId(137L) + .soldCount(162) + .build())); + when(quoteRepository.findShortData(HistorySearchDto.builder() + .carId(1) + .powerTrainId(1) + .bodyTypeId(3) + .operationId(5) + .optionIds(List.of(69, 84)) + .build())) + .thenReturn(Optional.of(HistoryShortDto.builder() + .historyId(169L) + .soldCount(140) + .build())); + + //when + List result = quoteService.findTopHistory(quoteDataDto); + + //then + softAssertions.assertThat(result).usingRecursiveComparison().isEqualTo(expected); + } +} \ No newline at end of file diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml deleted file mode 100644 index a3374c9..0000000 --- a/backend/src/test/resources/application.yml +++ /dev/null @@ -1,19 +0,0 @@ -spring: - config: - activate: - on-profile: test - h2: - console: - enabled: true - datasource: - url: jdbc:h2:mem:test;MODE=MySQL; - username: sa - password: - driver-class-name: org.h2.Driver - sql: - init: - mode: always - schema-locations: classpath:schema-h2.sql - -python: - url: http://localhost:5001/test diff --git a/backend/src/test/resources/insert/insert-sales-h2.sql b/backend/src/test/resources/insert/insert-sales-h2.sql new file mode 100644 index 0000000..d889878 --- /dev/null +++ b/backend/src/test/resources/insert/insert-sales-h2.sql @@ -0,0 +1,177 @@ +SET REFERENTIAL_INTEGRITY FALSE; + +INSERT INTO SubOptionData (sub_option_data_id, car_id, option_id, option_bought_count, option_price) VALUES (1, 1, 69, 48015, 1090000); +INSERT INTO SubOptionData (sub_option_data_id, car_id, option_id, option_bought_count, option_price) VALUES (2, 1, 70, 121380, 790000); + +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (129, 1, 155, '69,70'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (130, 1, 165, '69,70'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (131, 1, 152, '69,70'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (132, 1, 139, '69,70'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (133, 1, 135, '69,70'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (134, 1, 164, '69,70'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (135, 1, 153, '69,70'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (136, 1, 143, '69,70'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (137, 1, 162, '69,71'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (138, 1, 148, '69,71'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (139, 1, 138, '69,71'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (140, 1, 159, '69,71'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (141, 1, 156, '69,71'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (142, 1, 150, '69,71'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (143, 1, 154, '69,71'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (144, 1, 145, '69,71'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (145, 1, 144, '69,72'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (146, 1, 157, '69,72'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (147, 1, 150, '69,72'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (148, 1, 162, '69,72'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (149, 1, 145, '69,72'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (150, 1, 155, '69,72'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (151, 1, 162, '69,72'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (152, 1, 158, '69,72'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (153, 1, 149, '69,73'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (154, 1, 145, '69,73'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (155, 1, 150, '69,73'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (156, 1, 164, '69,73'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (157, 1, 163, '69,73'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (158, 1, 152, '69,73'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (159, 1, 143, '69,73'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (160, 1, 142, '69,73'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (161, 1, 140, '69,74'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (162, 1, 138, '69,74'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (163, 1, 151, '69,74'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (164, 1, 159, '69,74'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (165, 1, 144, '69,74'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (166, 1, 139, '69,74'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (167, 1, 144, '69,74'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (168, 1, 149, '69,74'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (169, 1, 140, '69,84'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (170, 1, 150, '69,84'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (171, 1, 152, '69,84'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (172, 1, 143, '69,84'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (173, 1, 162, '69,84'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (174, 1, 151, '69,84'); +INSERT INTO SalesHistory (history_id, car_id, sold_count, sold_options_id) VALUES (175, 1, 149, '69,84'); + + +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (384, 1, 129); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (385, 3, 129); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (386, 5, 129); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (387, 1, 130); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (388, 3, 130); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (389, 6, 130); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (390, 1, 131); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (391, 4, 131); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (392, 5, 131); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (393, 1, 132); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (394, 4, 132); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (395, 6, 132); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (396, 2, 133); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (397, 3, 133); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (398, 5, 133); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (399, 2, 134); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (400, 3, 134); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (401, 6, 134); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (402, 2, 135); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (403, 4, 135); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (404, 5, 135); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (405, 2, 136); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (406, 4, 136); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (407, 6, 136); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (408, 1, 137); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (409, 3, 137); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (410, 5, 137); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (411, 1, 138); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (412, 3, 138); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (413, 6, 138); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (414, 1, 139); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (415, 4, 139); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (416, 5, 139); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (417, 1, 140); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (418, 4, 140); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (419, 6, 140); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (420, 2, 141); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (421, 3, 141); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (422, 5, 141); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (423, 2, 142); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (424, 3, 142); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (425, 6, 142); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (426, 2, 143); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (427, 4, 143); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (428, 5, 143); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (429, 2, 144); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (430, 4, 144); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (431, 6, 144); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (432, 1, 145); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (433, 3, 145); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (434, 5, 145); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (435, 1, 146); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (436, 3, 146); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (437, 6, 146); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (438, 1, 147); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (439, 4, 147); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (440, 5, 147); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (441, 1, 148); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (442, 4, 148); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (443, 6, 148); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (444, 2, 149); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (445, 3, 149); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (446, 5, 149); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (447, 2, 150); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (448, 3, 150); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (449, 6, 150); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (450, 2, 151); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (451, 4, 151); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (452, 5, 151); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (453, 2, 152); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (454, 4, 152); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (455, 6, 152); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (456, 1, 153); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (457, 3, 153); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (458, 5, 153); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (459, 1, 154); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (460, 3, 154); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (461, 6, 154); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (462, 1, 155); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (463, 4, 155); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (464, 5, 155); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (465, 1, 156); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (466, 4, 156); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (467, 6, 156); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (468, 2, 157); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (469, 3, 157); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (470, 5, 157); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (471, 2, 158); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (472, 3, 158); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (473, 6, 158); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (474, 2, 159); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (475, 4, 159); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (476, 5, 159); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (477, 2, 160); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (478, 4, 160); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (479, 6, 160); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (480, 1, 161); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (481, 3, 161); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (482, 5, 161); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (483, 1, 162); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (484, 3, 162); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (485, 6, 162); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (486, 1, 163); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (487, 4, 163); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (488, 5, 163); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (489, 1, 164); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (490, 4, 164); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (491, 6, 164); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (492, 2, 165); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (493, 3, 165); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (494, 5, 165); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (495, 2, 166); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (496, 3, 166); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (497, 6, 166); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (498, 2, 167); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (499, 4, 167); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (500, 5, 167); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (501, 2, 168); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (502, 4, 168); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (503, 6, 168); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (504, 1, 169); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (505, 3, 169); +INSERT INTO HistoryModelMapper (history_model_mapper_id, model_id, history_id) VALUES (506, 5, 169);