This commit is contained in:
2026-08-08 20:39:49 +08:00
parent 0558f7ae59
commit f239bb739b
6 changed files with 2767 additions and 24 deletions
@@ -0,0 +1,49 @@
openapi: 3.0.3
info:
title: Truck Appointment Attachments and History API
version: 1.0.0
paths:
/api/platform/v1/truck-appointments/{id}/history:
parameters: [{$ref: '#/components/parameters/AppointmentId'}]
get:
summary: Query immutable appointment status history
responses:
'200': {description: History entries including action, from/to status, operator, reason and time}
/api/platform/v1/truck-appointments/{id}/attachments:
parameters: [{$ref: '#/components/parameters/AppointmentId'}]
get:
summary: Query attachment references owned by the appointment
responses:
'200': {description: Attachment references}
post:
summary: Reference a file already owned by the appointment customer and business scope
responses:
'200': {description: Created attachment reference}
/api/platform/v1/truck-appointments/{id}/attachments/upload:
parameters: [{$ref: '#/components/parameters/AppointmentId'}]
post:
summary: Upload and reference a truck appointment attachment
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
attachmentType: {type: string, maxLength: 32}
file: {type: string, format: binary}
responses:
'200': {description: Uploaded attachment reference}
/api/platform/v1/truck-appointments/{id}/attachments/{attachmentId}:
parameters:
- {$ref: '#/components/parameters/AppointmentId'}
- {$ref: '#/components/parameters/AttachmentId'}
delete:
summary: Delete an attachment reference owned by the appointment
responses:
'200': {description: Reference deleted}
components:
parameters:
AppointmentId: {name: id, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
AttachmentId: {name: attachmentId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
@@ -16,10 +16,16 @@ import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class TruckAppointmentControllerContractTest {
@Test void exposesDedicatedEndpointAndPermissions() {String p=PlatformTruckAppointmentController.class.getAnnotation(org.springframework.web.bind.annotation.RequestMapping.class).value()[0];assertThat(p).isEqualTo("/api/platform/v1/truck-appointments"); for(String n:new String[]{"page","detail","create","edit","action","reschedule"}) assertThat(java.util.Arrays.stream(PlatformTruckAppointmentController.class.getDeclaredMethods()).anyMatch(m->m.getName().equals(n))).isTrue();}
@Test void exposesDedicatedEndpointAndPermissions() {String p=PlatformTruckAppointmentController.class.getAnnotation(org.springframework.web.bind.annotation.RequestMapping.class).value()[0];assertThat(p).isEqualTo("/api/platform/v1/truck-appointments"); for(String n:new String[]{"page","detail","history","attachments","attach","upload","deleteAttachment","create","edit","action","reschedule"}) assertThat(java.util.Arrays.stream(PlatformTruckAppointmentController.class.getDeclaredMethods()).anyMatch(m->m.getName().equals(n))).isTrue();}
@Test void schemaHasAggregateTablesAndStates() throws Exception {String s=new ClassPathResource("db/phase3-truck-appointment-schema.sql").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("fl_truck_appointment","fl_truck_appointment_history","fl_truck_appointment_attachment","fl_truck_appointment_operation","PENDING_DISPATCH","PICKUP_IN_PROGRESS","ARRIVED_WAREHOUSE","RESCHEDULED");}
@Test void methodsHavePlatformPermissionsAndAuthenticatedScope() {
Map<String,String> expected=Map.of("page","platform:firstleg:truck-appointment:list","detail","platform:firstleg:truck-appointment:view","create","platform:firstleg:truck-appointment:create","edit","platform:firstleg:truck-appointment:edit","action","platform:firstleg:truck-appointment:operate","reschedule","platform:firstleg:truck-appointment:operate");
Map<String,String> expected=Map.ofEntries(
Map.entry("page","platform:firstleg:truck-appointment:list"), Map.entry("detail","platform:firstleg:truck-appointment:view"),
Map.entry("history","platform:firstleg:truck-appointment:view"), Map.entry("attachments","platform:firstleg:truck-appointment:view"),
Map.entry("attach","platform:firstleg:truck-appointment:edit"), Map.entry("upload","platform:firstleg:truck-appointment:edit"),
Map.entry("deleteAttachment","platform:firstleg:truck-appointment:edit"), Map.entry("create","platform:firstleg:truck-appointment:create"),
Map.entry("edit","platform:firstleg:truck-appointment:edit"), Map.entry("action","platform:firstleg:truck-appointment:operate"),
Map.entry("reschedule","platform:firstleg:truck-appointment:operate"));
for (Method m: PlatformTruckAppointmentController.class.getDeclaredMethods()) { if (!expected.containsKey(m.getName())) continue; assertThat(m.getAnnotation(PreAuthorize.class).value()).isEqualTo("hasAuthority('"+expected.get(m.getName())+"')"); assertThat(java.util.Arrays.stream(m.getParameterAnnotations()).anyMatch(a -> java.util.Arrays.stream(a).anyMatch(x -> x.annotationType()==AuthenticationPrincipal.class))).isTrue(); }
}
@Test void actionKeepsOptionalIdempotencyHeaders() throws Exception {
@@ -0,0 +1,401 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808123832880",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T12:38:35.384Z",
"startedAt": "2026-08-08T12:38:32.880Z",
"success": true,
"fatal": null,
"summary": {
"total": 19,
"passed": 19,
"failed": 0
},
"modules": [
{
"name": "supplemental-boundaries",
"startedAt": "2026-08-08T12:38:33.252Z",
"finishedAt": "2026-08-08T12:38:35.384Z",
"success": true,
"passed": 18,
"failed": 0,
"fatal": null
}
],
"scenarios": [
{
"name": "admin-boundary-automation",
"module": "supplemental-boundaries",
"success": true,
"deploymentBlocked": false,
"passed": 18,
"failed": 0
}
],
"deploymentBlockers": [],
"coverageGaps": [
{
"module": "supplemental-boundaries",
"scenario": "resource-occupancy",
"name": "托盘/库位真实占用删除保护",
"reason": "当前公共验收 API 未提供可安全构造并释放占用关联的独立 fixture;未操作历史资源",
"category": "no-public-fixture-api"
}
],
"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": "7a4122c2-57cb-43a1-8f6c-8a0e0e001e08, 0e20722a-b785-40dd-8abd-e49b1b4bb541",
"traceId": "ca5a6a7db86f4c449f755bb3a906ef00",
"evidence": "",
"checkedAt": "2026-08-08T12:38:33.250Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "创建边界验收隔离仓库",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/warehouse-admin"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "68acea99-d807-4dfc-9f5e-ae7d4cd6537c, 3ab9ad21-b100-48b2-8fff-361fc59dd229",
"traceId": "e779ee6717044b50bcf2a680ddd65b78",
"evidence": "",
"checkedAt": "2026-08-08T12:38:33.978Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "创建边界验收隔离库位",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/locations?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "50858329-b85d-4dfd-bb29-0740f5101567, 6754a5b0-2725-4ece-abac-72e394a827d5",
"traceId": "0d4cc4498ba14702a10892279f10b507",
"evidence": "",
"checkedAt": "2026-08-08T12:38:34.095Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "读取隔离库位版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/locations/2086069551981707265?tenantId=0&warehouseId=2086069551419670529"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ec2e456d-660f-4368-86ea-6ea509c1591c, 5a6b624c-7db7-41a1-a985-9427c6441465",
"traceId": "eb59590f77724d9183cfea4d261e60e4",
"evidence": "",
"checkedAt": "2026-08-08T12:38:34.140Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "更新隔离库位以推进版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/locations/2086069551981707265?tenantId=0&warehouseId=2086069551419670529"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "faaddf3b-9280-490e-b424-68b48065d0d2, 7c81a029-31ce-4749-b51b-86d52ab8e518",
"traceId": "7885cc13944140c78f01ffc9ed830a30",
"evidence": "version=0",
"checkedAt": "2026-08-08T12:38:34.214Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "库位旧 version PUT 返回冲突",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/locations/2086069551981707265?tenantId=0&warehouseId=2086069551419670529"
},
"http": 200,
"code": "A0409",
"message": "库位已被其他操作修改,请刷新后重试",
"requestId": "c61bfeaf-c6d7-45b1-a819-149fccfc449b",
"traceId": "98cd3bf54e074a4eafff545932927d5a",
"evidence": "HTTP=200, code=A0409",
"checkedAt": "2026-08-08T12:38:34.271Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "冲突后回查库位",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/locations/2086069551981707265?tenantId=0&warehouseId=2086069551419670529"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5daeea9d-1de6-49d9-af81-08c3cfe900ed, 08eb3a73-7dc6-4306-8880-6acd628f9439",
"traceId": "24bf5b3f0fd243d1a3955e42370dd718",
"evidence": "",
"checkedAt": "2026-08-08T12:38:34.314Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "库位冲突未造成部分更新",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/locations/2086069551981707265?tenantId=0&warehouseId=2086069551419670529"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5daeea9d-1de6-49d9-af81-08c3cfe900ed, 08eb3a73-7dc6-4306-8880-6acd628f9439",
"traceId": "24bf5b3f0fd243d1a3955e42370dd718",
"evidence": "name=边界库位-编辑",
"checkedAt": "2026-08-08T12:38:34.314Z"
},
{
"module": "supplemental-boundaries",
"scenario": "import-boundaries",
"name": "仓库空文件安全失败且不模拟导入成功",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/warehouse-admin/import?tenantId=0&dryRun=true"
},
"http": 200,
"code": "A0422",
"message": "仓库导入文件校验失败",
"requestId": "2e8ad0c6-c08f-410e-8892-471ef98094b7",
"traceId": "1301488310ef422294f5f3522eb9ee92",
"evidence": "HTTP=200, code=A0422, bytes=0",
"checkedAt": "2026-08-08T12:38:34.365Z"
},
{
"module": "supplemental-boundaries",
"scenario": "import-boundaries",
"name": "仓库缺列伪 XLSX安全失败且不模拟导入成功",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/warehouse-admin/import?tenantId=0&dryRun=true"
},
"http": 200,
"code": "A0422",
"message": "仓库导入文件校验失败",
"requestId": "310528eb-14e9-4a13-a9cd-a448b749e550",
"traceId": "bfed5d5245de4d11991d66b2d4eb2e91",
"evidence": "HTTP=200, code=A0422, bytes=6",
"checkedAt": "2026-08-08T12:38:34.495Z"
},
{
"module": "supplemental-boundaries",
"scenario": "import-boundaries",
"name": "快递公司错误扩展名安全失败且不模拟导入成功",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/express-companies/import?tenantId=0"
},
"http": 200,
"code": "A0422",
"message": "快递公司导入文件校验失败",
"requestId": "66f6c744-a075-40dd-9433-aca813ff1ae0",
"traceId": "544a7ebdc4954baf8e678c684f7421fc",
"evidence": "HTTP=200, code=A0422, bytes=27",
"checkedAt": "2026-08-08T12:38:34.539Z"
},
{
"module": "supplemental-boundaries",
"scenario": "import-boundaries",
"name": "往来单位非 XLSX安全失败且不模拟导入成功",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/partners/import?tenantId=0"
},
"http": 200,
"code": "A0422",
"message": "往来单位导入文件校验失败",
"requestId": "f17bb5a5-f47e-4551-a007-e427e7d4b018",
"traceId": "a81168d94dde4eee89bd3fce348f3c91",
"evidence": "HTTP=200, code=A0422, bytes=27",
"checkedAt": "2026-08-08T12:38:34.582Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "错误客户海外仓费用 ID 安全失败",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/999999999999999999/overseas-storage-fees"
},
"http": 200,
"code": "A0404",
"message": "海外仓费用不存在",
"requestId": "93257eb1-50e1-465b-8c60-a91839406f7c",
"traceId": "4f867f925be844308c4ac133fb14232a",
"evidence": "HTTP=200, code=A0404",
"checkedAt": "2026-08-08T12:38:34.678Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "库位列表未授权边界响应正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/locations?page=1&size=1&tenantId=0"
},
"http": 401,
"code": "A0401",
"message": "未登录或令牌已失效",
"requestId": "09a9dbb3-2492",
"traceId": null,
"evidence": "HTTP=401, code=A0401",
"checkedAt": "2026-08-08T12:38:34.702Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "下载中心非法页码边界响应正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/files/export-tasks?page=0&size=1"
},
"http": 200,
"code": "A0422",
"message": "page.page: must be greater than or equal to 1",
"requestId": "a424cdde-4050-4646-9629-73d9033232e6",
"traceId": "a807d130122c41fea37d6fdf77b6b319",
"evidence": "HTTP=200, code=A0422",
"checkedAt": "2026-08-08T12:38:35.009Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "船期价格非法状态边界响应正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books?status=INVALID"
},
"http": 200,
"code": "A0400",
"message": "Bad request",
"requestId": "f6f27fe9-fe8d-40a8-8533-3839a10dd9ea",
"traceId": "543a3eb50f6040dd9db2ccd621634ff9",
"evidence": "HTTP=200, code=A0400",
"checkedAt": "2026-08-08T12:38:35.235Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "停用隔离库位以便清理",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PATCH",
"path": "/api/platform/v1/locations/2086069551981707265/status?tenantId=0&warehouseId=2086069551419670529"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b4db06df-896c-4cff-9a41-00fad82f1702, 3213100d-ef66-4484-8732-59406c57ef1e",
"traceId": "7f5423ff170849948dd98eb3d0047405",
"evidence": "",
"checkedAt": "2026-08-08T12:38:35.289Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "删除隔离库位",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "DELETE",
"path": "/api/platform/v1/locations/2086069551981707265?tenantId=0&warehouseId=2086069551419670529&version=2"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "293e1cd9-56b5-41a1-9679-65d9568f301f, 718b10c4-2980-41d2-9095-0c9989b4b108",
"traceId": "470c23f5d6f84f32adfd5d5e43478af2",
"evidence": "",
"checkedAt": "2026-08-08T12:38:35.326Z"
},
{
"module": "supplemental-boundaries",
"scenario": "admin-boundary-automation",
"name": "停用隔离仓库",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PATCH",
"path": "/api/platform/v1/warehouse-admin/2086069551419670529/status?tenantId=0&status=INACTIVE"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f4aa5fb6-a1d8-4ce4-a9cb-ec8718a968c2, 46128fd3-7a3c-4c96-b3bb-0a11b375ed5b",
"traceId": "1e7e85e33f3f43db8e634ee6e5b8b3ab",
"evidence": "",
"checkedAt": "2026-08-08T12:38:35.384Z"
}
]
}
@@ -0,0 +1,937 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808123844850",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T12:38:47.387Z",
"startedAt": "2026-08-08T12:38:44.850Z",
"success": false,
"fatal": null,
"summary": {
"total": 47,
"passed": 45,
"failed": 2
},
"modules": [
{
"name": "ocean-bookings",
"startedAt": "2026-08-08T12:38:45.224Z",
"finishedAt": "2026-08-08T12:38:46.748Z",
"success": true,
"passed": 23,
"failed": 0,
"fatal": null
},
{
"name": "truck-appointments",
"startedAt": "2026-08-08T12:38:46.748Z",
"finishedAt": "2026-08-08T12:38:47.226Z",
"success": false,
"passed": 13,
"failed": 2,
"fatal": null
},
{
"name": "service-center-flow",
"startedAt": "2026-08-08T12:38:47.227Z",
"finishedAt": "2026-08-08T12:38:47.387Z",
"success": true,
"passed": 8,
"failed": 0,
"fatal": null
}
],
"scenarios": [],
"deploymentBlockers": [
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "创建隔离拖车预约",
"evidence": "",
"code": "A0400",
"requestId": "a0e77899-707f-423d-a194-b00e464a0b06",
"traceId": "ecd32424e4674136a3ab42dcd50f8d50"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约费用、司机车辆和地址字段完整",
"evidence": "id=missing, status=missing",
"code": "A0400",
"requestId": "a0e77899-707f-423d-a194-b00e464a0b06",
"traceId": "ecd32424e4674136a3ab42dcd50f8d50"
}
],
"coverageGaps": [],
"createdResources": {
"oceanBooking": {
"id": "2086069601618669569",
"marker": "ADMIN-API-20260808123844850",
"finalStatus": "RELEASED"
},
"serviceCenterFlow": {
"appointmentId": "2086069607310340098",
"oceanBookingId": "2086069601618669569",
"marker": "ADMIN-API-20260808123844850",
"finalStatus": "CANCELLED"
}
},
"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": "adf1a201-83b2-4dc7-a3c0-af89c51a1453, 05bc222c-53f5-4461-8750-79b99473b672",
"traceId": "6d8c7a0ca6ea4d188f45af50c3eb4b6d",
"evidence": "",
"checkedAt": "2026-08-08T12:38:45.223Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱列表分页与 keyword 查询",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&keyword=ADMIN-API-20260808123844850"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "8282e144-2f40-4105-aa7e-e9003cc78103, 81f90a3b-096a-45a3-9f40-7e8235fb422a",
"traceId": "73a39258d4ad488b8ba6b3418145e321",
"evidence": "",
"checkedAt": "2026-08-08T12:38:45.485Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱列表分页结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&keyword=ADMIN-API-20260808123844850"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "8282e144-2f40-4105-aa7e-e9003cc78103, 81f90a3b-096a-45a3-9f40-7e8235fb422a",
"traceId": "73a39258d4ad488b8ba6b3418145e321",
"evidence": "records=0",
"checkedAt": "2026-08-08T12:38:45.485Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱 DRAFT 状态筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=DRAFT"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ae93ca45-7680-4222-8c2b-5a7c28ae22b8, e4475ec5-257e-4520-82ce-c4fdf3595468",
"traceId": "71bc0cdf508e4044b8d2453ca9e96025",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:45.537Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱 SUBMITTED 状态筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=SUBMITTED"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2e2fc320-f075-48b2-8e7b-5fde389fcdd1, 9523048d-b9b1-43c5-ba7f-123d20e01e3c",
"traceId": "afde9ebbdb294e53bde0f88d4de40921",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:45.579Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱 CONFIRMED 状态筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=CONFIRMED"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ebc1bbb8-2d98-4211-a906-611c550d271f, 26e2776a-4862-40f0-a70b-df38efc7a7cb",
"traceId": "d146b43157f1481aa418e29237b8120c",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:45.628Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱 RELEASED 状态筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=RELEASED"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3a23ed1f-71bb-4ace-a5f0-c3f384e48ca4, 1a4d8ed9-1d12-4c90-be10-a42a730057de",
"traceId": "95eba836c1824e7394fa48f38cea6b01",
"evidence": "HTTP=200, code=200, records=5",
"checkedAt": "2026-08-08T12:38:45.687Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱 FAILED 状态筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=FAILED"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3f8e4af3-8d1f-4831-89b2-b49aef69f16a, a69b2cfe-9cdb-4849-94bc-6600ef68e979",
"traceId": "6b0a1599f2f743bdb30b919dc5dc2298",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:45.729Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱 CANCELLED 状态筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings?page=1&size=5&status=CANCELLED"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b00224b6-4162-4e7f-91d4-540291c012c1, 75e2d97e-321b-419d-8bdf-5b60de1c7ace",
"traceId": "cd0f9f9208d943fba451f5f46e6d06fd",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:45.759Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "创建隔离海运订舱",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/ocean-bookings"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "125ba892-a866-41ae-a796-081dce3cc6f8, ec2b1999-5afa-4ae4-b628-4e1a5525be53",
"traceId": "2763a6b3d70346f8a0e57aa3043f9282",
"evidence": "key=ADMIN-API-20260808123844850-ocean-create",
"checkedAt": "2026-08-08T12:38:46.022Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱 DTO 字段、DRAFT 和9类关联字段完整",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/ocean-bookings"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "125ba892-a866-41ae-a796-081dce3cc6f8, ec2b1999-5afa-4ae4-b628-4e1a5525be53",
"traceId": "2763a6b3d70346f8a0e57aa3043f9282",
"evidence": "bookingId=2086069601618669569, parties=3, requirements=1",
"checkedAt": "2026-08-08T12:38:46.022Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "创建 Idempotency-Key 重放返回同一订舱",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/ocean-bookings"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "959b4cb3-639e-477a-86e8-7eaeea5d8bf9, 7aff8266-91d8-478c-a170-fda9ed20602a",
"traceId": "b2fda443ccb14c0eb23a96e57f53c0c8",
"evidence": "first=2086069601618669569, replay=2086069601618669569",
"checkedAt": "2026-08-08T12:38:46.078Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "编辑 DRAFT 订舱",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/ocean-bookings/2086069601618669569"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ef7163c3-e4d3-4b83-9504-417674273106, e289b155-f480-4bb0-8bd7-ddb2ed1fe461",
"traceId": "3621b9645a2f40e682b6e49e88018c8d",
"evidence": "version=0",
"checkedAt": "2026-08-08T12:38:46.189Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱旧版本编辑返回冲突",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/ocean-bookings/2086069601618669569"
},
"http": 200,
"code": "A0409",
"message": "数据已被其他人修改,请刷新后重试",
"requestId": "86fd3076-2ad0-4f0b-be45-5d3a23aa25c4",
"traceId": "4328fda9b3e4498ead9b15405b2416f1",
"evidence": "HTTP=200, code=A0409",
"checkedAt": "2026-08-08T12:38:46.228Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "冲突后回查订舱",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings/2086069601618669569"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c9a4e2ad-2bb8-4071-84fc-89d0b5b97f90, 985015d4-1f51-41e0-9370-66d0ec8a743a",
"traceId": "8c87a9161ba9444c843c17109b847ec0",
"evidence": "",
"checkedAt": "2026-08-08T12:38:46.291Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱冲突未部分更新",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings/2086069601618669569"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c9a4e2ad-2bb8-4071-84fc-89d0b5b97f90, 985015d4-1f51-41e0-9370-66d0ec8a743a",
"traceId": "8c87a9161ba9444c843c17109b847ec0",
"evidence": "vessel=编辑船-8123844850",
"checkedAt": "2026-08-08T12:38:46.291Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "提交订舱",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/ocean-bookings/2086069601618669569/submit"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "19fec60a-5558-4c57-a38a-49f7e1eb282d, baaa7dba-5966-4a6b-b09b-d21446ba3104",
"traceId": "76470dd20b4c4d2380819bee2f7d624b",
"evidence": "status=SUBMITTED",
"checkedAt": "2026-08-08T12:38:46.385Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "确认订舱",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/ocean-bookings/2086069601618669569/confirm"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9ce4a320-b4ae-4b5a-8787-484060de58d6, 808e9b04-23bb-4065-898f-9edd21242746",
"traceId": "2c9f92a164034e96afc6e8faec22d7f4",
"evidence": "status=CONFIRMED",
"checkedAt": "2026-08-08T12:38:46.469Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "申请改船",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/ocean-bookings/2086069601618669569/change-request"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "841ce97b-0d54-4aa9-bfba-2276f1dff274, 59734c0f-527e-4eaa-9b15-33b6283425e6",
"traceId": "33af20c208a843809c6367a6b8954385",
"evidence": "",
"checkedAt": "2026-08-08T12:38:46.553Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "确认改船",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/ocean-bookings/2086069601618669569/change-confirm"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "68dea18b-9300-41cb-9db6-48d827b8e36d, b7e9421a-1510-4bbe-9cb8-bdc3d1b90683",
"traceId": "10974d14aa6e45728f04d91a5edfc716",
"evidence": "status=CONFIRMED",
"checkedAt": "2026-08-08T12:38:46.620Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "放舱",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/ocean-bookings/2086069601618669569/release"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "04922d43-7ac1-4278-ae52-8b4237f05b77, c56cd9ea-cbba-49a0-b559-263a1cf4392f",
"traceId": "c39f3a4afefc45d597056c8dd678d85f",
"evidence": "status=RELEASED",
"checkedAt": "2026-08-08T12:38:46.682Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "回查 RELEASED 订舱详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings/2086069601618669569"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3163e816-f73a-43f7-9414-c29cf1d2dbaf, d19e4a7e-66b9-43cd-b429-af1467b32b39",
"traceId": "138f19b8c06e4d9bb1d78c53d555431f",
"evidence": "",
"checkedAt": "2026-08-08T12:38:46.712Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "订舱状态历史、审计操作者和终态完整",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings/2086069601618669569"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3163e816-f73a-43f7-9414-c29cf1d2dbaf, d19e4a7e-66b9-43cd-b429-af1467b32b39",
"traceId": "138f19b8c06e4d9bb1d78c53d555431f",
"evidence": "status=RELEASED, histories=6",
"checkedAt": "2026-08-08T12:38:46.712Z"
},
{
"module": "ocean-bookings",
"scenario": "ocean-booking-lifecycle",
"name": "不存在订舱详情安全失败",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/ocean-bookings/999999999999999999"
},
"http": 200,
"code": "A0404",
"message": "订舱单不存在",
"requestId": "aec2d889-358d-42d8-8bc1-6ef038fde9d1",
"traceId": "82359864e1244961b9da53906e891235",
"evidence": "HTTP=200, code=A0404",
"checkedAt": "2026-08-08T12:38:46.747Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约分页 keyword 查询",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&keyword=ADMIN-API-20260808123844850"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f1bfff8d-c263-4bc5-adcb-581bcf52d966, 9c310982-c9b1-4c2d-b251-efe55caf3ac3",
"traceId": "ddc9dac485f44e4b99e687bca1390818",
"evidence": "",
"checkedAt": "2026-08-08T12:38:46.786Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约分页结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&keyword=ADMIN-API-20260808123844850"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f1bfff8d-c263-4bc5-adcb-581bcf52d966, 9c310982-c9b1-4c2d-b251-efe55caf3ac3",
"traceId": "ddc9dac485f44e4b99e687bca1390818",
"evidence": "records=0",
"checkedAt": "2026-08-08T12:38:46.786Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约 DRAFT 筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=DRAFT"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "85c54ebe-8741-4d2f-a6bb-cc2b8a9d0bcb, d772c85c-1042-4965-bb00-7ce17271b4a7",
"traceId": "874e8298000e4e8b8632064e6c7e87d3",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:46.829Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约 PENDING_DISPATCH 筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=PENDING_DISPATCH"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "414c386b-09f7-47e7-911f-262564f9b1cb, 86bcc25a-1a1c-47f8-91d4-9cfdf8fbee22",
"traceId": "4a82fa02255149cf8db6422a99b1f068",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:46.853Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约 DISPATCHED 筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=DISPATCHED"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "8a8163d5-cc3d-4364-afc2-8a47f871cf88, 652a9de8-9bec-4239-9413-10e6ff1bfaed",
"traceId": "c2fe6be757f74669bd97041d0b2b64c2",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:46.889Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约 PICKUP_IN_PROGRESS 筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=PICKUP_IN_PROGRESS"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5f96c4f2-9439-42ce-af06-5da1eac4551a, 5ebb8e5f-47f2-4c1d-b437-2e8c0c6f8542",
"traceId": "c14b59053dff44319aedd657e2d1fb32",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:46.916Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约 ARRIVED_WAREHOUSE 筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=ARRIVED_WAREHOUSE"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "eab4ebd3-c797-4d89-aa7d-2e67b1c12164, 70869095-f565-40cf-9d90-44d9243e7ddf",
"traceId": "73d0d50cd021455da9dc9ce6e5e7154b",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:46.947Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约 LOADING 筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=LOADING"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "62d80194-2812-4ec2-b417-881fe7816868, 52f240d5-c553-4283-a0a6-501474c078e6",
"traceId": "81d3eb25c7da40a5b0db6c705a9968bf",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:46.978Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约 DEPARTED 筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=DEPARTED"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "382928a0-7ba0-422e-a154-58a5dabc5446, 950b0112-ad6a-4e6f-9e53-579ccfb28c7e",
"traceId": "4feeb921516547c8a2f646e082535053",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:47.011Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约 COMPLETED 筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=COMPLETED"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "146427b7-62cc-4729-9fd0-be22bc65c9c7, e1fe483a-6f93-4d85-b437-bf4415dc3b22",
"traceId": "b4dc9c5f033846549ae6bb604e4fbebf",
"evidence": "HTTP=200, code=200, records=5",
"checkedAt": "2026-08-08T12:38:47.052Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约 RESCHEDULED 筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=RESCHEDULED"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ab12d760-8be1-42f9-a6ae-ca15cf9729c7, 3ec600b6-9ce1-4b1a-98dd-22c57de1fcc4",
"traceId": "570d0bb7986e4af58fa65bde23777604",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:47.088Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约 EXCEPTION 筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=EXCEPTION"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "969dbebe-5c56-45b1-b7c9-32925e488071, 6068d2b3-9285-455d-a6fc-faca766a50ac",
"traceId": "0ce51b860c754b1ca57d8ef14800a60d",
"evidence": "HTTP=200, code=200, records=0",
"checkedAt": "2026-08-08T12:38:47.119Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约 CANCELLED 筛选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments?page=1&size=5&status=CANCELLED"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "535be6c9-1eae-4a49-8436-de9b24bcc6f2, 8ade76ff-5814-44f6-b29c-9bfffe8dbd7b",
"traceId": "bfc3ab1892634acabfff0ae702b39fa9",
"evidence": "HTTP=200, code=200, records=4",
"checkedAt": "2026-08-08T12:38:47.155Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "创建隔离拖车预约",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "POST",
"path": "/api/platform/v1/truck-appointments"
},
"http": 200,
"code": "A0400",
"message": "oceanBookingId不能为空",
"requestId": "a0e77899-707f-423d-a194-b00e464a0b06",
"traceId": "ecd32424e4674136a3ab42dcd50f8d50",
"evidence": "",
"checkedAt": "2026-08-08T12:38:47.226Z"
},
{
"module": "truck-appointments",
"scenario": "truck-appointment-lifecycle",
"name": "拖车预约费用、司机车辆和地址字段完整",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "POST",
"path": "/api/platform/v1/truck-appointments"
},
"http": 200,
"code": "A0400",
"message": "oceanBookingId不能为空",
"requestId": "a0e77899-707f-423d-a194-b00e464a0b06",
"traceId": "ecd32424e4674136a3ab42dcd50f8d50",
"evidence": "id=missing, status=missing",
"checkedAt": "2026-08-08T12:38:47.226Z"
},
{
"module": "service-center-flow",
"scenario": "service-center-cross-flow",
"name": "创建关联海运订舱的拖车预约",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/truck-appointments"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d79b07fd-ae0b-4d3b-b6c5-af4cf9f290c7, c0961ea6-1932-492f-a6d3-ef80c7e0f5c3",
"traceId": "b6b5b2cf0ed24100a6c2c7ffb594e037",
"evidence": "oceanBookingId=2086069601618669569",
"checkedAt": "2026-08-08T12:38:47.277Z"
},
{
"module": "service-center-flow",
"scenario": "service-center-cross-flow",
"name": "拖车预约关联订舱 ID、版本和审计字段完整",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/truck-appointments"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d79b07fd-ae0b-4d3b-b6c5-af4cf9f290c7, c0961ea6-1932-492f-a6d3-ef80c7e0f5c3",
"traceId": "b6b5b2cf0ed24100a6c2c7ffb594e037",
"evidence": "appointmentId=2086069607310340098, oceanBookingId=2086069601618669569, version=0",
"checkedAt": "2026-08-08T12:38:47.277Z"
},
{
"module": "service-center-flow",
"scenario": "service-center-cross-flow",
"name": "取消跨服务链路拖车预约",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/truck-appointments/2086069607310340098/cancel"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "add9beb1-33a4-43e3-a393-805e61e8a9ca, d1b4cd84-59a5-4f1f-bcc2-8b9bdf405b14",
"traceId": "cc24e6133d9f48d5a5f003fcb14e1d7f",
"evidence": "",
"checkedAt": "2026-08-08T12:38:47.320Z"
},
{
"module": "service-center-flow",
"scenario": "service-center-cross-flow",
"name": "跨服务链路安全终态为 CANCELLED",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/truck-appointments/2086069607310340098/cancel"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "add9beb1-33a4-43e3-a393-805e61e8a9ca, d1b4cd84-59a5-4f1f-bcc2-8b9bdf405b14",
"traceId": "cc24e6133d9f48d5a5f003fcb14e1d7f",
"evidence": "status=CANCELLED",
"checkedAt": "2026-08-08T12:38:47.320Z"
},
{
"module": "service-center-flow",
"scenario": "service-center-cross-flow",
"name": "回查跨服务链路安全终态",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments/2086069607310340098"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ef742f81-bb2d-4c6f-aca6-2aa1cd0b34b8, 2d10218c-aacc-45fd-9bf2-bed5c586c6e1",
"traceId": "1fc1dd79f62a4c5c900003ac7388b1d1",
"evidence": "",
"checkedAt": "2026-08-08T12:38:47.354Z"
},
{
"module": "service-center-flow",
"scenario": "service-center-cross-flow",
"name": "跨服务链路终态持久化且关联订舱未漂移",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments/2086069607310340098"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ef742f81-bb2d-4c6f-aca6-2aa1cd0b34b8, 2d10218c-aacc-45fd-9bf2-bed5c586c6e1",
"traceId": "1fc1dd79f62a4c5c900003ac7388b1d1",
"evidence": "status=CANCELLED, oceanBookingId=2086069601618669569",
"checkedAt": "2026-08-08T12:38:47.354Z"
},
{
"module": "service-center-flow",
"scenario": "service-center-cross-flow",
"name": "查询跨服务链路拖车预约状态历史",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments/2086069607310340098/history"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "46532c3d-a62b-4532-9261-9908bc8f017f, 42e8644e-0c46-4a36-96aa-4818bb48bcfe",
"traceId": "553bf4e56b964ef9bacaa745956f9c6e",
"evidence": "",
"checkedAt": "2026-08-08T12:38:47.387Z"
},
{
"module": "service-center-flow",
"scenario": "service-center-cross-flow",
"name": "跨服务链路历史包含归属、状态、操作者、时间和备注",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/truck-appointments/2086069607310340098/history"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "46532c3d-a62b-4532-9261-9908bc8f017f, 42e8644e-0c46-4a36-96aa-4818bb48bcfe",
"traceId": "553bf4e56b964ef9bacaa745956f9c6e",
"evidence": "history=2, cancelFrom=DRAFT, cancelTo=CANCELLED",
"checkedAt": "2026-08-08T12:38:47.387Z"
}
]
}
File diff suppressed because it is too large Load Diff
@@ -1,28 +1,91 @@
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 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 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)}`); if (missing(list) || list.http === 0) { context.addStep(MODULE, '拖车预约服务契约已部署', list, false, `入口 ${BASE} 不可用:HTTP=${list.http}, code=${codeOf(list)}`, OPTIONS); return } 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 createResponse = await req(context, 'POST', BASE, payload(context, suffix)); const appointment = context.apiSuccess(MODULE, '创建隔离拖车预约', createResponse, '', OPTIONS); const appointmentId = id(appointment?.id); context.assert(MODULE, '拖车预约费用、司机车辆和地址字段完整', createResponse, 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, '', OPTIONS); context.assert(MODULE, `${name} 状态返回`, r, Boolean(d?.status), `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)
const historyResponse = await req(context, 'GET', `${path}/history`)
const history = context.apiSuccess(MODULE, '查询拖车预约状态历史', historyResponse, '', OPTIONS)
const expectedActions = ['CREATE', 'EDIT', 'SUBMIT', 'DISPATCH', 'PICKUP', 'ARRIVE', 'LOAD', 'DEPART', 'COMPLETE']
const actualActions = Array.isArray(history) ? history.map((item) => item?.action) : []
context.assert(MODULE, '拖车预约历史动作和状态链完整', historyResponse,
Array.isArray(history) && expectedActions.every((value) => actualActions.includes(value))
&& history.every((item) => id(item?.appointmentId) === appointmentId
&& Boolean(item?.action) && Object.hasOwn(item, 'fromStatus') && Boolean(item?.toStatus)
&& Boolean(item?.operatorId) && Boolean(item?.operatorName) && Boolean(item?.createdAt)),
`history=${history?.length ?? 'n/a'}, actions=${actualActions.join(',')}`, OPTIONS)
function id(value) { return value == null ? '' : String(value) }
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
function missing(response) { return response?.http === 404 || response?.http === 405 || response?.http === 503 }
function failure(response) { return [400, 401, 403, 404, 409, 503].includes(response?.http) || (response?.http === 200 && codeOf(response) !== '200') }
function data(response) { return codeOf(response) === '200' ? response.body.data : null }
const missingHistory = await req(context, 'GET', `${BASE}/999999999999999999/history`)
context.addStep(MODULE, '不存在预约不能读取状态历史', missingHistory,
failure(missingHistory), `HTTP=${missingHistory.http}, code=${codeOf(missingHistory)}`, OPTIONS)
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 } }
}
async function uploadAttachment(context, appointmentId) {
const path = `${BASE}/${appointmentId}/attachments/upload?attachmentType=ACCEPTANCE_EVIDENCE`
const form = new FormData()
form.append('file', new Blob([new TextEncoder().encode(context.marker)], { type: 'text/plain' }), `${context.marker}-truck.txt`)
try {
const response = await fetch(`${context.options.gateway}${path}`, { method: 'POST', headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}` }, body: form, 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: 'POST', 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: 'POST', path, http: 0, body: null, raw: String(error), requestId: null, traceId: null } }
}
function payload(context, suffix, version = 0) {
return {
outboundOrderId: null, oceanBookingId: context.createdResources?.oceanBooking?.id || 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 request(context, 'GET', `${BASE}?page=1&size=5&keyword=${encodeURIComponent(context.marker)}`)
if (missing(list) || list.http === 0) { context.addStep(MODULE, '拖车预约服务契约已部署', list, false, `入口 ${BASE} 不可用:HTTP=${list.http}, code=${codeOf(list)}`, OPTIONS); return }
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 response = await request(context, 'GET', `${BASE}?page=1&size=5&status=${status}`); const result = data(response)
context.addStep(MODULE, `拖车预约 ${status} 筛选`, response, response.http === 200 && codeOf(response) === '200' && Array.isArray(result?.records) && result.records.every((item) => item.status === status), `HTTP=${response.http}, code=${codeOf(response)}, records=${result?.records?.length ?? 'n/a'}`, OPTIONS)
}
const createResponse = await request(context, 'POST', BASE, payload(context, suffix)); const appointment = context.apiSuccess(MODULE, '创建隔离拖车预约', createResponse, '', OPTIONS)
const appointmentId = id(appointment?.id)
context.assert(MODULE, '拖车预约费用、司机车辆和地址字段完整', createResponse, 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: 'COMPLETED' }
const path = `${BASE}/${appointmentId}`
const upload = await uploadAttachment(context, appointmentId); const attachment = context.apiSuccess(MODULE, '上传本轮拖车预约文本附件', upload, `fileName=${context.marker}-truck.txt`, OPTIONS)
const attachmentId = id(attachment?.id)
context.assert(MODULE, '附件引用归属、类型和哈希元数据完整', upload, Boolean(attachmentId) && id(attachment?.appointmentId) === appointmentId && attachment?.attachmentType === 'ACCEPTANCE_EVIDENCE' && Boolean(attachment?.fileId) && Boolean(attachment?.fileHash), `attachmentId=${attachmentId || 'missing'}, fileId=${attachment?.fileId || 'missing'}`, OPTIONS)
const attachments = await request(context, 'GET', `${path}/attachments`); const attachmentList = context.apiSuccess(MODULE, '查询拖车预约附件列表', attachments, '', OPTIONS)
context.assert(MODULE, '附件列表包含本轮上传引用', attachments, Array.isArray(attachmentList) && attachmentList.some((item) => id(item.id) === attachmentId), `attachments=${attachmentList?.length ?? 'n/a'}`, OPTIONS)
const forged = await request(context, 'POST', `${path}/attachments`, { fileId: '999999999999999999', attachmentType: 'ACCEPTANCE_EVIDENCE', fileName: `${context.marker}-forged.txt`, fileHash: 'forged' })
context.addStep(MODULE, '伪造附件 fileId 安全拒绝', forged, failure(forged), `HTTP=${forged.http}, code=${codeOf(forged)}`, OPTIONS)
if (attachmentId) { const removed = await request(context, 'DELETE', `${path}/attachments/${attachmentId}`); context.apiSuccess(MODULE, '删除本轮拖车预约附件引用', removed, `attachmentId=${attachmentId}`, OPTIONS) }
let version = Number(appointment.version)
const edit = await request(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 request(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 request(context, 'GET', path); const afterData = data(after); context.apiSuccess(MODULE, '冲突后回查拖车预约', after, '', OPTIONS)
context.assert(MODULE, '拖车预约冲突未部分更新', after, afterData?.vehicleNo === edited?.vehicleNo, `vehicleNo=${afterData?.vehicleNo || 'missing'}`, OPTIONS); version = Number(afterData?.version)
const action = async (name, endpoint, expectedStatus) => { const response = await request(context, 'POST', `${path}/${endpoint}`, { expectedVersion: version, reason: context.marker }, { 'Idempotency-Key': `${context.marker}-${endpoint}` }); const result = context.apiSuccess(MODULE, name, response, '', OPTIONS); context.assert(MODULE, `${name} 状态返回`, response, result?.status === expectedStatus, `status=${result?.status || 'missing'}`, OPTIONS); version = Number(result?.version) }
for (const [name, endpoint, status] of [['提交预约','submit','PENDING_DISPATCH'],['派车','dispatch','DISPATCHED'],['开始提货','pickup','PICKUP_IN_PROGRESS'],['到仓','arrive','ARRIVED_WAREHOUSE'],['开始装货','load','LOADING'],['离场','depart','DEPARTED'],['完成','complete','COMPLETED']]) await action(name, endpoint, status)
const final = await request(context, 'GET', path); const finalData = data(final); context.apiSuccess(MODULE, '回查 COMPLETED 拖车预约', final, '', OPTIONS)
context.assert(MODULE, '拖车预约状态终态和费用快照正确', final, finalData?.status === 'COMPLETED' && finalData?.payableAmount != null && finalData?.receivableAmount != null, `status=${finalData?.status || 'missing'}`, OPTIONS)
const history = await request(context, 'GET', `${path}/history`); const histories = context.apiSuccess(MODULE, '查询拖车预约状态历史', history, '', OPTIONS)
const actions = Array.isArray(histories) ? histories.map((item) => item.action) : []
context.assert(MODULE, '拖车状态历史动作、版本和审计操作者完整', history, ['SUBMIT','DISPATCH','PICKUP','ARRIVE','LOAD','DEPART','COMPLETE'].every((item) => actions.includes(item)) && histories.every((item) => Number.isFinite(Number(item.revision)) && Boolean(item.operatorName) && Boolean(item.createdAt)), `actions=${actions.join(',')}`, OPTIONS)
}