/** * Vitest 테스트 환경 설정 * * Miniflare를 사용하여 D1 Database를 in-memory SQLite로 시뮬레이션 */ import { readFileSync } from 'fs'; import { join } from 'path'; import { beforeAll, afterEach } from 'vitest'; import { Miniflare } from 'miniflare'; let mf: Miniflare | null = null; let db: D1Database | null = null; // 이전 코드와의 호환성을 위한 전역 함수 declare global { var getMiniflareBindings: () => { DB: D1Database; RATE_LIMIT_KV: KVNamespace; }; } beforeAll(async () => { // Miniflare 인스턴스 생성 (D1만 사용) mf = new Miniflare({ modules: true, script: 'export default { fetch() { return new Response("test"); } }', d1Databases: { DB: '__test_db__', }, kvNamespaces: ['RATE_LIMIT_KV', 'SESSION_KV'], bindings: { SUMMARY_THRESHOLD: '20', MAX_SUMMARIES_PER_USER: '3', }, }); db = await mf.getD1Database('DB'); // 전역 함수 등록 (global as any).getMiniflareBindings = () => ({ DB: db as D1Database, RATE_LIMIT_KV: {} as KVNamespace, // Mock KV (테스트에 필요 시) }); // 스키마 초기화 const schemaPath = join(__dirname, '../schema.sql'); const schema = readFileSync(schemaPath, 'utf-8'); // 주석 제거하고 statement별로 분리 const cleanSchema = schema .split('\n') .filter(line => !line.trim().startsWith('--')) .join('\n'); // 세미콜론으로 statement 분리하고 각 statement를 한 줄로 압축 const statements = cleanSchema .split(';') .map(s => s.replace(/\s+/g, ' ').trim()) .filter(s => s.length > 0); // 각 statement 개별 실행 try { for (const statement of statements) { await db.exec(statement + ';'); } } catch (error) { console.error('Schema initialization failed:', error); throw error; } }); afterEach(async () => { // DB가 있을 경우만 정리 if (db) { // 각 테스트 후 데이터 정리 (FK 제약 순서 고려) // 1. bank_notifications (참조: deposit_transactions) await db.exec('DELETE FROM bank_notifications'); // 2. deposit_transactions (참조: users) await db.exec('DELETE FROM deposit_transactions'); // 3. user_deposits (참조: users) await db.exec('DELETE FROM user_deposits'); // 4. 기타 테이블 (참조: users) await db.exec('DELETE FROM user_domains'); await db.exec('DELETE FROM summaries'); await db.exec('DELETE FROM message_buffer'); // 5. users (마지막) await db.exec('DELETE FROM users'); } }); /** * 테스트용 헬퍼 함수: 사용자 생성 */ export async function createTestUser( telegramId: string, username?: string ): Promise { const bindings = getMiniflareBindings(); const result = await bindings.DB.prepare( 'INSERT INTO users (telegram_id, username) VALUES (?, ?)' ).bind(telegramId, username || null).run(); return Number(result.meta?.last_row_id || 0); } /** * 테스트용 헬퍼 함수: 은행 알림 생성 */ export async function createBankNotification( depositorName: string, amount: number ): Promise { const bindings = getMiniflareBindings(); const result = await bindings.DB.prepare( 'INSERT INTO bank_notifications (depositor_name, depositor_name_prefix, amount) VALUES (?, ?, ?)' ).bind(depositorName, depositorName.slice(0, 7), amount).run(); return Number(result.meta?.last_row_id || 0); } /** * 테스트용 헬퍼 함수: 입금 거래 생성 */ export async function createDepositTransaction( userId: number, amount: number, status: string, depositorName?: string ): Promise { const bindings = getMiniflareBindings(); const result = await bindings.DB.prepare( `INSERT INTO deposit_transactions (user_id, type, amount, status, depositor_name, depositor_name_prefix) VALUES (?, 'deposit', ?, ?, ?, ?)` ).bind( userId, amount, status, depositorName || null, depositorName ? depositorName.slice(0, 7) : null ).run(); return Number(result.meta?.last_row_id || 0); } /** * 테스트용 헬퍼 함수: DB 바인딩 가져오기 */ export function getTestDB(): D1Database { return getMiniflareBindings().DB; } /** * 테스트용 헬퍼 함수: 요약 생성 */ export async function createSummary( userId: number, chatId: string, generation: number, summary: string, messageCount: number ): Promise { const bindings = getMiniflareBindings(); const result = await bindings.DB.prepare( 'INSERT INTO summaries (user_id, chat_id, generation, summary, message_count) VALUES (?, ?, ?, ?, ?)' ).bind(userId, chatId, generation, summary, messageCount).run(); return Number(result.meta?.last_row_id || 0); } /** * 테스트용 헬퍼 함수: 메시지 버퍼 생성 */ export async function createMessageBuffer( userId: number, chatId: string, role: 'user' | 'bot', message: string ): Promise { const bindings = getMiniflareBindings(); const result = await bindings.DB.prepare( 'INSERT INTO message_buffer (user_id, chat_id, role, message) VALUES (?, ?, ?, ?)' ).bind(userId, chatId, role, message).run(); return Number(result.meta?.last_row_id || 0); }