优化
This commit is contained in:
@@ -61,7 +61,7 @@ spring:
|
||||
- id: warehouse-service
|
||||
uri: lb://warehouse-service
|
||||
predicates:
|
||||
- Path=/api/merchant/v1/warehouses/**,/api/merchant/v1/partners/**,/api/platform/v1/warehouses/**,/api/platform/v1/warehouse-admin/**,/api/platform/v1/partners/**,/api/platform/v1/express-companies/**,/api/platform/v1/pallets/**,/api/platform/v1/locations/**,/api/platform/v1/overseas-storage-fees/**,/api/platform/v1/iam/tenants/*/overseas-storage-fees/**
|
||||
- Path=/api/merchant/v1/warehouses/**,/api/merchant/v1/partners/**,/api/platform/v1/warehouses/**,/api/platform/v1/warehouse-admin/**,/api/platform/v1/partners/**,/api/platform/v1/express-companies/**,/api/platform/v1/pallets/**,/api/platform/v1/locations/**,/api/platform/v1/overseas-storage-fees/**,/api/platform/v1/iam/tenants/{tenantId}/overseas-storage-fees,/api/platform/v1/iam/tenants/{tenantId}/overseas-storage-fees/**
|
||||
- id: file-service
|
||||
uri: lb://file-service
|
||||
predicates:
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
{
|
||||
"reportName": "后台管理测试环境 HTTP API 验收",
|
||||
"marker": "ADMIN-API-20260808060030925",
|
||||
"gateway": "http://8.163.30.128:9000",
|
||||
"generatedAt": "2026-08-08T06:00:32.398Z",
|
||||
"startedAt": "2026-08-08T06:00:30.925Z",
|
||||
"success": false,
|
||||
"fatal": null,
|
||||
"summary": {
|
||||
"total": 7,
|
||||
"passed": 6,
|
||||
"failed": 1
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"name": "overseas-storage-fees",
|
||||
"startedAt": "2026-08-08T06:00:31.303Z",
|
||||
"finishedAt": "2026-08-08T06:00:32.398Z",
|
||||
"success": false,
|
||||
"passed": 5,
|
||||
"failed": 1,
|
||||
"fatal": null
|
||||
}
|
||||
],
|
||||
"scenarios": [],
|
||||
"deploymentBlockers": [
|
||||
{
|
||||
"module": "overseas-storage-fees",
|
||||
"scenario": "customer-overseas-storage-fee-lifecycle",
|
||||
"name": "查询客户海外仓费用配置",
|
||||
"evidence": "tenantId=2085969381372317698",
|
||||
"code": "S0500",
|
||||
"requestId": "43e1231e-8404-4be0-b8da-db4dd63133e3",
|
||||
"traceId": "1ef03fcf4e954433bfc70ab7c226454d"
|
||||
}
|
||||
],
|
||||
"createdResources": {
|
||||
"overseasStorageFeeCustomer": {
|
||||
"tenantId": "2085969381372317698",
|
||||
"marker": "ADMIN-API-20260808060030925",
|
||||
"ownedByModule": true
|
||||
}
|
||||
},
|
||||
"excluded": [
|
||||
{
|
||||
"scope": "dashboard",
|
||||
"reason": "首页为静态演示数据,不调用后端 API"
|
||||
},
|
||||
{
|
||||
"scope": "hidden-prototypes",
|
||||
"reason": "隐藏原型页面尚无已确认后端契约"
|
||||
}
|
||||
],
|
||||
"securityNote": "报告不保存登录密码、Access Token、Refresh Token、图片二进制或 OCR 原文全文。",
|
||||
"steps": [
|
||||
{
|
||||
"module": "authentication",
|
||||
"scenario": null,
|
||||
"name": "平台管理员登录",
|
||||
"passed": true,
|
||||
"deploymentBlocker": false,
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/api/auth/login"
|
||||
},
|
||||
"http": 200,
|
||||
"code": "200",
|
||||
"message": "success",
|
||||
"requestId": "fe913670-d582-4ca0-8a4f-0d0ed77d1a26, 9871584d-e19d-408c-9b11-b7953a436b0f",
|
||||
"traceId": "7214d678e2c0433db416a817fd7d6c16",
|
||||
"evidence": "",
|
||||
"checkedAt": "2026-08-08T06:00:31.302Z"
|
||||
},
|
||||
{
|
||||
"module": "overseas-storage-fees",
|
||||
"scenario": null,
|
||||
"name": "检查测试联系电话可用性",
|
||||
"passed": true,
|
||||
"deploymentBlocker": false,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/iam/tenants/contact-phone-availability?contactPhone=16168831304"
|
||||
},
|
||||
"http": 200,
|
||||
"code": "200",
|
||||
"message": "success",
|
||||
"requestId": "99783175-a094-4677-913f-3b338405a38a, b9139302-91ea-4ca8-a257-8339d089b3ad",
|
||||
"traceId": "f2b5ed1db5a246e7a4ac88bd48784967",
|
||||
"evidence": "attempt=1",
|
||||
"checkedAt": "2026-08-08T06:00:31.356Z"
|
||||
},
|
||||
{
|
||||
"module": "overseas-storage-fees",
|
||||
"scenario": "customer-overseas-storage-fee-lifecycle",
|
||||
"name": "创建海外仓费用隔离客户",
|
||||
"passed": true,
|
||||
"deploymentBlocker": false,
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/api/platform/v1/iam/tenants"
|
||||
},
|
||||
"http": 200,
|
||||
"code": "200",
|
||||
"message": "success",
|
||||
"requestId": "0a5b9b08-171f-41de-b879-3ca2c6ba0ad9, 606ad7cc-e242-4d59-8267-78909a68c787",
|
||||
"traceId": "636b0885ae024db9bbd41a35416d128e",
|
||||
"evidence": "",
|
||||
"checkedAt": "2026-08-08T06:00:32.221Z"
|
||||
},
|
||||
{
|
||||
"module": "overseas-storage-fees",
|
||||
"scenario": "customer-overseas-storage-fee-lifecycle",
|
||||
"name": "海外仓费用隔离客户创建并带 marker",
|
||||
"passed": true,
|
||||
"deploymentBlocker": false,
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/api/platform/v1/iam/tenants"
|
||||
},
|
||||
"http": 200,
|
||||
"code": "200",
|
||||
"message": "success",
|
||||
"requestId": "0a5b9b08-171f-41de-b879-3ca2c6ba0ad9, 606ad7cc-e242-4d59-8267-78909a68c787",
|
||||
"traceId": "636b0885ae024db9bbd41a35416d128e",
|
||||
"evidence": "tenantId=2085969381372317698",
|
||||
"checkedAt": "2026-08-08T06:00:32.221Z"
|
||||
},
|
||||
{
|
||||
"module": "overseas-storage-fees",
|
||||
"scenario": "customer-overseas-storage-fee-lifecycle",
|
||||
"name": "查询客户海外仓费用配置",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/iam/tenants/2085969381372317698/overseas-storage-fees"
|
||||
},
|
||||
"http": 200,
|
||||
"code": "S0500",
|
||||
"message": "Internal server error",
|
||||
"requestId": "43e1231e-8404-4be0-b8da-db4dd63133e3",
|
||||
"traceId": "1ef03fcf4e954433bfc70ab7c226454d",
|
||||
"evidence": "tenantId=2085969381372317698",
|
||||
"checkedAt": "2026-08-08T06:00:32.258Z"
|
||||
},
|
||||
{
|
||||
"module": "overseas-storage-fees",
|
||||
"scenario": "customer-overseas-storage-fee-lifecycle",
|
||||
"name": "费用配置契约不可用时清理隔离客户",
|
||||
"passed": true,
|
||||
"deploymentBlocker": false,
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/api/platform/v1/iam/tenants/2085969381372317698/archive"
|
||||
},
|
||||
"http": 200,
|
||||
"code": "200",
|
||||
"message": "success",
|
||||
"requestId": "7ddac37a-a099-4f83-b8a8-e662245e62e1, c756d5e2-959b-41ab-99b5-74415583f40d",
|
||||
"traceId": "1878469a40be488abbbf6d3ce9300c8f",
|
||||
"evidence": "tenantId=2085969381372317698",
|
||||
"checkedAt": "2026-08-08T06:00:32.398Z"
|
||||
},
|
||||
{
|
||||
"module": "overseas-storage-fees",
|
||||
"scenario": "customer-overseas-storage-fee-lifecycle",
|
||||
"name": "隔离客户清理后停用",
|
||||
"passed": true,
|
||||
"deploymentBlocker": false,
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/api/platform/v1/iam/tenants/2085969381372317698/archive"
|
||||
},
|
||||
"http": 200,
|
||||
"code": "200",
|
||||
"message": "success",
|
||||
"requestId": "7ddac37a-a099-4f83-b8a8-e662245e62e1, c756d5e2-959b-41ab-99b5-74415583f40d",
|
||||
"traceId": "1878469a40be488abbbf6d3ce9300c8f",
|
||||
"evidence": "status=DISABLED",
|
||||
"checkedAt": "2026-08-08T06:00:32.398Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,705 @@
|
||||
{
|
||||
"reportName": "后台管理测试环境 HTTP API 验收",
|
||||
"marker": "ADMIN-API-20260808061243233",
|
||||
"gateway": "http://8.163.30.128:9000",
|
||||
"generatedAt": "2026-08-08T06:12:44.205Z",
|
||||
"startedAt": "2026-08-08T06:12:43.233Z",
|
||||
"success": false,
|
||||
"fatal": null,
|
||||
"summary": {
|
||||
"total": 25,
|
||||
"passed": 1,
|
||||
"failed": 24
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"name": "ocean-bookings",
|
||||
"startedAt": "2026-08-08T06:12:43.611Z",
|
||||
"finishedAt": "2026-08-08T06:12:43.833Z",
|
||||
"success": false,
|
||||
"passed": 0,
|
||||
"failed": 9,
|
||||
"fatal": "Cannot access 'create' before initialization"
|
||||
},
|
||||
{
|
||||
"name": "truck-appointments",
|
||||
"startedAt": "2026-08-08T06:12:43.834Z",
|
||||
"finishedAt": "2026-08-08T06:12:44.205Z",
|
||||
"success": false,
|
||||
"passed": 0,
|
||||
"failed": 15,
|
||||
"fatal": null
|
||||
}
|
||||
],
|
||||
"scenarios": [],
|
||||
"deploymentBlockers": [
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱列表分页与 keyword 查询",
|
||||
"evidence": "",
|
||||
"code": null,
|
||||
"requestId": "935b2577-51",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱列表分页结构正确",
|
||||
"evidence": "records=n/a",
|
||||
"code": null,
|
||||
"requestId": "935b2577-51",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 DRAFT 状态筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-52",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 SUBMITTED 状态筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "935b2577-53",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 CONFIRMED 状态筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-54",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 RELEASED 状态筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "935b2577-55",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 FAILED 状态筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-56",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 CANCELLED 状态筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "935b2577-57",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约分页 keyword 查询",
|
||||
"evidence": "",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-58",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约分页结构正确",
|
||||
"evidence": "records=n/a",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-58",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 DRAFT 筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "935b2577-59",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 PENDING_DISPATCH 筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-60",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 DISPATCHED 筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "935b2577-61",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 PICKUP_IN_PROGRESS 筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-62",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 ARRIVED_WAREHOUSE 筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "935b2577-63",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 LOADING 筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-64",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 DEPARTED 筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "935b2577-65",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 COMPLETED 筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-66",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 RESCHEDULED 筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "935b2577-67",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 EXCEPTION 筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-68",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 CANCELLED 筛选",
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"code": null,
|
||||
"requestId": "935b2577-69",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "创建隔离拖车预约",
|
||||
"evidence": "",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-70",
|
||||
"traceId": null
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约费用、司机车辆和地址字段完整",
|
||||
"evidence": "id=missing, status=missing",
|
||||
"code": null,
|
||||
"requestId": "b82ce6ff-70",
|
||||
"traceId": null
|
||||
}
|
||||
],
|
||||
"createdResources": {},
|
||||
"excluded": [
|
||||
{
|
||||
"scope": "dashboard",
|
||||
"reason": "首页为静态演示数据,不调用后端 API"
|
||||
},
|
||||
{
|
||||
"scope": "hidden-prototypes",
|
||||
"reason": "隐藏原型页面尚无已确认后端契约"
|
||||
}
|
||||
],
|
||||
"securityNote": "报告不保存登录密码、Access Token、Refresh Token、图片二进制或 OCR 原文全文。",
|
||||
"steps": [
|
||||
{
|
||||
"module": "authentication",
|
||||
"scenario": null,
|
||||
"name": "平台管理员登录",
|
||||
"passed": true,
|
||||
"deploymentBlocker": false,
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/api/auth/login"
|
||||
},
|
||||
"http": 200,
|
||||
"code": "200",
|
||||
"message": "success",
|
||||
"requestId": "4d3a980d-5349-47c6-8c96-8a31a6934744, 86c817c2-80bc-45c0-8de1-cc4e52242456",
|
||||
"traceId": "aae31740ac694e65ad8176b9e343096d",
|
||||
"evidence": "",
|
||||
"checkedAt": "2026-08-08T06:12:43.610Z"
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱列表分页与 keyword 查询",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&keyword=ADMIN-API-20260808061243233"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "935b2577-51",
|
||||
"traceId": null,
|
||||
"evidence": "",
|
||||
"checkedAt": "2026-08-08T06:12:43.669Z"
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱列表分页结构正确",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&keyword=ADMIN-API-20260808061243233"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "935b2577-51",
|
||||
"traceId": null,
|
||||
"evidence": "records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.669Z"
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 DRAFT 状态筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=DRAFT"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-52",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.693Z"
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 SUBMITTED 状态筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=SUBMITTED"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "935b2577-53",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.712Z"
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 CONFIRMED 状态筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=CONFIRMED"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-54",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.741Z"
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 RELEASED 状态筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=RELEASED"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "935b2577-55",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.772Z"
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 FAILED 状态筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=FAILED"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-56",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.801Z"
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": "ocean-booking-lifecycle",
|
||||
"name": "订舱 CANCELLED 状态筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=CANCELLED"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "935b2577-57",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.833Z"
|
||||
},
|
||||
{
|
||||
"module": "ocean-bookings",
|
||||
"scenario": null,
|
||||
"name": "模块执行",
|
||||
"passed": false,
|
||||
"deploymentBlocker": false,
|
||||
"request": null,
|
||||
"http": null,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": null,
|
||||
"traceId": null,
|
||||
"evidence": "Cannot access 'create' before initialization",
|
||||
"checkedAt": "2026-08-08T06:12:43.833Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约分页 keyword 查询",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&keyword=ADMIN-API-20260808061243233"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-58",
|
||||
"traceId": null,
|
||||
"evidence": "",
|
||||
"checkedAt": "2026-08-08T06:12:43.864Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约分页结构正确",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&keyword=ADMIN-API-20260808061243233"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-58",
|
||||
"traceId": null,
|
||||
"evidence": "records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.864Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 DRAFT 筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=DRAFT"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "935b2577-59",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.895Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 PENDING_DISPATCH 筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=PENDING_DISPATCH"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-60",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.925Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 DISPATCHED 筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=DISPATCHED"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "935b2577-61",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.956Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 PICKUP_IN_PROGRESS 筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=PICKUP_IN_PROGRESS"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-62",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:43.986Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 ARRIVED_WAREHOUSE 筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=ARRIVED_WAREHOUSE"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "935b2577-63",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:44.005Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 LOADING 筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=LOADING"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-64",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:44.037Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 DEPARTED 筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=DEPARTED"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "935b2577-65",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:44.064Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 COMPLETED 筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=COMPLETED"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-66",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:44.096Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 RESCHEDULED 筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=RESCHEDULED"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "935b2577-67",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:44.115Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 EXCEPTION 筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=EXCEPTION"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-68",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:44.142Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约 CANCELLED 筛选",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=CANCELLED"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "935b2577-69",
|
||||
"traceId": null,
|
||||
"evidence": "HTTP=404, code=missing, records=n/a",
|
||||
"checkedAt": "2026-08-08T06:12:44.173Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "创建隔离拖车预约",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/api/platform/v1/truck-appointments"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-70",
|
||||
"traceId": null,
|
||||
"evidence": "",
|
||||
"checkedAt": "2026-08-08T06:12:44.205Z"
|
||||
},
|
||||
{
|
||||
"module": "truck-appointments",
|
||||
"scenario": "truck-appointment-lifecycle",
|
||||
"name": "拖车预约费用、司机车辆和地址字段完整",
|
||||
"passed": false,
|
||||
"deploymentBlocker": true,
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/api/platform/v1/truck-appointments"
|
||||
},
|
||||
"http": 404,
|
||||
"code": null,
|
||||
"message": null,
|
||||
"requestId": "b82ce6ff-70",
|
||||
"traceId": null,
|
||||
"evidence": "id=missing, status=missing",
|
||||
"checkedAt": "2026-08-08T06:12:44.205Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
const MODULE = 'ocean-bookings'
|
||||
const OPTIONS = { scenario: 'ocean-booking-lifecycle', deploymentBlocker: true }
|
||||
const BASE = '/api/platform/v1/ocean-bookings'
|
||||
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
function codeOf(r) { return r?.body?.code == null ? 'missing' : String(r.body.code) }
|
||||
function missing(r) { return r?.http === 404 || r?.http === 405 || r?.http === 503 }
|
||||
function failure(r) { return [400, 401, 403, 404, 409, 503].includes(r?.http) || (r?.http === 200 && codeOf(r) !== '200') }
|
||||
async function request(context, method, path, body, headers = {}) {
|
||||
try {
|
||||
const response = await fetch(`${context.options.gateway}${path}`, { method, headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}`, ...(body === undefined ? {} : { 'Content-Type': 'application/json; charset=utf-8' }), ...headers }, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(60_000) })
|
||||
const raw = await response.text(); let payload = null
|
||||
try { payload = raw ? JSON.parse(raw) : null } catch { /* bounded raw evidence */ }
|
||||
return { method, path, http: response.status, body: payload, raw: payload === null ? raw.slice(0, 500) : null, requestId: payload?.requestId || response.headers.get('x-request-id'), traceId: payload?.traceId || response.headers.get('x-trace-id') }
|
||||
} catch (error) { return { method, path, http: 0, body: null, raw: String(error), requestId: null, traceId: null } }
|
||||
}
|
||||
function data(r) { return String(r?.body?.code) === '200' ? r.body.data : null }
|
||||
function draft(context, suffix) { return { customerId: '10001', customerName: `订舱验收客户-${suffix}`, shippingCompanyId: null, shippingCompanyName: `船公司-${suffix}`, bookingAgentId: null, bookingAgentName: `订舱代理-${suffix}`, vesselName: `验收船-${suffix}`, voyageNo: `V${suffix}`, departurePortCode: 'CNSZX', departurePortName: '深圳', destinationPortCode: 'USLAX', destinationPortName: '洛杉矶', placeOfDelivery: 'LAX', serviceTerm: 'CY/CY', freightTerm: 'PREPAID', estimatedSailingAt: '2026-09-01T08:00:00', estimatedArrivalAt: '2026-09-20T08:00:00', billOfLadingNo: `BL-${suffix}`, remark: context.marker, parties: [{ partyType: 'SHIPPER', partyName: `发货方-${suffix}` }, { partyType: 'CONSIGNEE', partyName: `收货方-${suffix}` }, { partyType: 'NOTIFY_PARTY', partyName: `通知方-${suffix}` }], containerRequirements: [{ containerType: '40HQ', containerKind: 'FCL', quantity: 1 }] } }
|
||||
async function create(context, suffix, key) { return request(context, 'POST', BASE, { expectedVersion: 0, data: draft(context, suffix) }, { 'Idempotency-Key': key }) }
|
||||
function step(context, name, r, pass, evidence, options = OPTIONS) { context.addStep(MODULE, name, r, pass, evidence, { ...options, deploymentBlocker: options.deploymentBlocker && !pass }) }
|
||||
|
||||
export async function run(context) {
|
||||
const suffix = context.marker.replace(/\D/g, '').slice(-10)
|
||||
const list = await request(context, 'GET', `${BASE}?page=1&size=5&keyword=${encodeURIComponent(context.marker)}`)
|
||||
const page = context.apiSuccess(MODULE, '订舱列表分页与 keyword 查询', list, '', OPTIONS)
|
||||
context.assert(MODULE, '订舱列表分页结构正确', list, Array.isArray(page?.records) && Number.isFinite(Number(page?.total)), `records=${page?.records?.length ?? 'n/a'}`, OPTIONS)
|
||||
const statuses = ['DRAFT', 'SUBMITTED', 'CONFIRMED', 'RELEASED', 'FAILED', 'CANCELLED']
|
||||
for (const status of statuses) { const r = await request(context, 'GET', `${BASE}?page=1&size=5&status=${status}`); const d = data(r); step(context, `订舱 ${status} 状态筛选`, r, r.http === 200 && codeOf(r) === '200' && Array.isArray(d?.records) && d.records.every((x) => x.status === status), `HTTP=${r.http}, code=${codeOf(r)}, records=${d?.records?.length ?? 'n/a'}`) }
|
||||
|
||||
const key = `${context.marker}-ocean-create`
|
||||
const create = await create(context, suffix, key)
|
||||
const detail = data(create); const bookingId = id(detail?.oceanBooking?.id)
|
||||
context.apiSuccess(MODULE, '创建隔离海运订舱', create, `key=${key}`, OPTIONS)
|
||||
context.assert(MODULE, '订舱 DTO 字段、DRAFT 和9类关联字段完整', create, Boolean(bookingId) && detail?.oceanBooking?.status === 'DRAFT' && detail?.parties?.length === 3 && detail?.containerRequirements?.length === 1, `bookingId=${bookingId || 'missing'}, parties=${detail?.parties?.length ?? 'n/a'}, requirements=${detail?.containerRequirements?.length ?? 'n/a'}`, OPTIONS)
|
||||
if (!bookingId) return
|
||||
context.createdResources.oceanBooking = { id: bookingId, marker: context.marker, finalStatus: 'RELEASED' }
|
||||
const replay = await create(context, suffix, key)
|
||||
context.assert(MODULE, '创建 Idempotency-Key 重放返回同一订舱', replay, String(data(replay)?.oceanBooking?.id) === bookingId, `first=${bookingId}, replay=${id(data(replay)?.oceanBooking?.id)}`, OPTIONS)
|
||||
const path = `${BASE}/${bookingId}`
|
||||
const initialVersion = Number(detail.oceanBooking.version)
|
||||
const edited = { ...draft(context, suffix), vesselName: `编辑船-${suffix}`, voyageNo: `EV${suffix}` }
|
||||
const update = await request(context, 'PUT', path, { expectedVersion: initialVersion, data: edited }, { 'Idempotency-Key': `${context.marker}-ocean-edit` })
|
||||
context.apiSuccess(MODULE, '编辑 DRAFT 订舱', update, `version=${initialVersion}`, OPTIONS)
|
||||
const stale = await request(context, 'PUT', path, { expectedVersion: initialVersion, data: { ...edited, vesselName: `STALE-${suffix}` } }, { 'Idempotency-Key': `${context.marker}-ocean-stale` })
|
||||
step(context, '订舱旧版本编辑返回冲突', stale, stale.http === 200 && ['A0409', '409'].includes(codeOf(stale)), `HTTP=${stale.http}, code=${codeOf(stale)}`)
|
||||
const afterStale = await request(context, 'GET', path); const afterData = data(afterStale)
|
||||
context.apiSuccess(MODULE, '冲突后回查订舱', afterStale, '', OPTIONS)
|
||||
context.assert(MODULE, '订舱冲突未部分更新', afterStale, afterData?.oceanBooking?.vesselName === edited.vesselName, `vessel=${afterData?.oceanBooking?.vesselName || 'missing'}`, OPTIONS)
|
||||
let version = Number(afterData?.oceanBooking?.version)
|
||||
const action = async (name, endpoint, expected, reason) => { const r = await request(context, 'POST', `${path}/${endpoint}`, { expectedVersion: expected, reason }, { 'Idempotency-Key': `${context.marker}-${endpoint}` }); const d = data(r); context.apiSuccess(MODULE, name, r, `status=${d?.oceanBooking?.status || 'missing'}`, OPTIONS); version = Number(d?.oceanBooking?.version); return d }
|
||||
await action('提交订舱', 'submit', version, 'marker submit')
|
||||
await action('确认订舱', 'confirm', version, 'marker confirm')
|
||||
const changed = await request(context, 'POST', `${path}/change-request`, { expectedVersion: version, vesselName: `改船-${suffix}`, voyageNo: `CV${suffix}`, reason: context.marker }, { 'Idempotency-Key': `${context.marker}-change` })
|
||||
context.apiSuccess(MODULE, '申请改船', changed, '', OPTIONS); version = Number(data(changed)?.oceanBooking?.version)
|
||||
await action('确认改船', 'change-confirm', version, 'confirm change')
|
||||
await action('放舱', 'release', version, 'release')
|
||||
const final = await request(context, 'GET', path); const fd = data(final)
|
||||
context.apiSuccess(MODULE, '回查 RELEASED 订舱详情', final, '', OPTIONS)
|
||||
context.assert(MODULE, '订舱状态历史、审计操作者和终态完整', final, fd?.oceanBooking?.status === 'RELEASED' && fd.histories?.length >= 5 && fd.histories.every((h) => h.operatorName), `status=${fd?.oceanBooking?.status || 'missing'}, histories=${fd?.histories?.length ?? 'n/a'}`, OPTIONS)
|
||||
const missingDetail = await request(context, 'GET', `${BASE}/999999999999999999`)
|
||||
step(context, '不存在订舱详情安全失败', missingDetail, failure(missingDetail), `HTTP=${missingDetail.http}, code=${codeOf(missingDetail)}`)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
const MODULE = 'truck-appointments'
|
||||
const OPTIONS = { scenario: 'truck-appointment-lifecycle', deploymentBlocker: true }
|
||||
const BASE = '/api/platform/v1/truck-appointments'
|
||||
function id(v) { return v == null ? '' : String(v) }
|
||||
function codeOf(r) { return r?.body?.code == null ? 'missing' : String(r.body.code) }
|
||||
function failure(r) { return [400, 401, 403, 404, 409, 503].includes(r?.http) || (r?.http === 200 && codeOf(r) !== '200') }
|
||||
async function req(context, method, path, body, headers = {}) { try { const response = await fetch(`${context.options.gateway}${path}`, { method, headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}`, ...(body === undefined ? {} : { 'Content-Type': 'application/json; charset=utf-8' }), ...headers }, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(60_000) }); const raw = await response.text(); let payload = null; try { payload = raw ? JSON.parse(raw) : null } catch {} return { method, path, http: response.status, body: payload, raw: payload === null ? raw.slice(0, 500) : null, requestId: payload?.requestId || response.headers.get('x-request-id'), traceId: payload?.traceId || response.headers.get('x-trace-id') } } catch (e) { return { method, path, http: 0, body: null, raw: String(e), requestId: null, traceId: null } } }
|
||||
function data(r) { return String(r?.body?.code) === '200' ? r.body.data : null }
|
||||
function payload(context, suffix, version = 0) { return { outboundOrderId: null, oceanBookingId: null, containerBookingId: null, loadingPlanId: null, warehouseId: 193000000000000001, customerId: 10001, customerName: `拖车验收客户-${suffix}`, carrierId: null, carrierName: `承运商-${suffix}`, fleetContact: '车队联系人', fleetPhone: '13800000004', driverName: '司机验收', driverPhone: '13800000005', driverIdCard: null, vehicleNo: `粤B-${suffix.slice(-6)}`, storeName: `门店-${suffix}`, storeContact: '门店联系人', storePhone: '13800000006', storeAddress: '深圳市南山区验收地址', storeCity: '深圳市', storeZipcode: '518000', packageCount: 10, grossWeightKg: 100, volumeCbm: 2.5, palletCount: 1, poNo: `PO-${suffix}`, plannedLoadingAt: '2026-09-02T08:00:00', transitMinutes: 120, payableAmount: 100, receivableAmount: 150, currency: 'RMB', remark: context.marker, expectedVersion: version } }
|
||||
export async function run(context) { const suffix = context.marker.replace(/\D/g, '').slice(-10); const list = await req(context, 'GET', `${BASE}?page=1&size=5&keyword=${encodeURIComponent(context.marker)}`); const page = context.apiSuccess(MODULE, '拖车预约分页 keyword 查询', list, '', OPTIONS); context.assert(MODULE, '拖车预约分页结构正确', list, Array.isArray(page?.records), `records=${page?.records?.length ?? 'n/a'}`, OPTIONS); for (const status of ['DRAFT','PENDING_DISPATCH','DISPATCHED','PICKUP_IN_PROGRESS','ARRIVED_WAREHOUSE','LOADING','DEPARTED','COMPLETED','RESCHEDULED','EXCEPTION','CANCELLED']) { const r = await req(context, 'GET', `${BASE}?page=1&size=5&status=${status}`); const d = data(r); context.addStep(MODULE, `拖车预约 ${status} 筛选`, r, r.http === 200 && codeOf(r) === '200' && Array.isArray(d?.records) && d.records.every((x) => x.status === status), `HTTP=${r.http}, code=${codeOf(r)}, records=${d?.records?.length ?? 'n/a'}`, OPTIONS) }
|
||||
const create = await req(context, 'POST', BASE, payload(context, suffix)); const appointment = context.apiSuccess(MODULE, '创建隔离拖车预约', create, '', OPTIONS); const appointmentId = id(appointment?.id); context.assert(MODULE, '拖车预约费用、司机车辆和地址字段完整', create, Boolean(appointmentId) && appointment?.status === 'DRAFT' && appointment?.payableAmount != null && appointment?.receivableAmount != null, `id=${appointmentId || 'missing'}, status=${appointment?.status || 'missing'}`, OPTIONS); if (!appointmentId) return; context.createdResources.truckAppointment = { id: appointmentId, marker: context.marker, finalStatus: 'CANCELLED' }; const path = `${BASE}/${appointmentId}`; let version = Number(appointment.version); const edit = await req(context, 'PUT', path, { ...payload(context, suffix, version), vehicleNo: `粤C-${suffix.slice(-6)}` }); const edited = context.apiSuccess(MODULE, '编辑拖车预约', edit, `version=${version}`, OPTIONS); version = Number(edited?.version); const stale = await req(context, 'PUT', path, { ...payload(context, suffix, 0), vehicleNo: `STALE-${suffix}` }); context.addStep(MODULE, '拖车预约旧版本编辑返回冲突', stale, stale.http === 200 && ['A0409','409'].includes(codeOf(stale)), `HTTP=${stale.http}, code=${codeOf(stale)}`, OPTIONS); const after = await req(context, 'GET', path); const ad = data(after); context.apiSuccess(MODULE, '冲突后回查拖车预约', after, '', OPTIONS); context.assert(MODULE, '拖车预约冲突未部分更新', after, ad?.vehicleNo === edited?.vehicleNo, `vehicleNo=${ad?.vehicleNo || 'missing'}`, OPTIONS); version = Number(ad?.version); const action = async (name, endpoint, expected) => { const r = await req(context, 'POST', `${path}/${endpoint}`, { expectedVersion: expected, reason: context.marker }, { 'Idempotency-Key': `${context.marker}-${endpoint}` }); const d = context.apiSuccess(MODULE, name, r, `status=${d?.status || 'missing'}`, OPTIONS); version = Number(d?.version); return d }; for (const [name, endpoint] of [['提交预约','submit'],['派车','dispatch'],['开始提货','pickup'],['到仓','arrive'],['开始装货','load'],['离场','depart'],['完成','complete']]) await action(name, endpoint, version); const final = await req(context, 'GET', path); const fd = data(final); context.apiSuccess(MODULE, '回查 COMPLETED 拖车预约', final, '', OPTIONS); context.assert(MODULE, '拖车预约状态终态和费用快照正确', final, fd?.status === 'COMPLETED' && fd?.payableAmount != null && fd?.receivableAmount != null, `status=${fd?.status || 'missing'}`, OPTIONS); context.addStep(MODULE, '附件引用安全边界自动化缺口', null, false, '当前 Controller 无附件上传/引用 API,未伪造附件成功', OPTIONS) }
|
||||
@@ -44,7 +44,7 @@ public class OverseasStorageFeeService {
|
||||
public OverseasStorageFeeDtos.View detailByTenant(long tenantId) {
|
||||
OverseasStorageFeeRuleEntity rule = repository.page(new OverseasStorageFeeDtos.PageQuery(1, 1, tenantId, null))
|
||||
.records().stream().findFirst().orElseThrow(() -> new BizException(ResultCode.NOT_FOUND, "海外仓费用不存在"));
|
||||
return detail(rule.getId());
|
||||
return view(rule, repository.findTiers(List.of(rule.getId())));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
||||
+13
@@ -83,6 +83,19 @@ class OverseasStorageFeeServiceTest {
|
||||
.isEqualTo(new BigDecimal("0.8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadsCustomerDetailFromPagedRuleWithoutSecondRuleLookup() {
|
||||
OverseasStorageFeeRuleEntity rule = rule();
|
||||
OverseasStorageFeeDtos.PageQuery query = new OverseasStorageFeeDtos.PageQuery(1, 1, 10001L, null);
|
||||
when(repository.page(query)).thenReturn(new PageResult<>(List.of(rule), 1, 1, 1, 1));
|
||||
when(repository.findTiers(List.of(10L))).thenReturn(List.of());
|
||||
|
||||
var result = service.detailByTenant(10001L);
|
||||
|
||||
assertThat(result.customerId()).isEqualTo(10001L);
|
||||
verify(repository, never()).find(10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsStaleUpdate() {
|
||||
OverseasStorageFeeRuleEntity rule = rule();
|
||||
|
||||
Reference in New Issue
Block a user