- 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>
614 lines
22 KiB
TypeScript
614 lines
22 KiB
TypeScript
/**
|
|
* Comprehensive Unit Tests for deposit-agent.ts
|
|
*
|
|
* 테스트 범위:
|
|
* 1. 음수 금액 거부
|
|
* 2. 동시성 처리 안전성
|
|
* 3. 부분 배치 실패 처리
|
|
* 4. 입금자명 7글자 매칭
|
|
* 5. 잔액 부족 시나리오
|
|
* 6. 관리자 권한 검증
|
|
* 7. 거래 상태 검증
|
|
*/
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { executeDepositFunction, DepositContext } from '../src/agents/deposit-agent';
|
|
import {
|
|
createTestUser,
|
|
createBankNotification,
|
|
createDepositTransaction,
|
|
getTestDB,
|
|
} from './setup';
|
|
|
|
describe('executeDepositFunction', () => {
|
|
let testUserId: number;
|
|
let testContext: DepositContext;
|
|
|
|
beforeEach(async () => {
|
|
// 각 테스트마다 새로운 사용자 생성
|
|
testUserId = await createTestUser('123456789', 'testuser');
|
|
testContext = {
|
|
userId: testUserId,
|
|
telegramUserId: '123456789',
|
|
isAdmin: false,
|
|
db: getTestDB(),
|
|
};
|
|
});
|
|
|
|
describe('get_balance', () => {
|
|
it('should return zero balance for new user', async () => {
|
|
const result = await executeDepositFunction('get_balance', {}, testContext);
|
|
|
|
expect(result).toHaveProperty('balance', 0);
|
|
expect(result).toHaveProperty('formatted', '0원');
|
|
});
|
|
|
|
it('should return current balance', async () => {
|
|
// 잔액 설정 - executeDepositFunction이 자동으로 생성하므로 기존 레코드 먼저 생성
|
|
await testContext.db.prepare(
|
|
'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);
|
|
|
|
expect(result.balance).toBe(50000);
|
|
expect(result.formatted).toBe('50,000원');
|
|
});
|
|
});
|
|
|
|
describe('get_account_info', () => {
|
|
it('should return bank account information', async () => {
|
|
const result = await executeDepositFunction('get_account_info', {}, testContext);
|
|
|
|
expect(result).toHaveProperty('bank', '하나은행');
|
|
expect(result).toHaveProperty('account', '427-910018-27104');
|
|
expect(result).toHaveProperty('holder', '주식회사 아이언클래드');
|
|
expect(result).toHaveProperty('instruction');
|
|
});
|
|
});
|
|
|
|
describe('request_deposit - Negative Amount Validation', () => {
|
|
it('should reject negative amounts', async () => {
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동',
|
|
amount: -10000,
|
|
}, testContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('충전 금액을 입력해주세요');
|
|
});
|
|
|
|
it('should reject zero amount', async () => {
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동',
|
|
amount: 0,
|
|
}, testContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('충전 금액을 입력해주세요');
|
|
});
|
|
|
|
it('should reject missing amount', async () => {
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동',
|
|
}, testContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('충전 금액을 입력해주세요');
|
|
});
|
|
|
|
it('should reject missing depositor name', async () => {
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
amount: 10000,
|
|
}, testContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('입금자명을 입력해주세요');
|
|
});
|
|
});
|
|
|
|
describe('request_deposit - 7-Character Prefix Matching', () => {
|
|
it('should auto-match with 7-character prefix when bank notification exists', async () => {
|
|
// 은행 알림 먼저 생성: "홍길동아버지님어머니" → prefix "홍길동아버지님" (7글자)
|
|
await createBankNotification('홍길동아버지님어머니', 50000);
|
|
|
|
// 사용자가 "홍길동아버지님" (7글자)로 입금 신고 → 앞 7글자 매칭
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동아버지님',
|
|
amount: 50000,
|
|
}, testContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.auto_matched).toBe(true);
|
|
expect(result.new_balance).toBe(50000);
|
|
expect(result.message).toContain('자동 매칭');
|
|
});
|
|
|
|
it('should match exactly 7 characters from user input', async () => {
|
|
// 은행 알림: "홍길동아버지의부인이모" → prefix "홍길동아버지의" (7글자)
|
|
await createBankNotification('홍길동아버지의부인이모', 30000);
|
|
|
|
// 사용자: "홍길동아버지의부인" (9글자) → 앞 7글자 "홍길동아버지의" 매칭
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동아버지의부인',
|
|
amount: 30000,
|
|
}, testContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.auto_matched).toBe(true);
|
|
expect(result.new_balance).toBe(30000);
|
|
});
|
|
|
|
it('should not match if prefix differs', async () => {
|
|
// 은행 알림: "김철수씨" (5글자)
|
|
await createBankNotification('김철수씨', 20000);
|
|
|
|
// 사용자: "홍길동" (3글자) - 불일치
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동',
|
|
amount: 20000,
|
|
}, testContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.auto_matched).toBe(false);
|
|
expect(result.status).toBe('pending');
|
|
});
|
|
|
|
it('should not match if amount differs', async () => {
|
|
await createBankNotification('홍길동', 50000);
|
|
|
|
// 금액 불일치
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동',
|
|
amount: 30000,
|
|
}, testContext);
|
|
|
|
expect(result.auto_matched).toBe(false);
|
|
expect(result.status).toBe('pending');
|
|
});
|
|
});
|
|
|
|
describe('request_deposit - Pending Transaction', () => {
|
|
it('should create pending transaction when no bank notification', async () => {
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동',
|
|
amount: 10000,
|
|
}, testContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.auto_matched).toBe(false);
|
|
expect(result.status).toBe('pending');
|
|
expect(result).toHaveProperty('transaction_id');
|
|
expect(result).toHaveProperty('account_info');
|
|
|
|
// DB 확인
|
|
const tx = await testContext.db.prepare(
|
|
'SELECT * FROM deposit_transactions WHERE id = ?'
|
|
).bind(result.transaction_id).first();
|
|
|
|
expect(tx).toBeDefined();
|
|
expect(tx?.status).toBe('pending');
|
|
expect(tx?.depositor_name).toBe('홍길동');
|
|
expect(tx?.depositor_name_prefix).toBe('홍길동');
|
|
expect(tx?.amount).toBe(10000);
|
|
});
|
|
|
|
it('should store depositor_name_prefix correctly', async () => {
|
|
// 8글자 이름 → 앞 7글자만 prefix로 저장
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동아버지님이',
|
|
amount: 25000,
|
|
}, testContext);
|
|
|
|
const tx = await testContext.db.prepare(
|
|
'SELECT depositor_name_prefix FROM deposit_transactions WHERE id = ?'
|
|
).bind(result.transaction_id).first<{ depositor_name_prefix: string }>();
|
|
|
|
expect(tx?.depositor_name_prefix).toBe('홍길동아버지님');
|
|
});
|
|
});
|
|
|
|
describe('request_deposit - Batch Failure Handling', () => {
|
|
it.skip('DEPRECATED: batch 테스트 - 현재는 Optimistic Locking 사용', async () => {
|
|
// NOTE: 프로덕션 코드가 executeWithOptimisticLock()을 사용하도록 변경됨
|
|
// db.batch()를 직접 사용하지 않으므로 이 테스트는 더 이상 유효하지 않음
|
|
// TODO: Optimistic Locking 동시성 충돌 시뮬레이션 테스트 추가 필요
|
|
// - Version mismatch 시나리오
|
|
// - 재시도 로직 검증
|
|
// - OptimisticLockError 처리 확인
|
|
});
|
|
});
|
|
|
|
describe('get_transactions', () => {
|
|
it('should return empty list for new user', async () => {
|
|
const result = await executeDepositFunction('get_transactions', {}, testContext);
|
|
|
|
expect(result.transactions).toEqual([]);
|
|
expect(result.message).toContain('거래 내역이 없습니다');
|
|
});
|
|
|
|
it('should return transactions ordered by date DESC', async () => {
|
|
// 여러 거래 생성 (시간 순서 보장을 위해 순차 생성)
|
|
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);
|
|
});
|
|
|
|
it('should respect limit parameter', async () => {
|
|
// 5개 거래 생성
|
|
for (let i = 0; i < 5; i++) {
|
|
await createDepositTransaction(testUserId, 10000 * (i + 1), 'confirmed');
|
|
}
|
|
|
|
const result = await executeDepositFunction('get_transactions', {
|
|
limit: 3,
|
|
}, testContext);
|
|
|
|
expect(result.transactions).toHaveLength(3);
|
|
});
|
|
|
|
it('should enforce maximum limit of 100', async () => {
|
|
const result = await executeDepositFunction('get_transactions', {
|
|
limit: 500, // 과도한 limit
|
|
}, testContext);
|
|
|
|
// 실제로는 100개까지만 조회되어야 함 (로직 검증)
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
it('should use default limit of 10 when not specified', async () => {
|
|
// 15개 거래 생성
|
|
for (let i = 0; i < 15; i++) {
|
|
await createDepositTransaction(testUserId, 1000, 'confirmed');
|
|
}
|
|
|
|
const result = await executeDepositFunction('get_transactions', {}, testContext);
|
|
|
|
expect(result.transactions).toHaveLength(10);
|
|
});
|
|
});
|
|
|
|
describe('cancel_transaction', () => {
|
|
it('should cancel pending transaction', async () => {
|
|
const txId = await createDepositTransaction(testUserId, 10000, 'pending', '홍길동');
|
|
|
|
const result = await executeDepositFunction('cancel_transaction', {
|
|
transaction_id: txId,
|
|
}, testContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.transaction_id).toBe(txId);
|
|
|
|
// DB 확인
|
|
const tx = await testContext.db.prepare(
|
|
'SELECT status FROM deposit_transactions WHERE id = ?'
|
|
).bind(txId).first<{ status: string }>();
|
|
|
|
expect(tx?.status).toBe('cancelled');
|
|
});
|
|
|
|
it('should reject canceling confirmed transaction', async () => {
|
|
const txId = await createDepositTransaction(testUserId, 10000, 'confirmed', '홍길동');
|
|
|
|
const result = await executeDepositFunction('cancel_transaction', {
|
|
transaction_id: txId,
|
|
}, testContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('대기 중인 거래만 취소');
|
|
});
|
|
|
|
it('should reject canceling other user transaction', async () => {
|
|
const otherUserId = await createTestUser('987654321', 'otheruser');
|
|
const txId = await createDepositTransaction(otherUserId, 10000, 'pending', '김철수');
|
|
|
|
const result = await executeDepositFunction('cancel_transaction', {
|
|
transaction_id: txId,
|
|
}, testContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('본인의 거래만 취소');
|
|
});
|
|
|
|
it('should allow admin to cancel any transaction', async () => {
|
|
const otherUserId = await createTestUser('987654321', 'otheruser');
|
|
const txId = await createDepositTransaction(otherUserId, 10000, 'pending', '김철수');
|
|
|
|
const adminContext = { ...testContext, isAdmin: true };
|
|
const result = await executeDepositFunction('cancel_transaction', {
|
|
transaction_id: txId,
|
|
}, adminContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should return error for non-existent transaction', async () => {
|
|
const result = await executeDepositFunction('cancel_transaction', {
|
|
transaction_id: 99999,
|
|
}, testContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('거래를 찾을 수 없습니다');
|
|
});
|
|
});
|
|
|
|
describe('Admin Functions - Permission Checks', () => {
|
|
it('should reject non-admin calling get_pending_list', async () => {
|
|
const result = await executeDepositFunction('get_pending_list', {}, testContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('관리자 권한이 필요합니다');
|
|
});
|
|
|
|
it('should reject non-admin calling confirm_deposit', async () => {
|
|
const txId = await createDepositTransaction(testUserId, 10000, 'pending');
|
|
|
|
const result = await executeDepositFunction('confirm_deposit', {
|
|
transaction_id: txId,
|
|
}, testContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('관리자 권한이 필요합니다');
|
|
});
|
|
|
|
it('should reject non-admin calling reject_deposit', async () => {
|
|
const txId = await createDepositTransaction(testUserId, 10000, 'pending');
|
|
|
|
const result = await executeDepositFunction('reject_deposit', {
|
|
transaction_id: txId,
|
|
}, testContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('관리자 권한이 필요합니다');
|
|
});
|
|
});
|
|
|
|
describe('Admin Functions - get_pending_list', () => {
|
|
it('should return pending deposits for admin', async () => {
|
|
const adminContext = { ...testContext, isAdmin: true };
|
|
|
|
// pending 거래 생성
|
|
await createDepositTransaction(testUserId, 10000, 'pending', '홍길동');
|
|
await createDepositTransaction(testUserId, 20000, 'confirmed', '김철수');
|
|
|
|
const result = await executeDepositFunction('get_pending_list', {}, adminContext);
|
|
|
|
expect(result.pending).toHaveLength(1);
|
|
expect(result.pending[0].amount).toBe(10000);
|
|
expect(result.pending[0].depositor_name).toBe('홍길동');
|
|
});
|
|
|
|
it('should return empty list when no pending deposits', async () => {
|
|
const adminContext = { ...testContext, isAdmin: true };
|
|
|
|
const result = await executeDepositFunction('get_pending_list', {}, adminContext);
|
|
|
|
expect(result.pending).toEqual([]);
|
|
expect(result.message).toContain('대기 중인 입금 요청이 없습니다');
|
|
});
|
|
});
|
|
|
|
describe('Admin Functions - confirm_deposit', () => {
|
|
it('should confirm pending deposit and increase balance', async () => {
|
|
const adminContext = { ...testContext, isAdmin: true };
|
|
|
|
// 초기 잔액 설정 - executeDepositFunction이 자동 생성하므로 먼저 확인
|
|
await testContext.db.prepare(
|
|
'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', '홍길동');
|
|
|
|
const result = await executeDepositFunction('confirm_deposit', {
|
|
transaction_id: txId,
|
|
}, adminContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.amount).toBe(15000);
|
|
|
|
// 거래 상태 확인
|
|
const tx = await testContext.db.prepare(
|
|
'SELECT status, confirmed_at FROM deposit_transactions WHERE id = ?'
|
|
).bind(txId).first<{ status: string; confirmed_at: string | null }>();
|
|
|
|
expect(tx?.status).toBe('confirmed');
|
|
expect(tx?.confirmed_at).toBeDefined();
|
|
|
|
// 잔액 확인
|
|
const deposit = await testContext.db.prepare(
|
|
'SELECT balance FROM user_deposits WHERE user_id = ?'
|
|
).bind(testUserId).first<{ balance: number }>();
|
|
|
|
expect(deposit?.balance).toBe(25000); // 10000 + 15000
|
|
});
|
|
|
|
it('should reject confirming already confirmed transaction', async () => {
|
|
const adminContext = { ...testContext, isAdmin: true };
|
|
const txId = await createDepositTransaction(testUserId, 10000, 'confirmed');
|
|
|
|
const result = await executeDepositFunction('confirm_deposit', {
|
|
transaction_id: txId,
|
|
}, adminContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('대기 중인 거래만 확인');
|
|
});
|
|
|
|
it.skip('DEPRECATED: batch 테스트 - 현재는 Optimistic Locking 사용', async () => {
|
|
// NOTE: confirm_deposit도 executeWithOptimisticLock()을 사용하도록 변경됨
|
|
// 더 이상 db.batch()를 직접 사용하지 않음
|
|
// TODO: 관리자 입금 확인 시 Optimistic Locking 테스트 추가 필요
|
|
// - 동시 확인 시도 시 하나만 성공 확인
|
|
// - Version mismatch 재시도 검증
|
|
});
|
|
});
|
|
|
|
describe('Admin Functions - reject_deposit', () => {
|
|
it('should reject pending deposit', async () => {
|
|
const adminContext = { ...testContext, isAdmin: true };
|
|
const txId = await createDepositTransaction(testUserId, 10000, 'pending', '홍길동');
|
|
|
|
const result = await executeDepositFunction('reject_deposit', {
|
|
transaction_id: txId,
|
|
}, adminContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
// DB 확인
|
|
const tx = await testContext.db.prepare(
|
|
'SELECT status FROM deposit_transactions WHERE id = ?'
|
|
).bind(txId).first<{ status: string }>();
|
|
|
|
expect(tx?.status).toBe('rejected');
|
|
});
|
|
|
|
it('should reject rejecting already confirmed transaction', async () => {
|
|
const adminContext = { ...testContext, isAdmin: true };
|
|
const txId = await createDepositTransaction(testUserId, 10000, 'confirmed');
|
|
|
|
const result = await executeDepositFunction('reject_deposit', {
|
|
transaction_id: txId,
|
|
}, adminContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('대기 중인 거래만 거절');
|
|
});
|
|
});
|
|
|
|
describe('Concurrency Safety', () => {
|
|
it('should handle concurrent deposit requests from same user', async () => {
|
|
// 동시에 3개의 입금 요청
|
|
const promises = [
|
|
executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동',
|
|
amount: 10000,
|
|
}, testContext),
|
|
executeDepositFunction('request_deposit', {
|
|
depositor_name: '김철수',
|
|
amount: 20000,
|
|
}, testContext),
|
|
executeDepositFunction('request_deposit', {
|
|
depositor_name: '이영희',
|
|
amount: 15000,
|
|
}, testContext),
|
|
];
|
|
|
|
const results = await Promise.all(promises);
|
|
|
|
// 모든 요청이 성공해야 함
|
|
expect(results).toHaveLength(3);
|
|
results.forEach(result => {
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
// DB에 3개의 거래가 모두 저장되어야 함
|
|
const transactions = await testContext.db.prepare(
|
|
'SELECT COUNT(*) as count FROM deposit_transactions WHERE user_id = ?'
|
|
).bind(testUserId).first<{ count: number }>();
|
|
|
|
expect(transactions?.count).toBe(3);
|
|
});
|
|
|
|
it('should handle race condition in auto-matching', async () => {
|
|
// 은행 알림 생성: 8글자 → prefix 7글자
|
|
await createBankNotification('홍길동아버지님', 30000);
|
|
|
|
// 동시에 2개의 매칭 시도 (같은 은행 알림, 같은 사용자)
|
|
const promises = [
|
|
executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동아버지님',
|
|
amount: 30000,
|
|
}, testContext),
|
|
executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동아버지님',
|
|
amount: 30000,
|
|
}, testContext),
|
|
];
|
|
|
|
const results = await Promise.all(promises);
|
|
|
|
// 첫 번째는 자동 매칭, 두 번째는 pending이어야 함
|
|
const autoMatched = results.filter(r => r.auto_matched).length;
|
|
const pending = results.filter(r => !r.auto_matched).length;
|
|
|
|
// 정확히 하나만 자동 매칭되어야 함 (RACE CONDITION 발생 가능)
|
|
// 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: 99999999, // 1억원 미만
|
|
}, testContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.amount).toBe(99999999);
|
|
});
|
|
|
|
it('should handle Korean names with special characters', async () => {
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '홍길동(주)',
|
|
amount: 10000,
|
|
}, testContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.depositor_name).toBe('홍길동(주)');
|
|
});
|
|
|
|
it('should handle very short names', async () => {
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '김',
|
|
amount: 10000,
|
|
}, testContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should handle exactly 7 character names', async () => {
|
|
const result = await executeDepositFunction('request_deposit', {
|
|
depositor_name: '1234567',
|
|
amount: 10000,
|
|
}, testContext);
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
const tx = await testContext.db.prepare(
|
|
'SELECT depositor_name_prefix FROM deposit_transactions WHERE id = ?'
|
|
).bind(result.transaction_id).first<{ depositor_name_prefix: string }>();
|
|
|
|
expect(tx?.depositor_name_prefix).toBe('1234567');
|
|
});
|
|
});
|
|
|
|
describe('Unknown Function', () => {
|
|
it('should return error for unknown function name', async () => {
|
|
const result = await executeDepositFunction('unknown_function', {}, testContext);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result.error).toContain('알 수 없는 기능');
|
|
});
|
|
});
|
|
});
|