This commit is contained in:
2026-08-08 14:13:28 +08:00
parent ef5878a85d
commit 87508bba82
7 changed files with 976 additions and 2 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ spring:
- id: warehouse-service - id: warehouse-service
uri: lb://warehouse-service uri: lb://warehouse-service
predicates: 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 - id: file-service
uri: lb://file-service uri: lb://file-service
predicates: 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) { public OverseasStorageFeeDtos.View detailByTenant(long tenantId) {
OverseasStorageFeeRuleEntity rule = repository.page(new OverseasStorageFeeDtos.PageQuery(1, 1, tenantId, null)) OverseasStorageFeeRuleEntity rule = repository.page(new OverseasStorageFeeDtos.PageQuery(1, 1, tenantId, null))
.records().stream().findFirst().orElseThrow(() -> new BizException(ResultCode.NOT_FOUND, "海外仓费用不存在")); .records().stream().findFirst().orElseThrow(() -> new BizException(ResultCode.NOT_FOUND, "海外仓费用不存在"));
return detail(rule.getId()); return view(rule, repository.findTiers(List.of(rule.getId())));
} }
@Transactional @Transactional
@@ -83,6 +83,19 @@ class OverseasStorageFeeServiceTest {
.isEqualTo(new BigDecimal("0.8")); .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 @Test
void detectsStaleUpdate() { void detectsStaleUpdate() {
OverseasStorageFeeRuleEntity rule = rule(); OverseasStorageFeeRuleEntity rule = rule();