/** * 로컬 엔드투엔드 점검 스크립트. * npm run db:reset && npm start (다른 터미널) * npm run smoke */ const BASE = process.env.BASE_URL ?? 'http://localhost:3100'; const j = async (method: string, path: string, body?: unknown) => { const res = await fetch(`${BASE}${path}`, { method, headers: body ? { 'content-type': 'application/json' } : undefined, body: body ? JSON.stringify(body) : undefined, }); const text = await res.text(); if (!res.ok) throw new Error(`${method} ${path} → ${res.status} ${text}`); return text ? JSON.parse(text) : null; }; const h = (t: string) => console.log(`\n\x1b[1m${t}\x1b[0m`); async function main() { h('0. health'); console.log(' ', await j('GET', '/health')); h('1. site-1001 생성 (첫 업체 — 전부 신규)'); const a = await j('POST', '/v1/merchants/site-1001/generate?sync=true'); printStats(a); h('2. site-1002 생성 (같은 강남/미용실 — 중복제거 발동)'); const b = await j('POST', '/v1/merchants/site-1002/generate?sync=true'); printStats(b); printDetails(b); h('3. publish 웹훅 + 표기 변형 (trigram 단계)'); const c = await j('POST', '/v1/merchants/publish', { externalId: 'site-1003', name: '강남 뷰티랩', industryId: 'beauty.hair', regionId: 'kr.seoul.gangnam', description: '강남 미용실. 염색 전문.', profile: { services: ['뿌리염색약', '여성펌'], features: ['주차가능'] }, sync: true, }); printStats(c.generation); printDetails(c.generation); h('4. SEO payload'); const seo = await j('GET', '/v1/sites/site-1001/seo?limit=8'); console.log(' title :', seo.title); console.log(' description:', seo.description); console.log(' keywords :', seo.keywords.join(', ')); h('5. AEO payload'); const aeo = await j('GET', '/v1/sites/site-1001/aeo?limit=3'); for (const f of aeo.faqs) console.log(` Q. ${f.question}\n A. ${f.answer}`); h('6. 의미 기반 키워드 검색'); const found = await j('POST', '/v1/keywords/search', { query: '강남 미용실 예약하고 싶어요', limit: 5 }); for (const r of found) console.log(` ${r.score.toFixed(3)} ${r.canonical} (${r.intent}, ${r.usage_count}개 업체)`); h('7. 성과 피드백 → 저성과 강등'); console.log( ' ', await j('POST', '/v1/sites/site-1001/performance', { items: [ { keyword: '강남 미용실 후기', impressions: 500, clicks: 0 }, { keyword: '강남 미용실', impressions: 300, clicks: 40 }, ], }), ); h('8. 비동기 큐 (BullMQ)'); console.log(' ', await j('POST', '/v1/merchants/site-2001/generate')); for (let i = 0; i < 30; i++) { const s = await j('GET', '/v1/sites/site-2001/seo?limit=5'); if (s.keywords.length) { console.log(' 워커 처리 완료 →', s.keywords.join(', ')); break; } await new Promise((r) => setTimeout(r, 500)); } console.log('\n✅ smoke 완료'); } function printStats(s: any) { console.log( ` 후보 ${s.candidates} → 신규 ${s.created} / 중복(정확 ${s.matchedExact}, 표기 ${s.matchedTrigram}, 의미 ${s.matchedVector})` + ` / 차단 ${s.rejected} / 연결 ${s.linked} / QA ${s.qaCreated} (${s.durationMs}ms)`, ); } function printDetails(s: any) { for (const d of s.details ?? []) { const sim = d.similarity != null ? ` (sim=${d.similarity.toFixed(3)} → '${d.matchedTo}')` : ''; console.log(` ${d.action.padEnd(16)} ${d.candidate}${sim}`); } } main().catch((e) => { console.error('\n❌', e.message); process.exit(1); });