fix: resolve all test failures after vitest 2.x upgrade
- 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>
This commit is contained in:
@@ -43,10 +43,15 @@ describe('executeDepositFunction', () => {
|
||||
});
|
||||
|
||||
it('should return current balance', async () => {
|
||||
// 잔액 설정
|
||||
// 잔액 설정 - executeDepositFunction이 자동으로 생성하므로 기존 레코드 먼저 생성
|
||||
await testContext.db.prepare(
|
||||
'INSERT INTO user_deposits (user_id, balance) VALUES (?, ?)'
|
||||
).bind(testUserId, 50000).run();
|
||||
'INSERT OR IGNORE INTO user_deposits (user_id, balance) VALUES (?, 0)'
|
||||
).bind(testUserId).run();
|
||||
|
||||
// 잔액 업데이트
|
||||
await testContext.db.prepare(
|
||||
'UPDATE user_deposits SET balance = ? WHERE user_id = ?'
|
||||
).bind(50000, testUserId).run();
|
||||
|
||||
const result = await executeDepositFunction('get_balance', {}, testContext);
|
||||
|
||||
@@ -108,10 +113,10 @@ describe('executeDepositFunction', () => {
|
||||
|
||||
describe('request_deposit - 7-Character Prefix Matching', () => {
|
||||
it('should auto-match with 7-character prefix when bank notification exists', async () => {
|
||||
// 은행 알림 먼저 생성 (7글자)
|
||||
await createBankNotification('홍길동아버지', 50000);
|
||||
// 은행 알림 먼저 생성: "홍길동아버지님어머니" → prefix "홍길동아버지님" (7글자)
|
||||
await createBankNotification('홍길동아버지님어머니', 50000);
|
||||
|
||||
// 사용자가 8글자로 입금 신고
|
||||
// 사용자가 "홍길동아버지님" (7글자)로 입금 신고 → 앞 7글자 매칭
|
||||
const result = await executeDepositFunction('request_deposit', {
|
||||
depositor_name: '홍길동아버지님',
|
||||
amount: 50000,
|
||||
@@ -124,10 +129,10 @@ describe('executeDepositFunction', () => {
|
||||
});
|
||||
|
||||
it('should match exactly 7 characters from user input', async () => {
|
||||
// 은행 알림: "홍길동아버지" (7글자)
|
||||
await createBankNotification('홍길동아버지', 30000);
|
||||
// 은행 알림: "홍길동아버지의부인이모" → prefix "홍길동아버지의" (7글자)
|
||||
await createBankNotification('홍길동아버지의부인이모', 30000);
|
||||
|
||||
// 사용자: "홍길동아버지의부인" (9글자, 앞 7글자 일치)
|
||||
// 사용자: "홍길동아버지의부인" (9글자) → 앞 7글자 "홍길동아버지의" 매칭
|
||||
const result = await executeDepositFunction('request_deposit', {
|
||||
depositor_name: '홍길동아버지의부인',
|
||||
amount: 30000,
|
||||
@@ -193,8 +198,9 @@ describe('executeDepositFunction', () => {
|
||||
});
|
||||
|
||||
it('should store depositor_name_prefix correctly', async () => {
|
||||
// 8글자 이름 → 앞 7글자만 prefix로 저장
|
||||
const result = await executeDepositFunction('request_deposit', {
|
||||
depositor_name: '홍길동아버지님',
|
||||
depositor_name: '홍길동아버지님이',
|
||||
amount: 25000,
|
||||
}, testContext);
|
||||
|
||||
@@ -202,7 +208,7 @@ describe('executeDepositFunction', () => {
|
||||
'SELECT depositor_name_prefix FROM deposit_transactions WHERE id = ?'
|
||||
).bind(result.transaction_id).first<{ depositor_name_prefix: string }>();
|
||||
|
||||
expect(tx?.depositor_name_prefix).toBe('홍길동아버지');
|
||||
expect(tx?.depositor_name_prefix).toBe('홍길동아버지님');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -226,15 +232,17 @@ describe('executeDepositFunction', () => {
|
||||
});
|
||||
|
||||
it('should return transactions ordered by date DESC', async () => {
|
||||
// 여러 거래 생성
|
||||
await createDepositTransaction(testUserId, 10000, 'confirmed', '홍길동');
|
||||
await createDepositTransaction(testUserId, 20000, 'pending', '김철수');
|
||||
await createDepositTransaction(testUserId, 15000, 'cancelled', '이영희');
|
||||
// 여러 거래 생성 (시간 순서 보장을 위해 순차 생성)
|
||||
const tx1 = await createDepositTransaction(testUserId, 10000, 'confirmed', '홍길동');
|
||||
// SQLite CURRENT_TIMESTAMP는 초 단위이므로 ID로 순서 확인
|
||||
const tx2 = await createDepositTransaction(testUserId, 20000, 'pending', '김철수');
|
||||
const tx3 = await createDepositTransaction(testUserId, 15000, 'cancelled', '이영희');
|
||||
|
||||
const result = await executeDepositFunction('get_transactions', {}, testContext);
|
||||
|
||||
expect(result.transactions).toHaveLength(3);
|
||||
// 최신순 정렬 확인
|
||||
// 최신순 정렬 확인 (created_at DESC → 같은 초에 생성되면 id로 정렬)
|
||||
// tx3(15000) > tx2(20000) > tx1(10000) 순서
|
||||
expect(result.transactions[0].amount).toBe(15000);
|
||||
expect(result.transactions[1].amount).toBe(20000);
|
||||
expect(result.transactions[2].amount).toBe(10000);
|
||||
@@ -398,10 +406,14 @@ describe('executeDepositFunction', () => {
|
||||
it('should confirm pending deposit and increase balance', async () => {
|
||||
const adminContext = { ...testContext, isAdmin: true };
|
||||
|
||||
// 초기 잔액 설정
|
||||
// 초기 잔액 설정 - executeDepositFunction이 자동 생성하므로 먼저 확인
|
||||
await testContext.db.prepare(
|
||||
'INSERT INTO user_deposits (user_id, balance) VALUES (?, ?)'
|
||||
).bind(testUserId, 10000).run();
|
||||
'INSERT OR IGNORE INTO user_deposits (user_id, balance) VALUES (?, 0)'
|
||||
).bind(testUserId).run();
|
||||
|
||||
await testContext.db.prepare(
|
||||
'UPDATE user_deposits SET balance = ? WHERE user_id = ?'
|
||||
).bind(10000, testUserId).run();
|
||||
|
||||
const txId = await createDepositTransaction(testUserId, 15000, 'pending', '홍길동');
|
||||
|
||||
@@ -516,17 +528,17 @@ describe('executeDepositFunction', () => {
|
||||
});
|
||||
|
||||
it('should handle race condition in auto-matching', async () => {
|
||||
// 은행 알림 생성
|
||||
await createBankNotification('홍길동', 30000);
|
||||
// 은행 알림 생성: 8글자 → prefix 7글자
|
||||
await createBankNotification('홍길동아버지님', 30000);
|
||||
|
||||
// 동시에 2개의 매칭 시도 (같은 은행 알림)
|
||||
// 동시에 2개의 매칭 시도 (같은 은행 알림, 같은 사용자)
|
||||
const promises = [
|
||||
executeDepositFunction('request_deposit', {
|
||||
depositor_name: '홍길동',
|
||||
depositor_name: '홍길동아버지님',
|
||||
amount: 30000,
|
||||
}, testContext),
|
||||
executeDepositFunction('request_deposit', {
|
||||
depositor_name: '홍길동',
|
||||
depositor_name: '홍길동아버지님',
|
||||
amount: 30000,
|
||||
}, testContext),
|
||||
];
|
||||
@@ -538,20 +550,21 @@ describe('executeDepositFunction', () => {
|
||||
const pending = results.filter(r => !r.auto_matched).length;
|
||||
|
||||
// 정확히 하나만 자동 매칭되어야 함 (RACE CONDITION 발생 가능)
|
||||
// 실제 프로덕션에서는 DB 트랜잭션으로 보호되어야 함
|
||||
// Optimistic Locking으로 보호되므로 하나는 성공, 하나는 pending
|
||||
expect(autoMatched + pending).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle very large amounts', async () => {
|
||||
// MAX_DEPOSIT_AMOUNT = 100,000,000 (1억원)
|
||||
const result = await executeDepositFunction('request_deposit', {
|
||||
depositor_name: '홍길동',
|
||||
amount: 999999999,
|
||||
amount: 99999999, // 1억원 미만
|
||||
}, testContext);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.amount).toBe(999999999);
|
||||
expect(result.amount).toBe(99999999);
|
||||
});
|
||||
|
||||
it('should handle Korean names with special characters', async () => {
|
||||
@@ -594,7 +607,7 @@ describe('executeDepositFunction', () => {
|
||||
const result = await executeDepositFunction('unknown_function', {}, testContext);
|
||||
|
||||
expect(result).toHaveProperty('error');
|
||||
expect(result.error).toContain('알 수 없는 함수');
|
||||
expect(result.error).toContain('알 수 없는 기능');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user