Files
ccdi/lsfx-mock-server/tests/test_statement_service.py

230 lines
7.8 KiB
Python

"""
StatementService 主绑定注入测试
"""
from collections import Counter, defaultdict
from services.file_service import FileService
from services.statement_service import StatementService
from services.statement_rule_samples import (
DEFAULT_LARGE_TRANSACTION_THRESHOLDS,
build_large_transaction_seed_statements,
)
def test_generate_statements_should_include_seeded_samples_before_noise():
"""生成流水时必须先混入固定命中样本,而不是纯随机噪声。"""
service = StatementService()
statements = service._generate_statements(group_id=1000, log_id=20001, count=30)
assert len(statements) >= 30
assert any(item["userMemo"] == "购买房产首付款" for item in statements)
def test_large_transaction_seed_should_cover_all_eight_rules():
"""大额交易样本生成器必须覆盖 8 条已实现规则的关键口径。"""
statements = build_large_transaction_seed_statements(group_id=1000, log_id=20001)
assert any(
item["userMemo"] == "购买房产首付款" and item["drAmount"] > 0
for item in statements
)
assert any(
"" in item["userMemo"] and item["drAmount"] > 0
for item in statements
)
assert any(
item["crAmount"] > DEFAULT_LARGE_TRANSACTION_THRESHOLDS["SINGLE_TRANSACTION_AMOUNT"]
for item in statements
)
assert sum(
1
for item in statements
if item["customerName"] == "浙江远望贸易有限公司" and item["crAmount"] > 0
) >= 3
assert sum(
1
for item in statements
if item["cashType"] == "现金存款"
and item["crAmount"] > DEFAULT_LARGE_TRANSACTION_THRESHOLDS["LARGE_CASH_DEPOSIT"]
) >= 1
assert any(
item["userMemo"] == "手机银行转账"
and item["drAmount"] > DEFAULT_LARGE_TRANSACTION_THRESHOLDS["FREQUENT_TRANSFER"]
for item in statements
)
same_day_cash_deposits = [
item for item in statements
if item["cretNo"] == "330101198801010011"
and item["trxDate"].startswith("2026-03-10")
and item["crAmount"] > DEFAULT_LARGE_TRANSACTION_THRESHOLDS["LARGE_CASH_DEPOSIT"]
]
assert len(same_day_cash_deposits) >= (
DEFAULT_LARGE_TRANSACTION_THRESHOLDS["FREQUENT_CASH_DEPOSIT"] + 1
)
def test_large_transaction_seed_income_should_avoid_salary_exclusion():
"""收入样本不得误带工资代发关键词,否则会被后端过滤。"""
statements = build_large_transaction_seed_statements(group_id=1000, log_id=20001)
income_samples = [item for item in statements if item["crAmount"] > 0]
assert income_samples
assert all(item["customerName"] != "浙江兰溪农村商业银行股份有限公司" for item in income_samples)
assert all(
keyword not in item["userMemo"]
for item in income_samples
for keyword in ("代发", "工资", "奖金", "薪酬", "薪金")
)
def test_generate_statements_should_fill_noise_up_to_requested_count():
"""样本不足请求总数时,服务层需要自动补齐噪声流水。"""
service = StatementService()
statements = service._generate_statements(group_id=1000, log_id=20001, count=80)
assert len(statements) == 80
def test_generate_statements_should_only_use_recognizable_identity_cards():
"""命中样本和随机噪声都只能使用现库可识别的身份证号。"""
service = StatementService()
statements = service._generate_statements(group_id=1000, log_id=20005, count=1600)
assert {item["cretNo"] for item in statements}.issubset(
{
"330101198801010011",
"330101199001010022",
"330101198802020033",
"330101199202020044",
}
)
def test_get_bank_statement_should_keep_same_cached_result_for_same_log_id():
"""同一 logId 首次生成后应复用缓存,避免分页结果漂移。"""
service = StatementService()
page1 = service.get_bank_statement(
{"groupId": 1000, "logId": 30001, "pageNow": 1, "pageSize": 20}
)
page2 = service.get_bank_statement(
{"groupId": 1000, "logId": 30001, "pageNow": 1, "pageSize": 20}
)
assert page1["data"]["bankStatementList"] == page2["data"]["bankStatementList"]
def test_get_bank_statement_uses_primary_binding_from_file_service(monkeypatch):
"""同一 logId 的流水记录必须复用 FileService 中的主体与账号绑定。"""
file_service = FileService()
statement_service = StatementService(file_service=file_service)
monkeypatch.setattr(
file_service,
"_generate_primary_binding",
lambda: ("绑定主体", "6222000011112222"),
)
response = file_service.fetch_inner_flow(
{
"groupId": 1001,
"customerNo": "customer_001",
"dataChannelCode": "test",
"requestDateId": 20240101,
"dataStartDateId": 20240101,
"dataEndDateId": 20240131,
"uploadUserId": 902001,
}
)
log_id = response["data"][0]
record = file_service.file_records[log_id]
statement_response = statement_service.get_bank_statement(
{
"groupId": 1001,
"logId": log_id,
"pageNow": 1,
"pageSize": 5,
}
)
statements = statement_response["data"]["bankStatementList"]
assert statements
assert all(item["leName"] == record.primary_enterprise_name for item in statements)
assert all(item["accountMaskNo"] == record.primary_account_no for item in statements)
def test_get_bank_statement_contains_large_transaction_hit_samples(monkeypatch):
"""流水 Mock 首次生成时必须稳定包含可命中大额交易规则的样本簇。"""
file_service = FileService()
statement_service = StatementService(file_service=file_service)
monkeypatch.setattr(
file_service,
"_generate_primary_binding",
lambda: ("命中主体", "6222000099998888"),
)
response = file_service.fetch_inner_flow(
{
"groupId": 1001,
"customerNo": "customer_large_transaction",
"dataChannelCode": "test",
"requestDateId": 20240101,
"dataStartDateId": 20240101,
"dataEndDateId": 20240131,
"uploadUserId": 902001,
}
)
log_id = response["data"][0]
statement_response = statement_service.get_bank_statement(
{
"groupId": 1001,
"logId": log_id,
"pageNow": 1,
"pageSize": 2000,
}
)
statements = statement_response["data"]["bankStatementList"]
assert statements
assert any(
item["cretNo"] in {
"330101198801010011",
"330101199001010022",
"330101198802020033",
"330101199202020044",
}
for item in statements
)
assert any("房产首付款" in item["userMemo"] for item in statements)
assert any("" in item["userMemo"] or "税务" in item["customerName"] for item in statements)
income_amounts = defaultdict(float)
cash_deposit_daily_counter = Counter()
has_large_transfer = False
for item in statements:
if (
item["cretNo"] == "330101198802020033"
and item["customerName"] == "浙江远望贸易有限公司"
and item["crAmount"] > 0
):
income_amounts[(item["cretNo"], item["customerName"])] += item["crAmount"]
if item["crAmount"] > 2000001 and "现金" in item["cashType"]:
cash_deposit_daily_counter[(item["cretNo"], item["trxDate"][:10])] += 1
if item["drAmount"] > 100001 and item["userMemo"] == "手机银行转账":
has_large_transfer = True
assert any(amount > 50000001 for amount in income_amounts.values())
assert any(count >= 6 for count in cash_deposit_daily_counter.values())
assert has_large_transfer