2026-03-13 15:13:18 +08:00
|
|
|
|
"""
|
|
|
|
|
|
API 端点测试
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-03-18 15:01:58 +08:00
|
|
|
|
from routers.api import file_service
|
|
|
|
|
|
|
2026-03-13 15:13:18 +08:00
|
|
|
|
|
|
|
|
|
|
def test_root_endpoint(client):
|
|
|
|
|
|
"""测试根路径"""
|
|
|
|
|
|
response = client.get("/")
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
|
assert data["status"] == "running"
|
|
|
|
|
|
assert "swagger_docs" in data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_health_check(client):
|
|
|
|
|
|
"""测试健康检查端点"""
|
|
|
|
|
|
response = client.get("/health")
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
|
assert data["status"] == "healthy"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_token_success(client, sample_token_request):
|
|
|
|
|
|
"""测试获取 Token - 成功场景"""
|
|
|
|
|
|
response = client.post("/account/common/getToken", data=sample_token_request)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
|
assert data["code"] == "200"
|
|
|
|
|
|
assert "token" in data["data"]
|
|
|
|
|
|
assert "projectId" in data["data"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_token_error_40101(client):
|
|
|
|
|
|
"""测试获取 Token - 错误场景 40101"""
|
|
|
|
|
|
request_data = {
|
|
|
|
|
|
"projectNo": "test_error_40101",
|
|
|
|
|
|
"entityName": "测试企业",
|
|
|
|
|
|
"userId": "902001",
|
|
|
|
|
|
"userName": "902001",
|
|
|
|
|
|
"appId": "remote_app",
|
|
|
|
|
|
"appSecretCode": "test_secret_code_12345",
|
|
|
|
|
|
"role": "VIEWER",
|
|
|
|
|
|
"orgCode": "902000",
|
|
|
|
|
|
"departmentCode": "902000",
|
|
|
|
|
|
}
|
|
|
|
|
|
response = client.post("/account/common/getToken", data=request_data)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
|
assert data["code"] == "40101"
|
|
|
|
|
|
assert data["successResponse"] == False
|
2026-03-13 16:38:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fetch_inner_flow_success(client, sample_inner_flow_request):
|
|
|
|
|
|
"""测试拉取行内流水 - 成功场景"""
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
|
"/watson/api/project/getJZFileOrZjrcuFile",
|
|
|
|
|
|
data=sample_inner_flow_request
|
|
|
|
|
|
)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
|
assert data["code"] == "200"
|
|
|
|
|
|
assert data["successResponse"] == True
|
|
|
|
|
|
assert isinstance(data["data"], list)
|
|
|
|
|
|
assert len(data["data"]) == 1
|
|
|
|
|
|
assert isinstance(data["data"][0], int)
|
|
|
|
|
|
assert 10000 <= data["data"][0] <= 99999
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fetch_inner_flow_error_501014(client):
|
|
|
|
|
|
"""测试拉取行内流水 - 错误场景 501014"""
|
|
|
|
|
|
request_data = {
|
|
|
|
|
|
"groupId": 1001,
|
|
|
|
|
|
"customerNo": "test_error_501014",
|
|
|
|
|
|
"dataChannelCode": "test_code",
|
|
|
|
|
|
"requestDateId": 20240101,
|
|
|
|
|
|
"dataStartDateId": 20240101,
|
|
|
|
|
|
"dataEndDateId": 20240131,
|
|
|
|
|
|
"uploadUserId": 902001,
|
|
|
|
|
|
}
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
|
"/watson/api/project/getJZFileOrZjrcuFile",
|
|
|
|
|
|
data=request_data
|
|
|
|
|
|
)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
|
assert data["code"] == "501014"
|
|
|
|
|
|
assert data["successResponse"] == False
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-18 15:01:58 +08:00
|
|
|
|
def test_fetch_inner_flow_followed_by_upload_status(client):
|
|
|
|
|
|
"""拉取行内流水后,上传状态查询应命中同一条绑定记录。"""
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
|
"/watson/api/project/getJZFileOrZjrcuFile",
|
|
|
|
|
|
data={
|
|
|
|
|
|
"groupId": 1001,
|
|
|
|
|
|
"customerNo": "test_customer_002",
|
|
|
|
|
|
"dataChannelCode": "test_code",
|
|
|
|
|
|
"requestDateId": 20240101,
|
|
|
|
|
|
"dataStartDateId": 20240101,
|
|
|
|
|
|
"dataEndDateId": 20240131,
|
|
|
|
|
|
"uploadUserId": 902001,
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
log_id = response.json()["data"][0]
|
|
|
|
|
|
|
|
|
|
|
|
assert log_id in file_service.file_records
|
|
|
|
|
|
record = file_service.file_records[log_id]
|
|
|
|
|
|
|
|
|
|
|
|
upload_response = client.get(
|
|
|
|
|
|
f"/watson/api/project/bs/upload?groupId=1001&logId={log_id}"
|
|
|
|
|
|
)
|
|
|
|
|
|
assert upload_response.status_code == 200
|
|
|
|
|
|
upload_data = upload_response.json()
|
|
|
|
|
|
|
|
|
|
|
|
assert upload_data["code"] == "200"
|
|
|
|
|
|
assert upload_data["successResponse"] is True
|
|
|
|
|
|
assert len(upload_data["data"]["logs"]) == 1
|
|
|
|
|
|
|
|
|
|
|
|
log = upload_data["data"]["logs"][0]
|
|
|
|
|
|
assert log["enterpriseNameList"] == [record.primary_enterprise_name]
|
|
|
|
|
|
assert log["accountNoList"] == [record.primary_account_no]
|
|
|
|
|
|
assert log["enterpriseNameList"][0] == record.primary_enterprise_name
|
|
|
|
|
|
assert log["accountNoList"][0] == record.primary_account_no
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-18 15:50:28 +08:00
|
|
|
|
def test_upload_file_followed_by_upload_status_reads_real_record(client, monkeypatch):
|
|
|
|
|
|
"""上传文件后,上传状态查询应优先返回真实记录而不是 deterministic 回退。"""
|
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
|
file_service,
|
|
|
|
|
|
"_generate_primary_binding",
|
|
|
|
|
|
lambda: ("上传主体", "6222777788889999"),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
upload_response = client.post(
|
|
|
|
|
|
"/watson/api/project/remoteUploadSplitFile",
|
|
|
|
|
|
data={"groupId": 1001},
|
|
|
|
|
|
files={"files": ("测试文件.csv", b"mock", "text/csv")},
|
|
|
|
|
|
)
|
|
|
|
|
|
assert upload_response.status_code == 200
|
|
|
|
|
|
upload_data = upload_response.json()
|
|
|
|
|
|
upload_log = upload_data["data"]["uploadLogList"][0]
|
|
|
|
|
|
|
|
|
|
|
|
log_id = upload_log["logId"]
|
|
|
|
|
|
status_response = client.get(f"/watson/api/project/bs/upload?groupId=1001&logId={log_id}")
|
|
|
|
|
|
assert status_response.status_code == 200
|
|
|
|
|
|
status_data = status_response.json()
|
|
|
|
|
|
status_log = status_data["data"]["logs"][0]
|
|
|
|
|
|
|
|
|
|
|
|
assert status_log["enterpriseNameList"] == upload_log["enterpriseNameList"]
|
|
|
|
|
|
assert status_log["accountNoList"] == upload_log["accountNoList"]
|
|
|
|
|
|
assert status_log["enterpriseNameList"] == ["上传主体"]
|
|
|
|
|
|
assert status_log["accountNoList"] == ["6222777788889999"]
|
|
|
|
|
|
assert len(status_log["enterpriseNameList"]) == 1
|
|
|
|
|
|
assert len(status_log["accountNoList"]) == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-18 15:01:58 +08:00
|
|
|
|
def test_fetch_inner_flow_marks_pending_complete(client):
|
|
|
|
|
|
"""拉取行内流水后,getpendings 应返回未解析状态。"""
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
|
"/watson/api/project/getJZFileOrZjrcuFile",
|
|
|
|
|
|
data={
|
|
|
|
|
|
"groupId": 1001,
|
|
|
|
|
|
"customerNo": "test_customer_003",
|
|
|
|
|
|
"dataChannelCode": "test_code",
|
|
|
|
|
|
"requestDateId": 20240101,
|
|
|
|
|
|
"dataStartDateId": 20240101,
|
|
|
|
|
|
"dataEndDateId": 20240131,
|
|
|
|
|
|
"uploadUserId": 902001,
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
log_id = response.json()["data"][0]
|
|
|
|
|
|
|
|
|
|
|
|
pending_response = client.post(
|
|
|
|
|
|
"/watson/api/project/upload/getpendings",
|
|
|
|
|
|
data={"groupId": 1001, "inprogressList": str(log_id)},
|
|
|
|
|
|
)
|
|
|
|
|
|
assert pending_response.status_code == 200
|
|
|
|
|
|
pending_data = pending_response.json()
|
|
|
|
|
|
|
|
|
|
|
|
assert pending_data["data"]["parsing"] is False
|
|
|
|
|
|
assert len(pending_data["data"]["pendingList"]) == 1
|
|
|
|
|
|
assert pending_data["data"]["pendingList"][0]["logId"] == log_id
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-13 16:38:07 +08:00
|
|
|
|
def test_get_upload_status_with_log_id(client):
|
|
|
|
|
|
"""测试带 logId 参数查询返回非空 logs"""
|
|
|
|
|
|
response = client.get("/watson/api/project/bs/upload?groupId=1000&logId=13994")
|
|
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
|
|
|
|
|
|
|
# 验证基本响应结构
|
|
|
|
|
|
assert data["code"] == "200"
|
|
|
|
|
|
assert data["status"] == "200"
|
|
|
|
|
|
assert data["successResponse"] is True
|
|
|
|
|
|
|
|
|
|
|
|
# 验证 logs 不为空
|
|
|
|
|
|
assert len(data["data"]["logs"]) == 1
|
|
|
|
|
|
|
|
|
|
|
|
# 验证返回的 logId 正确
|
|
|
|
|
|
log = data["data"]["logs"][0]
|
|
|
|
|
|
assert log["logId"] == 13994
|
|
|
|
|
|
|
|
|
|
|
|
# 验证固定成功状态
|
|
|
|
|
|
assert log["status"] == -5
|
|
|
|
|
|
assert log["uploadStatusDesc"] == "data.wait.confirm.newaccount"
|
|
|
|
|
|
|
|
|
|
|
|
# 验证 logMeta 格式正确
|
|
|
|
|
|
assert log["logMeta"] == "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_upload_status_without_log_id(client):
|
|
|
|
|
|
"""测试不带 logId 参数查询返回空 logs 数组"""
|
|
|
|
|
|
response = client.get("/watson/api/project/bs/upload?groupId=1000")
|
|
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
|
|
|
|
|
|
|
# 验证基本响应结构
|
|
|
|
|
|
assert data["code"] == "200"
|
|
|
|
|
|
assert data["status"] == "200"
|
|
|
|
|
|
assert data["successResponse"] is True
|
|
|
|
|
|
|
|
|
|
|
|
# 验证 logs 为空
|
|
|
|
|
|
assert len(data["data"]["logs"]) == 0
|
|
|
|
|
|
|
|
|
|
|
|
# 验证其他字段存在
|
|
|
|
|
|
assert data["data"]["status"] == ""
|
|
|
|
|
|
assert data["data"]["accountId"] == 8954
|
|
|
|
|
|
assert data["data"]["currency"] == "CNY"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_deterministic_data_generation(client):
|
|
|
|
|
|
"""测试相同 logId 多次查询返回相同的核心字段值"""
|
|
|
|
|
|
# 第一次查询
|
|
|
|
|
|
response1 = client.get("/watson/api/project/bs/upload?groupId=1000&logId=13994")
|
|
|
|
|
|
log1 = response1.json()["data"]["logs"][0]
|
|
|
|
|
|
|
|
|
|
|
|
# 第二次查询
|
|
|
|
|
|
response2 = client.get("/watson/api/project/bs/upload?groupId=1000&logId=13994")
|
|
|
|
|
|
log2 = response2.json()["data"]["logs"][0]
|
|
|
|
|
|
|
|
|
|
|
|
# 验证关键字段相同
|
|
|
|
|
|
assert log1["logId"] == log2["logId"]
|
|
|
|
|
|
assert log1["bankName"] == log2["bankName"]
|
|
|
|
|
|
assert log1["accountNoList"] == log2["accountNoList"]
|
|
|
|
|
|
assert log1["enterpriseNameList"] == log2["enterpriseNameList"]
|
2026-03-18 15:50:28 +08:00
|
|
|
|
assert len(log1["accountNoList"]) == 1
|
|
|
|
|
|
assert len(log1["enterpriseNameList"]) == 1
|
2026-03-13 16:38:07 +08:00
|
|
|
|
assert log1["status"] == log2["status"]
|
|
|
|
|
|
assert log1["logMeta"] == log2["logMeta"]
|
|
|
|
|
|
assert log1["templateName"] == log2["templateName"]
|
2026-03-18 15:50:28 +08:00
|
|
|
|
assert log1["fileUploadTime"] == log2["fileUploadTime"]
|
2026-03-13 16:38:07 +08:00
|
|
|
|
assert log1["trxDateStartId"] == log2["trxDateStartId"]
|
|
|
|
|
|
assert log1["trxDateEndId"] == log2["trxDateEndId"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_field_completeness(client):
|
|
|
|
|
|
"""测试返回数据包含完整的 26 个字段"""
|
|
|
|
|
|
response = client.get("/watson/api/project/bs/upload?groupId=1000&logId=13994")
|
|
|
|
|
|
log = response.json()["data"]["logs"][0]
|
|
|
|
|
|
|
|
|
|
|
|
# 验证所有必需字段存在
|
|
|
|
|
|
required_fields = [
|
|
|
|
|
|
"accountNoList", "bankName", "dataTypeInfo", "downloadFileName",
|
|
|
|
|
|
"enterpriseNameList", "fileSize", "fileUploadBy", "fileUploadByUserName",
|
|
|
|
|
|
"fileUploadTime", "isSplit", "leId", "logId", "logMeta", "logType",
|
|
|
|
|
|
"loginLeId", "lostHeader", "realBankName", "rows", "source", "status",
|
|
|
|
|
|
"templateName", "totalRecords", "trxDateEndId", "trxDateStartId",
|
|
|
|
|
|
"uploadFileName", "uploadStatusDesc"
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for field in required_fields:
|
|
|
|
|
|
assert field in log, f"缺少字段: {field}"
|
2026-03-16 16:25:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_delete_files_accepts_array_style_log_ids(client):
|
|
|
|
|
|
"""测试删除文件接口兼容数组风格的 logIds 入参"""
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
|
"/watson/api/project/batchDeleteUploadFile",
|
|
|
|
|
|
data={
|
|
|
|
|
|
"groupId": 1000,
|
|
|
|
|
|
"logIds": "[50689]",
|
|
|
|
|
|
"userId": 902001,
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
|
assert data["code"] == "200 OK"
|
|
|
|
|
|
assert data["message"] == "delete.files.success"
|