收敛Mock文件记录主体账号绑定模型

This commit is contained in:
wkc
2026-03-18 14:51:09 +08:00
parent 883b370e4b
commit 0120d097be
2 changed files with 99 additions and 25 deletions

View File

@@ -21,6 +21,8 @@ class FileRecord:
parsing: bool = True # True表示正在解析 parsing: bool = True # True表示正在解析
# 新增字段 - 账号和主体信息 # 新增字段 - 账号和主体信息
primary_enterprise_name: str = ""
primary_account_no: str = ""
account_no_list: List[str] = field(default_factory=list) account_no_list: List[str] = field(default_factory=list)
enterprise_name_list: List[str] = field(default_factory=list) enterprise_name_list: List[str] = field(default_factory=list)
@@ -74,6 +76,27 @@ class FileService:
else: else:
return "ZJRCU", "ZJRCU_T251114" return "ZJRCU", "ZJRCU_T251114"
def _generate_primary_binding(self) -> tuple:
"""生成单一稳定的本方主体/本方账号绑定。"""
primary_account_no = f"{random.randint(10000000000, 99999999999)}"
primary_enterprise_name = "测试主体"
return primary_enterprise_name, primary_account_no
def _generate_primary_binding_from_rng(self, rng: random.Random) -> tuple:
"""使用局部随机源生成单一稳定的本方主体/本方账号绑定。"""
primary_account_no = f"{rng.randint(10000000000, 99999999999)}"
primary_enterprise_name = "测试主体"
return primary_enterprise_name, primary_account_no
def _build_primary_binding_lists(
self, primary_enterprise_name: str, primary_account_no: str
) -> dict:
"""基于主绑定事实源构建列表字段。"""
return {
"accountNoList": [primary_account_no],
"enterpriseNameList": [primary_enterprise_name],
}
async def upload_file( async def upload_file(
self, group_id: int, file: UploadFile, background_tasks: BackgroundTasks self, group_id: int, file: UploadFile, background_tasks: BackgroundTasks
) -> Dict: ) -> Dict:
@@ -100,9 +123,11 @@ class FileService:
trx_date_start_id = int(start_date.strftime("%Y%m%d")) trx_date_start_id = int(start_date.strftime("%Y%m%d"))
trx_date_end_id = int(end_date.strftime("%Y%m%d")) trx_date_end_id = int(end_date.strftime("%Y%m%d"))
# 生成随机账号和主体 # 生成单一主绑定
account_no = f"{random.randint(10000000000, 99999999999)}" primary_enterprise_name, primary_account_no = self._generate_primary_binding()
enterprise_names = ["测试主体"] if random.random() > 0.3 else [""] binding_lists = self._build_primary_binding_lists(
primary_enterprise_name, primary_account_no
)
# 创建完整的文件记录 # 创建完整的文件记录
file_record = FileRecord( file_record = FileRecord(
@@ -113,8 +138,10 @@ class FileService:
bank_name=bank_name, bank_name=bank_name,
real_bank_name=bank_name, real_bank_name=bank_name,
template_name=template_name, template_name=template_name,
account_no_list=[account_no], primary_enterprise_name=primary_enterprise_name,
enterprise_name_list=enterprise_names, primary_account_no=primary_account_no,
account_no_list=binding_lists["accountNoList"],
enterprise_name_list=binding_lists["enterpriseNameList"],
le_id=10000 + random.randint(0, 9999), le_id=10000 + random.randint(0, 9999),
login_le_id=10000 + random.randint(0, 9999), login_le_id=10000 + random.randint(0, 9999),
file_size=random.randint(10000, 100000), file_size=random.randint(10000, 100000),
@@ -143,19 +170,21 @@ class FileService:
str(file_record.log_id): [ str(file_record.log_id): [
{ {
"bank": file_record.bank_name, "bank": file_record.bank_name,
"accountName": file_record.enterprise_name_list[0] if file_record.enterprise_name_list else "", "accountName": file_record.primary_enterprise_name,
"accountNo": file_record.account_no_list[0] if file_record.account_no_list else "", "accountNo": file_record.primary_account_no,
"currency": "CNY" "currency": "CNY"
} }
] ]
}, },
"uploadLogList": [ "uploadLogList": [
{ {
"accountNoList": file_record.account_no_list, **self._build_primary_binding_lists(
file_record.primary_enterprise_name,
file_record.primary_account_no,
),
"bankName": file_record.bank_name, "bankName": file_record.bank_name,
"dataTypeInfo": file_record.data_type_info, "dataTypeInfo": file_record.data_type_info,
"downloadFileName": file_record.download_file_name, "downloadFileName": file_record.download_file_name,
"enterpriseNameList": file_record.enterprise_name_list,
"filePackageId": file_record.file_package_id, "filePackageId": file_record.file_package_id,
"fileSize": file_record.file_size, "fileSize": file_record.file_size,
"fileUploadBy": file_record.file_upload_by, "fileUploadBy": file_record.file_upload_by,
@@ -197,7 +226,9 @@ class FileService:
if log_id in self.file_records: if log_id in self.file_records:
self.file_records[log_id].parsing = False self.file_records[log_id].parsing = False
def _generate_deterministic_record(self, log_id: int, group_id: int) -> dict: def _generate_deterministic_record(
self, log_id: int, group_id: int, rng: random.Random
) -> dict:
""" """
基于 logId 生成确定性的文件记录 基于 logId 生成确定性的文件记录
@@ -215,39 +246,40 @@ class FileService:
("ZJRCU", "ZJRCU_T251114") ("ZJRCU", "ZJRCU_T251114")
] ]
bank_name, template_name = random.choice(bank_options) bank_name, template_name = rng.choice(bank_options)
# 生成交易日期范围 # 生成交易日期范围
end_date = datetime.now() end_date = datetime.now()
start_date = end_date - timedelta(days=random.randint(90, 365)) start_date = end_date - timedelta(days=rng.randint(90, 365))
# 生成账号和主体 # 生成账号和主体
account_no = f"{random.randint(10000000000, 99999999999)}" primary_enterprise_name, primary_account_no = self._generate_primary_binding_from_rng(rng)
enterprise_names = ["测试主体"] if random.random() > 0.3 else [""] binding_lists = self._build_primary_binding_lists(
primary_enterprise_name, primary_account_no
)
return { return {
"accountNoList": [account_no], **binding_lists,
"bankName": bank_name, "bankName": bank_name,
"dataTypeInfo": ["CSV", ","], "dataTypeInfo": ["CSV", ","],
"downloadFileName": f"测试文件_{log_id}.csv", "downloadFileName": f"测试文件_{log_id}.csv",
"enterpriseNameList": enterprise_names, "fileSize": rng.randint(10000, 100000),
"fileSize": random.randint(10000, 100000),
"fileUploadBy": 448, "fileUploadBy": 448,
"fileUploadByUserName": "admin@support.com", "fileUploadByUserName": "admin@support.com",
"fileUploadTime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "fileUploadTime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"isSplit": 0, "isSplit": 0,
"leId": 10000 + random.randint(0, 9999), "leId": 10000 + rng.randint(0, 9999),
"logId": log_id, "logId": log_id,
"logMeta": "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}", "logMeta": "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}",
"logType": "bankstatement", "logType": "bankstatement",
"loginLeId": 10000 + random.randint(0, 9999), "loginLeId": 10000 + rng.randint(0, 9999),
"lostHeader": [], "lostHeader": [],
"realBankName": bank_name, "realBankName": bank_name,
"rows": 0, "rows": 0,
"source": "http", "source": "http",
"status": -5, "status": -5,
"templateName": template_name, "templateName": template_name,
"totalRecords": random.randint(100, 300), "totalRecords": rng.randint(100, 300),
"trxDateEndId": int(end_date.strftime("%Y%m%d")), "trxDateEndId": int(end_date.strftime("%Y%m%d")),
"trxDateStartId": int(start_date.strftime("%Y%m%d")), "trxDateStartId": int(start_date.strftime("%Y%m%d")),
"uploadFileName": f"测试文件_{log_id}.pdf", "uploadFileName": f"测试文件_{log_id}.pdf",
@@ -257,11 +289,13 @@ class FileService:
def _build_log_detail(self, record: FileRecord) -> dict: def _build_log_detail(self, record: FileRecord) -> dict:
"""构建日志详情对象""" """构建日志详情对象"""
return { return {
"accountNoList": record.account_no_list, **self._build_primary_binding_lists(
record.primary_enterprise_name,
record.primary_account_no,
),
"bankName": record.bank_name, "bankName": record.bank_name,
"dataTypeInfo": record.data_type_info, "dataTypeInfo": record.data_type_info,
"downloadFileName": record.download_file_name, "downloadFileName": record.download_file_name,
"enterpriseNameList": record.enterprise_name_list,
"fileSize": record.file_size, "fileSize": record.file_size,
"fileUploadBy": record.file_upload_by, "fileUploadBy": record.file_upload_by,
"fileUploadByUserName": record.file_upload_by_user_name, "fileUploadByUserName": record.file_upload_by_user_name,
@@ -333,11 +367,11 @@ class FileService:
logs = [] logs = []
if log_id: if log_id:
# 使用 logId 作为随机种子,确保相同 logId 返回相同数据 # 使用局部随机源,避免污染全局随机状态
random.seed(log_id) rng = random.Random(log_id)
# 生成确定性的文件记录 # 生成确定性的文件记录
record = self._generate_deterministic_record(log_id, group_id) record = self._generate_deterministic_record(log_id, group_id, rng)
logs.append(record) logs.append(record)
# 返回响应 # 返回响应

View File

@@ -0,0 +1,40 @@
"""
FileService 单一主绑定语义测试
"""
import asyncio
import io
from fastapi import BackgroundTasks
from fastapi.datastructures import UploadFile
from services.file_service import FileService
def test_upload_file_primary_binding_response(monkeypatch):
"""同一 logId 的主绑定必须稳定且只保留一组主体/账号信息。"""
service = FileService()
monkeypatch.setattr(
service,
"_generate_primary_binding",
lambda: ("测试主体A", "6222021234567890"),
)
background_tasks = BackgroundTasks()
file = UploadFile(filename="测试文件.csv", file=io.BytesIO(b"mock"))
response = asyncio.run(service.upload_file(1001, file, background_tasks))
log = response["data"]["uploadLogList"][0]
account_info = response["data"]["accountsOfLog"][str(log["logId"])][0]
record = service.file_records[log["logId"]]
assert log["enterpriseNameList"] == ["测试主体A"]
assert log["accountNoList"] == ["6222021234567890"]
assert account_info["accountName"] == "测试主体A"
assert account_info["accountNo"] == "6222021234567890"
assert record.primary_enterprise_name == "测试主体A"
assert record.primary_account_no == "6222021234567890"
assert record.enterprise_name_list == ["测试主体A"]
assert record.account_no_list == ["6222021234567890"]