Merge pull request #33 from Development-team-1/고객_검색
Just-Pickup 매장 검색 페이지 및 로직 구현
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.26.0",
|
||||
"core-js": "^3.8.3",
|
||||
"vue": "^2.6.14",
|
||||
"vue-router": "^3.5.3",
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
requestSearchStore(latitude, longitude, storeName, page) {
|
||||
const options = {
|
||||
params: {
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
storeName: storeName,
|
||||
page: page
|
||||
}
|
||||
}
|
||||
return axios.get("http://localhost:8000/store-service/search-store", options);
|
||||
},
|
||||
getCategoryList(){
|
||||
return axios.get(process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+'/store-service/category/');
|
||||
},
|
||||
|
||||
@@ -23,6 +23,11 @@ const routes = [
|
||||
path: "/home",
|
||||
name: 'home',
|
||||
component: () => import('../views/HomeView')
|
||||
},
|
||||
{
|
||||
path: "/search",
|
||||
name: 'search-store',
|
||||
component: () => import('../views/SearchStore')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<app-navigation></app-navigation>
|
||||
<v-main style="background-color: #f2f2f2!important;">
|
||||
<v-container class="px-8 py-8" style="background-color: white; height: 100%">
|
||||
<v-main>
|
||||
<v-container class="px-8 py-8">
|
||||
<router-view></router-view>
|
||||
</v-container>
|
||||
</v-main>
|
||||
@@ -25,6 +25,11 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
max-width: 960px;
|
||||
max-width: 768px;
|
||||
background-color: white;
|
||||
height: 100%;
|
||||
}
|
||||
main {
|
||||
background-color: #f2f2f2!important;
|
||||
}
|
||||
</style>
|
||||
147
customer-vue/src/views/SearchStore.vue
Normal file
147
customer-vue/src/views/SearchStore.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-row>
|
||||
<v-text-field
|
||||
v-model="storeName"
|
||||
@keyup.enter="search"
|
||||
@click:append="search"
|
||||
append-icon="mdi-magnify"
|
||||
label="지역, 가게명으로 찾아보세요"
|
||||
filled
|
||||
>
|
||||
</v-text-field>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-alert
|
||||
border="left"
|
||||
colored-border
|
||||
color="deep-purple accent-4"
|
||||
elevation="2"
|
||||
dense
|
||||
>
|
||||
거리 순으로 가까운 매장을 찾아왔어요 😁
|
||||
</v-alert>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col v-for="card in cards" v-bind:key="card.storeId" sm="6">
|
||||
<v-card v-bind:data-id="card.storeId">
|
||||
<v-img
|
||||
height="180"
|
||||
src="https://cdn.vuetifyjs.com/images/cards/cooking.png"
|
||||
></v-img>
|
||||
<v-card-title>{{ card.name }}</v-card-title>
|
||||
<v-card-text>
|
||||
거리 : {{ card.distance }}
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
</v-row>
|
||||
|
||||
<br><br>
|
||||
<v-row justify="center" v-if="hasNext">
|
||||
<v-btn @click="more"
|
||||
elevation="2"
|
||||
color="blue-grey"
|
||||
class="ma-2 white--text"
|
||||
>
|
||||
<b>더보기</b>
|
||||
<v-icon>mdi-chevron-down</v-icon>
|
||||
</v-btn>
|
||||
</v-row>
|
||||
|
||||
<v-overlay :value="isLoading">
|
||||
<v-progress-circular
|
||||
indeterminate
|
||||
size="64"
|
||||
></v-progress-circular>
|
||||
</v-overlay>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import storeApi from "../api/store.js";
|
||||
|
||||
export default {
|
||||
name: "SearchStore",
|
||||
async mounted() {
|
||||
const location = await this.getLocation();
|
||||
|
||||
this.latitude = location.latitude;
|
||||
this.longitude = location.longitude;
|
||||
|
||||
await this.search();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
isLoading: true,
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
storeName: "",
|
||||
page: 0,
|
||||
cards: [],
|
||||
hasNext: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getLocation: async function() {
|
||||
console.log("initGeoLocation");
|
||||
return new Promise(function (resolve, reject) {
|
||||
if ('geolocation' in navigator) {
|
||||
navigator.geolocation.getCurrentPosition((position) => {
|
||||
const coords = position.coords;
|
||||
|
||||
resolve({
|
||||
latitude: coords.latitude,
|
||||
longitude: coords.longitude
|
||||
})
|
||||
})
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
},
|
||||
search: async function() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
this.page = 0;
|
||||
const response = await storeApi.requestSearchStore(this.latitude, this.longitude, this.storeName, this.page);
|
||||
this.cards = [];
|
||||
this.renderCard(response.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
more: async function() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
this.page += 1;
|
||||
const response = await storeApi.requestSearchStore(this.latitude, this.longitude, this.storeName, this.page);
|
||||
this.renderCard(response.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
renderCard: function(json) {
|
||||
this.hasNext = json.data.hasNext;
|
||||
const stores = json.data.stores;
|
||||
|
||||
stores.forEach( (store) => {
|
||||
this.cards.push({
|
||||
storeId: store.storeId,
|
||||
name: store.name,
|
||||
distance: store.distance
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -64,17 +64,21 @@
|
||||
== snippets 작성 컨벤션
|
||||
domain-httpRequestCode-etc
|
||||
|
||||
== 주문
|
||||
=== 점주 서비스 - 카테고리 페이지
|
||||
- 페이지 offset : 6
|
||||
== 상품
|
||||
=== 상품 조회
|
||||
operation::item-get[snippets='curl-request,http-request,http-response,path-parameters,response-fields']
|
||||
=== 상품 조회 (존재하지 않는 상품)
|
||||
operation::item-get-notExistItemException[snippets='curl-request,http-request,http-response,path-parameters,response-fields']
|
||||
|
||||
== 카테고리
|
||||
== 카테고리 조회
|
||||
|
||||
operation::get-categoryList[snippets='curl-request,http-request,http-response,request-parameters,response-fields']
|
||||
|
||||
---
|
||||
|
||||
== 카테고리 수정
|
||||
operation::put-categoryList[snippets='curl-request,http-request,http-response,request-body,request-fields']
|
||||
|
||||
== 매장
|
||||
=== 매장 검색 조회
|
||||
operation::searchStore-get[snippets='curl-request,http-request,http-response,request-parameters,response-fields']
|
||||
|
||||
operation::put-categoryList[snippets='curl-request,http-request,http-response,request-parameters,response-fields']
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
package com.justpickup.storeservice;
|
||||
|
||||
import com.justpickup.storeservice.domain.map.entity.Map;
|
||||
import com.justpickup.storeservice.domain.store.entity.Store;
|
||||
import com.justpickup.storeservice.domain.store.repository.StoreRepository;
|
||||
import com.justpickup.storeservice.global.entity.Address;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableEurekaClient
|
||||
@@ -12,4 +21,48 @@ public class StoreServiceApplication {
|
||||
SpringApplication.run(StoreServiceApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
CommandLineRunner run(StoreRepository storeRepository) {
|
||||
return args -> {
|
||||
List<Store> stores = new ArrayList<>();
|
||||
|
||||
stores.add(
|
||||
Store.of(
|
||||
new Address("서울시", "마포구 도화동", "201-20"),
|
||||
Map.of(37.5398271003404, 126.94769672415691),
|
||||
1L,
|
||||
"커피온리 마포역점"
|
||||
)
|
||||
);
|
||||
|
||||
stores.add(
|
||||
Store.of(
|
||||
new Address("서울시", "마포구 도화동", "50-10"),
|
||||
Map.of(37.54010719003089, 126.94556661330861),
|
||||
2L,
|
||||
"만랩커피 마포점"
|
||||
)
|
||||
);
|
||||
|
||||
stores.add(
|
||||
Store.of(
|
||||
new Address("서울시", "마포구 도화동", "555"),
|
||||
Map.of(37.539797393793755, 126.9453578838543),
|
||||
3L,
|
||||
"이디야커피 마포오벨리스크점"
|
||||
)
|
||||
);
|
||||
|
||||
stores.add(
|
||||
Store.of(
|
||||
new Address("서울시", "영등포구 도림로", "31길 2"),
|
||||
Map.of(37.493033141569505, 126.89593667847592),
|
||||
4L,
|
||||
"이디야커피 대림역점"
|
||||
)
|
||||
);
|
||||
|
||||
storeRepository.saveAll(stores);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,15 @@ public class Map extends BaseEntity {
|
||||
@Id @GeneratedValue
|
||||
@Column(name = "map_id")
|
||||
private Long id;
|
||||
|
||||
private Double latitude;
|
||||
|
||||
private Double longitude;
|
||||
|
||||
public static Map of (double latitude, double longitude) {
|
||||
Map map = new Map();
|
||||
map.latitude = latitude;
|
||||
map.longitude = longitude;
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.justpickup.storeservice.domain.store.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Getter @AllArgsConstructor
|
||||
public class SearchStoreCondition {
|
||||
@NotNull(message = "필수 값입니다.")
|
||||
private double latitude;
|
||||
@NotNull(message = "필수 값입니다.")
|
||||
private double longitude;
|
||||
private String storeName;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.justpickup.storeservice.domain.store.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
@Getter @AllArgsConstructor
|
||||
public class SearchStoreResult {
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
private Double distanceMeter;
|
||||
|
||||
public String convertDistanceToString() {
|
||||
// km 으로 표시
|
||||
if (distanceMeter >= 1000) {
|
||||
double km = distanceMeter * 0.001;
|
||||
String format = new DecimalFormat("0.0").format(km);
|
||||
// ex) 1.7km
|
||||
return format + "km";
|
||||
}
|
||||
|
||||
// ex) 621m
|
||||
return new DecimalFormat("0").format(distanceMeter) + "m";
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ public class Store extends BaseEntity {
|
||||
|
||||
private LocalDateTime businessEndTime;
|
||||
|
||||
private String name;
|
||||
|
||||
private String phoneNumber;
|
||||
|
||||
@Embedded
|
||||
@@ -37,7 +39,7 @@ public class Store extends BaseEntity {
|
||||
@Embedded
|
||||
private Photo photo;
|
||||
|
||||
@OneToOne(fetch = LAZY)
|
||||
@OneToOne(fetch = LAZY, cascade = CascadeType.ALL)
|
||||
@JoinColumn(name = "map_id")
|
||||
private Map map;
|
||||
|
||||
@@ -60,4 +62,13 @@ public class Store extends BaseEntity {
|
||||
items.add(item);
|
||||
item.setStore(this);
|
||||
}
|
||||
|
||||
public static Store of(Address address, Map map, Long userId, String name) {
|
||||
Store store = new Store();
|
||||
store.address = address;
|
||||
store.map = map;
|
||||
store.userId = userId;
|
||||
store.name = name;
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.justpickup.storeservice.domain.store.repository;
|
||||
|
||||
import com.justpickup.storeservice.domain.store.dto.SearchStoreCondition;
|
||||
import com.justpickup.storeservice.domain.store.dto.SearchStoreResult;
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.core.types.dsl.Expressions;
|
||||
import com.querydsl.core.types.dsl.NumberExpression;
|
||||
import com.querydsl.core.types.dsl.NumberPath;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.SliceImpl;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.justpickup.storeservice.domain.store.entity.QStore.store;
|
||||
import static com.querydsl.core.types.dsl.MathExpressions.*;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class StoreRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
|
||||
public SliceImpl<SearchStoreResult> findSearchStoreScroll(SearchStoreCondition condition, Pageable pageable) {
|
||||
Expression<Double> latitude = Expressions.constant(condition.getLatitude());
|
||||
Expression<Double> longitude = Expressions.constant(condition.getLongitude());
|
||||
|
||||
NumberPath<Double> distanceAlias = Expressions.numberPath(Double.class, "distance");
|
||||
int earthRadius = 6371;
|
||||
|
||||
NumberExpression<Double> haversineDistance = acos(
|
||||
cos(radians(latitude))
|
||||
.multiply(cos(radians(store.map.latitude)))
|
||||
.multiply(
|
||||
cos(radians(store.map.longitude)
|
||||
.subtract(radians(longitude)))
|
||||
)
|
||||
.add(
|
||||
sin(radians(latitude))
|
||||
.multiply(sin(radians(store.map.latitude)))
|
||||
)
|
||||
)
|
||||
.multiply(Expressions.constant(earthRadius * 1000));
|
||||
|
||||
List<SearchStoreResult> content = queryFactory.select(
|
||||
Projections.constructor(SearchStoreResult.class,
|
||||
store.id,
|
||||
store.name,
|
||||
haversineDistance.as(distanceAlias))
|
||||
)
|
||||
.from(store)
|
||||
.join(store.map)
|
||||
.where(
|
||||
storeNameContains(condition.getStoreName())
|
||||
)
|
||||
.orderBy(distanceAlias.asc())
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize() + 1)
|
||||
.fetch();
|
||||
|
||||
boolean hasNext = false;
|
||||
if (content.size() > pageable.getPageSize()) {
|
||||
content.remove(pageable.getPageSize());
|
||||
hasNext = true;
|
||||
}
|
||||
|
||||
return new SliceImpl<>(content, pageable, hasNext);
|
||||
}
|
||||
|
||||
private BooleanExpression storeNameContains(String storeName) {
|
||||
return storeName == null ? null : store.name.contains(storeName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.justpickup.storeservice.domain.store.service;
|
||||
|
||||
import com.justpickup.storeservice.domain.store.dto.SearchStoreCondition;
|
||||
import com.justpickup.storeservice.domain.store.dto.SearchStoreResult;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.SliceImpl;
|
||||
|
||||
public interface StoreService {
|
||||
SliceImpl<SearchStoreResult> findSearchStoreScroll(SearchStoreCondition condition, Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.justpickup.storeservice.domain.store.service;
|
||||
|
||||
import com.justpickup.storeservice.domain.store.dto.SearchStoreCondition;
|
||||
import com.justpickup.storeservice.domain.store.dto.SearchStoreResult;
|
||||
import com.justpickup.storeservice.domain.store.repository.StoreRepositoryCustom;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.SliceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class StoreServiceImpl implements StoreService {
|
||||
|
||||
private final StoreRepositoryCustom storeRepositoryCustom;
|
||||
|
||||
@Override
|
||||
public SliceImpl<SearchStoreResult> findSearchStoreScroll(SearchStoreCondition condition, Pageable pageable) {
|
||||
return storeRepositoryCustom.findSearchStoreScroll(condition, pageable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.justpickup.storeservice.domain.store.web;
|
||||
|
||||
import com.justpickup.storeservice.domain.store.dto.SearchStoreCondition;
|
||||
import com.justpickup.storeservice.domain.store.dto.SearchStoreResult;
|
||||
import com.justpickup.storeservice.domain.store.service.StoreService;
|
||||
import com.justpickup.storeservice.global.dto.Result;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.SliceImpl;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class StoreController {
|
||||
|
||||
private final StoreService storeService;
|
||||
|
||||
@GetMapping("/search-store")
|
||||
public ResponseEntity<Result> searchStore(@Valid SearchStoreCondition condition,
|
||||
@PageableDefault(page = 0, size = 2) Pageable pageable) {
|
||||
SliceImpl<SearchStoreResult> searchStoreScroll = storeService.findSearchStoreScroll(condition, pageable);
|
||||
|
||||
SearchStoreResponse searchStoreResponse =
|
||||
new SearchStoreResponse(searchStoreScroll.getContent(), searchStoreScroll.hasNext());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.OK)
|
||||
.body(Result.createSuccessResult(searchStoreResponse));
|
||||
}
|
||||
|
||||
@Data @NoArgsConstructor
|
||||
static class SearchStoreResponse {
|
||||
private List<StoreDto> stores;
|
||||
private boolean hasNext;
|
||||
|
||||
@Data @AllArgsConstructor
|
||||
static class StoreDto {
|
||||
private Long id;
|
||||
private String name;
|
||||
private String distance;
|
||||
}
|
||||
|
||||
public SearchStoreResponse(List<SearchStoreResult> content, boolean hasNext) {
|
||||
this.stores = content.stream()
|
||||
.map(result ->
|
||||
new StoreDto(
|
||||
result.getStoreId(), result.getStoreName(), result.convertDistanceToString())
|
||||
)
|
||||
.collect(Collectors.toList());
|
||||
this.hasNext = hasNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.justpickup.storeservice.domain.store.web;
|
||||
|
||||
import com.justpickup.storeservice.config.TestConfig;
|
||||
import com.justpickup.storeservice.domain.store.dto.SearchStoreCondition;
|
||||
import com.justpickup.storeservice.domain.store.dto.SearchStoreResult;
|
||||
import com.justpickup.storeservice.domain.store.repository.StoreRepository;
|
||||
import com.justpickup.storeservice.domain.store.service.StoreService;
|
||||
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.restdocs.AutoConfigureRestDocs;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.SliceImpl;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
|
||||
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@WebMvcTest(StoreController.class)
|
||||
@Import(TestConfig.class)
|
||||
@AutoConfigureRestDocs(uriHost = "127.0.0.1", uriPort = 8000)
|
||||
class StoreControllerTest {
|
||||
|
||||
@Autowired
|
||||
MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
StoreService storeService;
|
||||
|
||||
@MockBean
|
||||
StoreRepository storeRepository;
|
||||
|
||||
@Test
|
||||
@DisplayName("[API] [GET] 매장 검색 페이지 조회")
|
||||
void getSearchStore() throws Exception {
|
||||
// GIVEN
|
||||
double latitude = 37.5403912;
|
||||
double longitude = 126.9438922;
|
||||
SearchStoreCondition condition = new SearchStoreCondition(latitude, longitude, null);
|
||||
PageRequest pageable = PageRequest.of(0, 2);
|
||||
|
||||
given(storeService.findSearchStoreScroll(any(SearchStoreCondition.class), any(Pageable.class)))
|
||||
.willReturn(getWillReturnSearchStore(pageable));
|
||||
|
||||
// WHEN
|
||||
ResultActions actions = mockMvc.perform(get("/search-store")
|
||||
.param("latitude", String.valueOf(condition.getLatitude()))
|
||||
.param("longitude", String.valueOf(condition.getLongitude()))
|
||||
.param("page", String.valueOf(pageable.getPageNumber()))
|
||||
.param("size", String.valueOf(pageable.getPageSize()))
|
||||
);
|
||||
|
||||
// THEN
|
||||
actions.andExpect(status().isOk())
|
||||
.andDo(print())
|
||||
.andDo(document("searchStore-get",
|
||||
requestParameters(
|
||||
parameterWithName("latitude").description("고객의 위도 [필수]"),
|
||||
parameterWithName("longitude").description("고객의 경도 [필수]"),
|
||||
parameterWithName("page").description("검색할 페이지 [Optional, default: 0]"),
|
||||
parameterWithName("size").description("검색할 페이지 사이즈 [Optional, default: 2]")
|
||||
),
|
||||
responseFields(
|
||||
fieldWithPath("code").description("결과 코드 SUCCESS/ERROR"),
|
||||
fieldWithPath("message").description("메시지"),
|
||||
fieldWithPath("data.stores[*].id").description("매장 고유번호"),
|
||||
fieldWithPath("data.stores[*].name").description("매장 이름"),
|
||||
fieldWithPath("data.stores[*].distance").description("고객과의 거리차이 m/km"),
|
||||
fieldWithPath("data.hasNext").description("더보기 버튼 유무")
|
||||
)
|
||||
))
|
||||
;
|
||||
}
|
||||
|
||||
private SliceImpl<SearchStoreResult> getWillReturnSearchStore(Pageable pageable) {
|
||||
SearchStoreResult result_1 = new SearchStoreResult(1L, "이디야커피 마포오벨리스크점", 145.11980562222007);
|
||||
SearchStoreResult result_2 = new SearchStoreResult(2L, "만랩커피 마포점", 150.97181089895466);
|
||||
SearchStoreResult result_3 = new SearchStoreResult(3L, "커피온리 마포역점", 341.25696860337655);
|
||||
return new SliceImpl<>(List.of(result_1, result_2, result_3), pageable,true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user