Files
telegram-bot-workers/tests/setup.ts
kappa f5df0c0ffe feat: add optimistic locking and improve type safety
- Implement optimistic locking for deposit balance updates
  - Prevent race conditions in concurrent deposit requests
  - Add automatic retry with exponential backoff (max 3 attempts)
  - Add version column to user_deposits table

- Improve type safety across codebase
  - Add explicit types for Namecheap API responses
  - Add typed function arguments (ManageDepositArgs, etc.)
  - Remove `any` types from deposit-agent and tool files

- Add reconciliation job for balance integrity verification
  - Compare user_deposits.balance vs SUM(confirmed transactions)
  - Alert admin on discrepancy detection

- Set up test environment with Vitest + Miniflare
  - Add 50+ test cases for deposit system
  - Add helper functions for test data creation

- Update documentation
  - Add migration guide for version columns
  - Document optimistic locking patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 23:23:09 +09:00

110 lines
2.9 KiB
TypeScript

/**
* Vitest 테스트 환경 설정
*
* Miniflare를 사용하여 D1 Database를 in-memory SQLite로 시뮬레이션
*/
import { readFileSync } from 'fs';
import { join } from 'path';
import { beforeAll, afterEach } from 'vitest';
declare global {
function getMiniflareBindings(): {
DB: D1Database;
RATE_LIMIT_KV: KVNamespace;
};
}
let db: D1Database;
beforeAll(async () => {
// Miniflare 바인딩 가져오기
const bindings = getMiniflareBindings();
db = bindings.DB;
// 스키마 초기화
const schemaPath = join(__dirname, '../schema.sql');
const schema = readFileSync(schemaPath, 'utf-8');
// 각 statement를 개별 실행
const statements = schema
.split(';')
.map(s => s.trim())
.filter(s => s.length > 0 && !s.startsWith('--'));
for (const statement of statements) {
await db.exec(statement);
}
});
afterEach(async () => {
// 각 테스트 후 데이터 정리 (스키마는 유지)
await db.exec('DELETE FROM deposit_transactions');
await db.exec('DELETE FROM bank_notifications');
await db.exec('DELETE FROM user_deposits');
await db.exec('DELETE FROM user_domains');
await db.exec('DELETE FROM summaries');
await db.exec('DELETE FROM message_buffer');
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;
}