- 实现账户管理改进设计文档中的所有核心功能 - 三科目余额管理 (个人余额、劳动报酬、冻结余额) - 交易状态机 (created → pending → bank_submitted → success/failed/timeout → reversed) - 三键幂等体系 (JZTxId/BankTxId/SourceKey) - 优先级扣款规则 (先个人后劳动) - 在途资金管理 (可用→在途→结转/回退) - 三账对账闭环 (总账 = 银行账 + 在途净额) - 补偿服务域 (超时检测、重试、死信队列) - 虚拟银行模拟器用于业务测试 - 完整的集成测试套件 (133 个测试全部通过) - Docker 容器化部署配置 - 前端 Vue3 + TypeScript 项目结构
202 lines
6.7 KiB
Rust
202 lines
6.7 KiB
Rust
//! 转账流程集成测试
|
|
|
|
use rust_decimal_macros::dec;
|
|
|
|
use rustjr::infrastructure::bank_integration::mock_bank::MockBankClient;
|
|
use rustjr::infrastructure::bank_integration::{BankClient, BankTransferRequest};
|
|
|
|
// ==================== 基本转账测试 ====================
|
|
|
|
#[tokio::test]
|
|
async fn test_basic_transfer_success() {
|
|
let client = MockBankClient::new();
|
|
client.create_account("FROM001", "转出账户", dec!(10000.00)).unwrap();
|
|
client.create_account("TO001", "转入账户", dec!(0.00)).unwrap();
|
|
|
|
let request = BankTransferRequest {
|
|
from_account: "FROM001".to_string(),
|
|
to_account: "TO001".to_string(),
|
|
to_account_name: "转入账户".to_string(),
|
|
to_bank_code: "MOCK".to_string(),
|
|
amount: dec!(5000.00),
|
|
remark: Some("测试转账".to_string()),
|
|
business_no: "TXN001".to_string(),
|
|
};
|
|
|
|
let response = client.transfer(request).await.unwrap();
|
|
|
|
assert!(response.success);
|
|
assert!(response.bank_ref_no.is_some());
|
|
assert!(response.error_code.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_transfer_insufficient_balance() {
|
|
let client = MockBankClient::new();
|
|
client.create_account("FROM001", "转出账户", dec!(100.00)).unwrap();
|
|
|
|
let request = BankTransferRequest {
|
|
from_account: "FROM001".to_string(),
|
|
to_account: "TO001".to_string(),
|
|
to_account_name: "转入账户".to_string(),
|
|
to_bank_code: "MOCK".to_string(),
|
|
amount: dec!(5000.00),
|
|
remark: None,
|
|
business_no: "TXN002".to_string(),
|
|
};
|
|
|
|
let response = client.transfer(request).await.unwrap();
|
|
|
|
assert!(!response.success);
|
|
assert_eq!(response.error_code, Some("INSUFFICIENT_BALANCE".to_string()));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_transfer_account_not_found() {
|
|
let client = MockBankClient::new();
|
|
|
|
let request = BankTransferRequest {
|
|
from_account: "NONEXISTENT".to_string(),
|
|
to_account: "TO001".to_string(),
|
|
to_account_name: "转入账户".to_string(),
|
|
to_bank_code: "MOCK".to_string(),
|
|
amount: dec!(100.00),
|
|
remark: None,
|
|
business_no: "TXN003".to_string(),
|
|
};
|
|
|
|
let response = client.transfer(request).await.unwrap();
|
|
|
|
assert!(!response.success);
|
|
assert_eq!(response.error_code, Some("ACCOUNT_NOT_FOUND".to_string()));
|
|
}
|
|
|
|
// ==================== 转账金额边界测试 ====================
|
|
|
|
#[tokio::test]
|
|
async fn test_transfer_zero_amount() {
|
|
let client = MockBankClient::new();
|
|
client.create_account("FROM001", "转出账户", dec!(10000.00)).unwrap();
|
|
client.create_account("TO001", "转入账户", dec!(0.00)).unwrap();
|
|
|
|
let request = BankTransferRequest {
|
|
from_account: "FROM001".to_string(),
|
|
to_account: "TO001".to_string(),
|
|
to_account_name: "转入账户".to_string(),
|
|
to_bank_code: "MOCK".to_string(),
|
|
amount: dec!(0.00),
|
|
remark: None,
|
|
business_no: "TXN004".to_string(),
|
|
};
|
|
|
|
let response = client.transfer(request).await.unwrap();
|
|
// 零金额转账应该成功(但没有实际资金移动)
|
|
assert!(response.success);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_transfer_exact_balance() {
|
|
let client = MockBankClient::new();
|
|
client.create_account("FROM001", "转出账户", dec!(1000.00)).unwrap();
|
|
client.create_account("TO001", "转入账户", dec!(0.00)).unwrap();
|
|
|
|
let request = BankTransferRequest {
|
|
from_account: "FROM001".to_string(),
|
|
to_account: "TO001".to_string(),
|
|
to_account_name: "转入账户".to_string(),
|
|
to_bank_code: "MOCK".to_string(),
|
|
amount: dec!(1000.00), // 刚好扣光
|
|
remark: None,
|
|
business_no: "TXN005".to_string(),
|
|
};
|
|
|
|
let response = client.transfer(request).await.unwrap();
|
|
assert!(response.success);
|
|
|
|
let balance = client.query_balance("FROM001").await.unwrap();
|
|
assert_eq!(balance.balance, dec!(0.00));
|
|
}
|
|
|
|
// ==================== 多笔转账测试 ====================
|
|
|
|
#[tokio::test]
|
|
async fn test_multiple_transfers() {
|
|
let client = MockBankClient::new();
|
|
client.create_account("FROM001", "转出账户", dec!(10000.00)).unwrap();
|
|
client.create_account("TO001", "转入账户1", dec!(0.00)).unwrap();
|
|
client.create_account("TO002", "转入账户2", dec!(0.00)).unwrap();
|
|
|
|
// 第一笔转账
|
|
let request1 = BankTransferRequest {
|
|
from_account: "FROM001".to_string(),
|
|
to_account: "TO001".to_string(),
|
|
to_account_name: "转入账户1".to_string(),
|
|
to_bank_code: "MOCK".to_string(),
|
|
amount: dec!(3000.00),
|
|
remark: None,
|
|
business_no: "TXN006".to_string(),
|
|
};
|
|
let response1 = client.transfer(request1).await.unwrap();
|
|
assert!(response1.success);
|
|
|
|
// 第二笔转账
|
|
let request2 = BankTransferRequest {
|
|
from_account: "FROM001".to_string(),
|
|
to_account: "TO002".to_string(),
|
|
to_account_name: "转入账户2".to_string(),
|
|
to_bank_code: "MOCK".to_string(),
|
|
amount: dec!(2000.00),
|
|
remark: None,
|
|
business_no: "TXN007".to_string(),
|
|
};
|
|
let response2 = client.transfer(request2).await.unwrap();
|
|
assert!(response2.success);
|
|
|
|
// 验证余额
|
|
let from_balance = client.query_balance("FROM001").await.unwrap();
|
|
assert_eq!(from_balance.balance, dec!(5000.00));
|
|
|
|
let to1_balance = client.query_balance("TO001").await.unwrap();
|
|
assert_eq!(to1_balance.balance, dec!(3000.00));
|
|
|
|
let to2_balance = client.query_balance("TO002").await.unwrap();
|
|
assert_eq!(to2_balance.balance, dec!(2000.00));
|
|
}
|
|
|
|
// ==================== 交易查询测试 ====================
|
|
|
|
#[tokio::test]
|
|
async fn test_query_transaction_status() {
|
|
let client = MockBankClient::new();
|
|
client.create_account("FROM001", "转出账户", dec!(10000.00)).unwrap();
|
|
client.create_account("TO001", "转入账户", dec!(0.00)).unwrap();
|
|
|
|
let request = BankTransferRequest {
|
|
from_account: "FROM001".to_string(),
|
|
to_account: "TO001".to_string(),
|
|
to_account_name: "转入账户".to_string(),
|
|
to_bank_code: "MOCK".to_string(),
|
|
amount: dec!(1000.00),
|
|
remark: None,
|
|
business_no: "TXN008".to_string(),
|
|
};
|
|
|
|
let response = client.transfer(request).await.unwrap();
|
|
assert!(response.success);
|
|
|
|
// 查询交易状态
|
|
let status = client.query_transaction_status("TXN008").await.unwrap();
|
|
assert!(status.success);
|
|
assert_eq!(status.bank_ref_no, response.bank_ref_no);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_query_nonexistent_transaction() {
|
|
let client = MockBankClient::new();
|
|
|
|
let status = client.query_transaction_status("NONEXISTENT").await.unwrap();
|
|
assert!(!status.success);
|
|
assert_eq!(status.error_code, Some("TXN_NOT_FOUND".to_string()));
|
|
}
|
|
|