diff --git a/prisma/seed/search-events.ts b/prisma/seed/search-events.ts index 4f32049..c6ce944 100644 --- a/prisma/seed/search-events.ts +++ b/prisma/seed/search-events.ts @@ -2,8 +2,9 @@ * 검색 집계 이벤트 + 인기 검색어 스냅샷 시드(검색 진입 화면 검증용). * * - 이벤트는 시드 유저 소유(account_id)로만 만들어 resetSeedScope가 유저 기준으로 정리한다. - * - 스냅샷은 이벤트에서 파생되는 캐시라 시드마다 전량 재생성한다(직전 정각 + 현재 정각 2개, - * 순위 변동 UP/DOWN/SAME/NEW가 모두 보이도록 구성). + * - 스냅샷은 시드가 쓰는 두 정각(직전·현재)만 지우고 재생성한다 — 다른 시각대의 + * 기존 스냅샷은 시드 데이터가 아니므로 보존한다(릴리즈 리뷰 반영). + * 순위 변동 UP/DOWN/SAME/NEW가 모두 보이도록 구성. */ import type { PrismaClient } from '@prisma/client'; @@ -63,7 +64,9 @@ export async function seedSearchEvents( ), }); - await prisma.searchKeywordRankSnapshot.deleteMany(); + await prisma.searchKeywordRankSnapshot.deleteMany({ + where: { ranked_at: { in: [previousAt, rankedAt] } }, + }); await prisma.searchKeywordRankSnapshot.createMany({ data: [ ...PREVIOUS_RANKING.map((keyword, i) => ({ diff --git a/src/common/utils/search-keyword.spec.ts b/src/common/utils/search-keyword.spec.ts index 29cbafc..1851e1e 100644 --- a/src/common/utils/search-keyword.spec.ts +++ b/src/common/utils/search-keyword.spec.ts @@ -27,6 +27,15 @@ describe('search-keyword utils', () => { }); }); + it('길이는 코드 포인트 기준이다(이모지 200개는 허용, 201개는 거절)', () => { + expect( + normalizeSearchKeyword('😀'.repeat(SEARCH_KEYWORD_MAX_LENGTH)).ok, + ).toBe(true); + expect( + normalizeSearchKeyword('😀'.repeat(SEARCH_KEYWORD_MAX_LENGTH + 1)), + ).toEqual({ ok: false, reason: 'TOO_LONG' }); + }); + it('정규화 후 200자를 넘으면 TOO_LONG으로 거절한다', () => { const raw = 'a'.repeat(SEARCH_KEYWORD_MAX_LENGTH + 1); expect(normalizeSearchKeyword(raw)).toEqual({ diff --git a/src/common/utils/search-keyword.ts b/src/common/utils/search-keyword.ts index d120669..ac478bf 100644 --- a/src/common/utils/search-keyword.ts +++ b/src/common/utils/search-keyword.ts @@ -30,7 +30,9 @@ export function normalizeSearchKeyword( ): NormalizeSearchKeywordResult { const keyword = raw.trim().replace(/\s+/g, ' '); if (keyword.length === 0) return { ok: false, reason: 'EMPTY' }; - if (keyword.length > SEARCH_KEYWORD_MAX_LENGTH) { + // MySQL VarChar(200)은 문자(코드 포인트) 수 기준 — UTF-16 단위(.length)로 세면 + // 서로게이트 쌍(이모지 등)이 2로 계산돼 저장 가능한 검색어를 거절한다(릴리즈 리뷰 반영) + if ([...keyword].length > SEARCH_KEYWORD_MAX_LENGTH) { return { ok: false, reason: 'TOO_LONG' }; } return { ok: true, keyword };