generated from ttipo/project-base
feat(backend): 장소 목록에 관광공사·확인 장소만 받는 trusted 조건 추가
CI / Backend (lint · test · e2e · build) (pull_request) Successful in 11m54s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m0s
CI / Frontend (lint · type-check · test) (pull_request) Successful in 11m5s
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
CI / Backend (lint · test · e2e · build) (pull_request) Successful in 11m54s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m0s
CI / Frontend (lint · type-check · test) (pull_request) Successful in 11m5s
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
장소 상세 '함께 가기 좋은 곳'은 확인 안 된 상가정보 매장을 빼야 한다(2026-11-02 합의). 앱이 가까운 30곳을 받아 거르면 도심처럼 상가가 빽빽한 곳은 받은 곳이 모두 상가라 칸이 비었다. 관광공사 장소 상세 860곳 중 30곳은 칸이 사라지고 186곳은 덜 보였다(봉산서원은 1.5km 안에 관광공사 장소가 53곳인데 0곳). trusted=true 면 관광공사 장소와 운영자가 확인한 장소만 주고 '추천 안 함' 판정도 뺀다. 선택 조건이라 기존 호출은 그대로다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #4.
This commit is contained in:
@@ -22,6 +22,10 @@ import { parseOpeningHours } from '@/shared/utils/opening-hours';
|
||||
const C = { lat: 36.5, lng: 130.5 };
|
||||
const at = (dLat: number) => ({ lat: C.lat + dLat, lng: C.lng });
|
||||
const near = `lat=${C.lat}&lng=${C.lng}&radius=2000`;
|
||||
/** 'trusted'(관광공사·확인 장소만) 검증용 묶음 — 위 묶음의 거리순 기대값에 섞이지 않게 5.5km 떨어뜨린다 */
|
||||
const C2 = { lat: 36.55, lng: 130.5 };
|
||||
const at2 = (dLat: number) => ({ lat: C2.lat + dLat, lng: C2.lng });
|
||||
const near2 = `lat=${C2.lat}&lng=${C2.lng}&radius=1000`;
|
||||
|
||||
interface ListItem {
|
||||
id: string;
|
||||
@@ -186,6 +190,58 @@ describe('장소 조회 API (e2e)', () => {
|
||||
verifiedMethod: 'phone',
|
||||
verifiedAt: '2026-11-02',
|
||||
});
|
||||
|
||||
// trusted 묶음: 관광공사 1 · 확인 안 된 상가 1 · 확인한 상가 1 · 추천 안 함으로 판정한 관광공사·상가 각 1
|
||||
await save('R1', {
|
||||
source: 'tour',
|
||||
sourceId: 'e2e-R1',
|
||||
category: 'attraction',
|
||||
name: '이투이 바닷가 전망대',
|
||||
...at2(0.001),
|
||||
});
|
||||
await save('R2', {
|
||||
source: 'store',
|
||||
sourceId: 'e2e-R2',
|
||||
category: 'cafe',
|
||||
name: '이투이 확인안한카페',
|
||||
...at2(0.0005), // 가장 가깝다 — trusted 가 아니면 첫 자리를 차지한다
|
||||
});
|
||||
await save('R3', {
|
||||
source: 'store',
|
||||
sourceId: 'e2e-R3',
|
||||
category: 'cafe',
|
||||
name: '이투이 확인한카페',
|
||||
...at2(0.002),
|
||||
});
|
||||
await save('R4', {
|
||||
source: 'tour',
|
||||
sourceId: 'e2e-R4',
|
||||
category: 'cafe',
|
||||
name: '이투이 추천안함카페',
|
||||
...at2(0.0025),
|
||||
});
|
||||
await save('R5', {
|
||||
source: 'store',
|
||||
sourceId: 'e2e-R5',
|
||||
category: 'bar',
|
||||
name: '이투이 추천안함바',
|
||||
...at2(0.003),
|
||||
});
|
||||
const curations = ds.getRepository(PlaceCuration);
|
||||
await curations.save({
|
||||
placeId: ids.R3,
|
||||
verdict: 'pending',
|
||||
verifiedMethod: 'official_channel',
|
||||
verifiedAt: '2026-11-04',
|
||||
});
|
||||
for (const key of ['R4', 'R5']) {
|
||||
await curations.save({
|
||||
placeId: ids[key],
|
||||
verdict: 'not_recommend',
|
||||
verifiedMethod: 'visit',
|
||||
verifiedAt: '2026-11-04',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -248,6 +304,21 @@ describe('장소 조회 API (e2e)', () => {
|
||||
expect(names(res)).toEqual(['이투이카페', '이투이 골목카페']);
|
||||
});
|
||||
|
||||
it('trusted 는 관광공사·운영자 확인 장소만 주고, 추천 안 함으로 판정한 곳은 뺀다', async () => {
|
||||
const all = await get(`/api/places?${near2}`).expect(200);
|
||||
expect(list(all).data.pageInfo.total).toBe(5);
|
||||
const res = await get(`/api/places?${near2}&trusted=true`).expect(200);
|
||||
expect(names(res)).toEqual(['이투이 바닷가 전망대', '이투이 확인한카페']);
|
||||
expect(list(res).data.pageInfo.total).toBe(2);
|
||||
// 가장 가까운 곳이 확인 안 된 상가여도 limit 안에 관광공사·확인 장소가 채워진다
|
||||
const plain = await get(`/api/places?${near2}&limit=1`).expect(200);
|
||||
expect(names(plain)).toEqual(['이투이 확인안한카페']);
|
||||
const one = await get(`/api/places?${near2}&trusted=true&limit=1`).expect(
|
||||
200,
|
||||
);
|
||||
expect(names(one)).toEqual(['이투이 바닷가 전망대']);
|
||||
});
|
||||
|
||||
it('좌표 없이 이름 검색하면 이름순으로 반경 제한 없이 준다', async () => {
|
||||
const res = await get(
|
||||
`/api/places?q=${encodeURIComponent('이투이')}`,
|
||||
@@ -266,6 +337,7 @@ describe('장소 조회 API (e2e)', () => {
|
||||
['없는 분류', 'category=pub'],
|
||||
['형식이 틀린 커서', `${near}&cursor=abc`],
|
||||
['true/false 가 아닌 필터', 'pet=yes'],
|
||||
['true/false 가 아닌 trusted', 'trusted=1'],
|
||||
])('%s 는 VAL_001(400)', async (_, query) => {
|
||||
const res = await get(`/api/places?${query}`).expect(400);
|
||||
expect(detail(res).error?.code).toBe('VAL_001');
|
||||
|
||||
Reference in New Issue
Block a user