- Attach rejects handler before advancing timers (vitest 2.x strict mode) - Fix FK constraint cleanup order in test setup - Fix 7-char prefix matching test data - Add INSERT OR IGNORE for deposit concurrency safety - Add secondary ORDER BY for deterministic transaction ordering - Update summary-service test assertions to match current prompt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
186 lines
5.1 KiB
TypeScript
186 lines
5.1 KiB
TypeScript
/**
|
|
* 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<number> {
|
|
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<number> {
|
|
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<number> {
|
|
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<number> {
|
|
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<number> {
|
|
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);
|
|
}
|