Test #1

Merged
admin merged 1726 commits from test into main 2026-09-17 11:52:45 +08:00
11 changed files with 3109 additions and 27 deletions
Showing only changes of commit 73bfc5a65c - Show all commits
+3 -1
View File
@@ -15,10 +15,12 @@ firstleg:
warehouse-resource-tenant-id: 0
measurement-openapi:
enabled: ${MEASUREMENT_OPENAPI_ENABLED:false}
# 墨家首轮联调暂不验 HMAC;通讯确认后将环境变量改为 true 即可恢复。
signature-required: ${MEASUREMENT_OPENAPI_SIGNATURE_REQUIRED:false}
max-clock-skew: ${MEASUREMENT_OPENAPI_MAX_CLOCK_SKEW:5m}
nonce-ttl: ${MEASUREMENT_OPENAPI_NONCE_TTL:10m}
apps:
# 墨家设备群共用一组固定 AppKey/Secret;每台机器用 data.deviceCode 区分。
# 墨家设备群共用固定 AppKey;签名恢复后再启用 Secret。每台机器用 data.deviceCode 区分。
mojia:
enabled: ${MOJIA_MEASUREMENT_ENABLED:false}
secret: ${MOJIA_MEASUREMENT_SECRET:}
+3 -1
View File
@@ -14,10 +14,12 @@ firstleg:
warehouse-resource-tenant-id: 0
measurement-openapi:
enabled: ${MEASUREMENT_OPENAPI_ENABLED:false}
# 墨家首轮联调暂不验 HMAC;通讯确认后将环境变量改为 true 即可恢复。
signature-required: ${MEASUREMENT_OPENAPI_SIGNATURE_REQUIRED:false}
max-clock-skew: ${MEASUREMENT_OPENAPI_MAX_CLOCK_SKEW:5m}
nonce-ttl: ${MEASUREMENT_OPENAPI_NONCE_TTL:10m}
apps:
# 墨家设备群共用一组固定 AppKey/Secret;每台机器用 data.deviceCode 区分。
# 墨家设备群共用固定 AppKey;签名恢复后再启用 Secret。每台机器用 data.deviceCode 区分。
mojia:
enabled: ${MOJIA_MEASUREMENT_ENABLED:false}
secret: ${MOJIA_MEASUREMENT_SECRET:}
@@ -18,6 +18,8 @@ import java.util.Map;
@ConfigurationProperties(prefix = "firstleg.measurement-openapi")
public class MeasurementOpenApiProperties {
private boolean enabled = false;
/** 联调期间可关闭 HMAC、时间戳和 Nonce;固定 AppKey 与业务幂等仍强制校验。 */
private boolean signatureRequired = true;
private Duration maxClockSkew = Duration.ofMinutes(5);
private Duration nonceTtl = Duration.ofMinutes(10);
private Map<String, AppCredential> apps = new LinkedHashMap<>();
@@ -47,9 +47,9 @@ public class OpenApiInboundMeasurementController {
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponse<EventResponse> receive(
@RequestHeader("X-App-Key") String appKey,
@RequestHeader("X-Timestamp") String timestamp,
@RequestHeader("X-Nonce") String nonce,
@RequestHeader("X-Signature") String signature,
@RequestHeader(value = "X-Timestamp", required = false) String timestamp,
@RequestHeader(value = "X-Nonce", required = false) String nonce,
@RequestHeader(value = "X-Signature", required = false) String signature,
@RequestHeader("Idempotency-Key") String idempotencyKey,
@RequestPart("data") String rawData,
@RequestPart("image") MultipartFile image) {
@@ -67,9 +67,9 @@ public class OpenApiInboundMeasurementController {
@Parameter(description = "第三方业务事件号", required = true)
@PathVariable @Pattern(regexp = "[A-Za-z0-9._:-]{1,128}") String eventNo,
@RequestHeader("X-App-Key") String appKey,
@RequestHeader("X-Timestamp") String timestamp,
@RequestHeader("X-Nonce") String nonce,
@RequestHeader("X-Signature") String signature) {
@RequestHeader(value = "X-Timestamp", required = false) String timestamp,
@RequestHeader(value = "X-Nonce", required = false) String nonce,
@RequestHeader(value = "X-Signature", required = false) String signature) {
return ApiResponse.success(service.query(eventNo,
new MeasurementRequestHeaders(appKey, timestamp, nonce, signature, null)));
}
@@ -36,35 +36,43 @@ public class MeasurementRequestAuthenticator {
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "第三方测量开放接口未启用");
}
requireHeader(headers.appKey(), "X-App-Key");
requireHeader(headers.timestamp(), "X-Timestamp");
requireHeader(headers.nonce(), "X-Nonce");
requireHeader(headers.signature(), "X-Signature");
validateTokenLength(headers.appKey(), 128, "X-App-Key");
if (requireIdempotencyKey) {
requireHeader(headers.idempotencyKey(), "Idempotency-Key");
}
validateTokenLength(headers.nonce(), 128, "X-Nonce");
if (requireIdempotencyKey) {
validateTokenLength(headers.idempotencyKey(), 128, "Idempotency-Key");
}
MeasurementOpenApiProperties.AppCredential credential = properties.getApps().get(headers.appKey());
if (credential == null || !credential.isEnabled() || !StringUtils.hasText(credential.getSecret())
if (credential == null || !credential.isEnabled()
|| properties.isSignatureRequired() && !StringUtils.hasText(credential.getSecret())
|| credential.getTenantId() == null || credential.getTenantId() <= 0
|| credential.getSubjectId() == null || credential.getSubjectId() <= 0) {
throw unauthorized("应用凭据无效");
}
long timestamp = parseTimestamp(headers.timestamp());
Duration drift = Duration.between(Instant.ofEpochMilli(timestamp), clock.instant()).abs();
if (drift.compareTo(properties.getMaxClockSkew()) > 0) {
throw unauthorized("请求时间戳已过期");
}
String canonical = canonicalString(method, path, headers.timestamp(), headers.nonce(),
requireIdempotencyKey ? headers.idempotencyKey() : "", sha256Hex(rawBody));
String expected = hmacHex(credential.getSecret(), canonical);
if (!constantTimeEquals(expected, headers.signature())) {
throw unauthorized("请求签名无效");
if (properties.isSignatureRequired()) {
requireHeader(headers.timestamp(), "X-Timestamp");
requireHeader(headers.nonce(), "X-Nonce");
requireHeader(headers.signature(), "X-Signature");
validateTokenLength(headers.nonce(), 128, "X-Nonce");
long timestamp = parseTimestamp(headers.timestamp());
Duration drift = Duration.between(Instant.ofEpochMilli(timestamp), clock.instant()).abs();
if (drift.compareTo(properties.getMaxClockSkew()) > 0) {
throw unauthorized("请求时间戳已过期");
}
String canonical = canonicalString(method, path, headers.timestamp(), headers.nonce(),
requireIdempotencyKey ? headers.idempotencyKey() : "", sha256Hex(rawBody));
String expected = hmacHex(credential.getSecret(), canonical);
if (!constantTimeEquals(expected, headers.signature())) {
throw unauthorized("请求签名无效");
}
claimNonce(headers);
}
return new AuthenticatedMeasurementApp(headers.appKey(), credential.getTenantId(),
credential.getSubjectId(), Map.copyOf(credential.getWarehouseBindings()));
}
private void claimNonce(MeasurementRequestHeaders headers) {
try {
if (!nonceReplayGuard.claim(headers.appKey(), headers.nonce(), properties.getNonceTtl())) {
throw new BizException(ResultCode.CONFLICT, "Nonce 已使用,拒绝重放请求");
@@ -74,8 +82,6 @@ public class MeasurementRequestAuthenticator {
} catch (RuntimeException exception) {
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "Nonce 防重放服务暂不可用");
}
return new AuthenticatedMeasurementApp(headers.appKey(), credential.getTenantId(),
credential.getSubjectId(), Map.copyOf(credential.getWarehouseBindings()));
}
public static String canonicalString(String method, String path, String timestamp, String nonce,
@@ -0,0 +1,288 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808021317129",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T02:13:18.249Z",
"startedAt": "2026-08-08T02:13:17.129Z",
"success": false,
"fatal": null,
"summary": {
"total": 12,
"passed": 4,
"failed": 8
},
"modules": [
{
"name": "orders",
"startedAt": "2026-08-08T02:13:17.613Z",
"finishedAt": "2026-08-08T02:13:18.186Z",
"success": false,
"passed": 3,
"failed": 7,
"fatal": "缺少创建测试订单所需的收货仓或目的仓"
},
{
"name": "inbound-weight-ocr",
"startedAt": "2026-08-08T02:13:18.188Z",
"finishedAt": "2026-08-08T02:13:18.249Z",
"success": false,
"passed": 0,
"failed": 1,
"fatal": null
}
],
"scenarios": [
{
"name": "weight-image-recognition-and-confirmation",
"module": "inbound-weight-ocr",
"success": false,
"deploymentBlocked": false,
"passed": 0,
"failed": 1
}
],
"deploymentBlockers": [
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "Admin 查询客户固定箱型",
"evidence": "tenantId=10001",
"code": null,
"requestId": "000491d0-2057",
"traceId": null
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "固定四箱型顺序、内径及每边加2cm正确",
"evidence": "types=missing",
"code": null,
"requestId": "000491d0-2057",
"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": "0ab24bf5-f65c-4c9e-98ac-686032ee17c4, 5de04702-c17c-4a47-b824-ac05b6fa8801",
"traceId": "142f895f3b8546a6966df4ae56fcb6da",
"evidence": "",
"checkedAt": "2026-08-08T02:13:17.612Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 测试账号登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "0c5a6873-bb38-4be1-96f4-97ee438adbc5, 77a28b17-ead2-4562-9620-9c5ab9abc82b",
"traceId": "7fa548e89954462eb6c67db4bed51844",
"evidence": "username=merchant_admin",
"checkedAt": "2026-08-08T02:13:18.048Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询当前企业资料",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/iam/tenant/profile"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c578974b-d386-4c2d-88cd-0de93fcb1d7e, 5669ad2d-1772-4162-ae07-989f738a9b3d",
"traceId": "40123a07c80d4bdfb70a4b460d9d92a9",
"evidence": "",
"checkedAt": "2026-08-08T02:13:18.091Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 测试企业已审核且客户 ID 有效",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/iam/tenant/profile"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c578974b-d386-4c2d-88cd-0de93fcb1d7e, 5669ad2d-1772-4162-ae07-989f738a9b3d",
"traceId": "40123a07c80d4bdfb70a4b460d9d92a9",
"evidence": "tenantId=10001, status=ACTIVE",
"checkedAt": "2026-08-08T02:13:18.091Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "Admin 查询客户固定箱型",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=10001"
},
"http": 503,
"code": null,
"message": null,
"requestId": "000491d0-2057",
"traceId": null,
"evidence": "tenantId=10001",
"checkedAt": "2026-08-08T02:13:18.124Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "固定四箱型顺序、内径及每边加2cm正确",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=10001"
},
"http": 503,
"code": null,
"message": null,
"requestId": "000491d0-2057",
"traceId": null,
"evidence": "types=missing",
"checkedAt": "2026-08-08T02:13:18.124Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询国内收货仓",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=DOMESTIC"
},
"http": 503,
"code": null,
"message": null,
"requestId": "37626d2f-2058",
"traceId": null,
"evidence": "",
"checkedAt": "2026-08-08T02:13:18.159Z"
},
{
"module": "orders",
"scenario": null,
"name": "存在可用国内收货仓",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=DOMESTIC"
},
"http": 503,
"code": null,
"message": null,
"requestId": "37626d2f-2058",
"traceId": null,
"evidence": "count=0",
"checkedAt": "2026-08-08T02:13:18.159Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询 FBA 目的仓",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=FBA"
},
"http": 503,
"code": null,
"message": null,
"requestId": "000491d0-2059",
"traceId": null,
"evidence": "",
"checkedAt": "2026-08-08T02:13:18.186Z"
},
{
"module": "orders",
"scenario": null,
"name": "存在可用 FBA 目的仓",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=FBA"
},
"http": 503,
"code": null,
"message": null,
"requestId": "000491d0-2059",
"traceId": null,
"evidence": "count=0",
"checkedAt": "2026-08-08T02:13:18.186Z"
},
{
"module": "orders",
"scenario": null,
"name": "模块执行",
"passed": false,
"deploymentBlocker": false,
"request": null,
"http": null,
"code": null,
"message": null,
"requestId": null,
"traceId": null,
"evidence": "缺少创建测试订单所需的收货仓或目的仓",
"checkedAt": "2026-08-08T02:13:18.186Z"
},
{
"module": "inbound-weight-ocr",
"scenario": "weight-image-recognition-and-confirmation",
"name": "仅选择带本轮测试标记的可编辑入库单",
"passed": false,
"deploymentBlocker": false,
"request": null,
"http": null,
"code": null,
"message": null,
"requestId": null,
"traceId": null,
"evidence": "marker=ADMIN-API-20260808021317129, 未找到本轮创建且状态为 WAIT_MEASURE/MEASURING/MEASURE_REJECTED 的入库单",
"checkedAt": "2026-08-08T02:13:18.249Z"
}
]
}
@@ -0,0 +1,49 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808021429703",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T02:14:30.190Z",
"startedAt": "2026-08-08T02:14:29.703Z",
"success": false,
"fatal": "平台管理员登录失败,未执行业务模块",
"summary": {
"total": 1,
"passed": 0,
"failed": 1
},
"modules": [],
"scenarios": [],
"deploymentBlockers": [],
"createdResources": {},
"excluded": [
{
"scope": "dashboard",
"reason": "首页为静态演示数据,不调用后端 API"
},
{
"scope": "hidden-prototypes",
"reason": "隐藏原型页面尚无已确认后端契约"
}
],
"securityNote": "报告不保存登录密码、Access Token、Refresh Token、图片二进制或 OCR 原文全文。",
"steps": [
{
"module": "authentication",
"scenario": null,
"name": "平台管理员登录",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "S0503",
"message": "IAM 权限服务暂时不可用",
"requestId": "ff84ec01-d420-4118-9590-2c358d8654af",
"traceId": "f1f5b5009a1740db96bbf1efff8d0b09",
"evidence": "",
"checkedAt": "2026-08-08T02:14:30.190Z"
}
]
}
@@ -0,0 +1,378 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808021540611",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T02:15:45.804Z",
"startedAt": "2026-08-08T02:15:40.612Z",
"success": false,
"fatal": null,
"summary": {
"total": 17,
"passed": 13,
"failed": 4
},
"modules": [
{
"name": "orders",
"startedAt": "2026-08-08T02:15:41.594Z",
"finishedAt": "2026-08-08T02:15:45.414Z",
"success": false,
"passed": 12,
"failed": 3,
"fatal": "产品图片新契约未部署,无法创建带图片的隔离订单"
},
{
"name": "inbound-weight-ocr",
"startedAt": "2026-08-08T02:15:45.416Z",
"finishedAt": "2026-08-08T02:15:45.804Z",
"success": false,
"passed": 0,
"failed": 1,
"fatal": null
}
],
"scenarios": [
{
"name": "weight-image-recognition-and-confirmation",
"module": "inbound-weight-ocr",
"success": false,
"deploymentBlocked": false,
"passed": 0,
"failed": 1
}
],
"deploymentBlockers": [
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "Client 上传产品图片",
"evidence": "contentType=image/png",
"code": null,
"requestId": "700de02f-10",
"traceId": null
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "产品图片上传返回字符串文件 ID",
"evidence": "fileIdType=undefined, contentType=missing",
"code": null,
"requestId": "700de02f-10",
"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": "6d4b2e6d-8811-4f56-a889-e595ff7cb503, 7f19250e-90f1-49df-a269-0f758c4be79e",
"traceId": "d89b6dad97df4c2c842ba58703984168",
"evidence": "",
"checkedAt": "2026-08-08T02:15:41.592Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 测试账号登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "8032a1de-240f-431c-8e79-de1b31484353, 272c9865-6588-4524-929d-d3d049d6d95a",
"traceId": "adc915919bc3446fa4f9472da2d43352",
"evidence": "username=merchant_admin",
"checkedAt": "2026-08-08T02:15:42.008Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询当前企业资料",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/iam/tenant/profile"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "eafa4f3a-9cc3-4ee6-a91c-2e5fdc0e3ebe, ee54cfa1-250a-4a96-b540-e6a02962480f",
"traceId": "36a69786b59548b985c67f49d970a51c",
"evidence": "",
"checkedAt": "2026-08-08T02:15:42.655Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 测试企业已审核且客户 ID 有效",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/iam/tenant/profile"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "eafa4f3a-9cc3-4ee6-a91c-2e5fdc0e3ebe, ee54cfa1-250a-4a96-b540-e6a02962480f",
"traceId": "36a69786b59548b985c67f49d970a51c",
"evidence": "tenantId=10001, status=ACTIVE",
"checkedAt": "2026-08-08T02:15:42.655Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "Admin 查询客户固定箱型",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b701963f-025e-4612-af31-f7997c04094a, dc095ff3-98d0-4c26-b6b3-b51b5fcac89f",
"traceId": "48bb3f6f9ae941f09122277efdcb92ce",
"evidence": "tenantId=10001",
"checkedAt": "2026-08-08T02:15:43.480Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "固定四箱型顺序、内径及每边加2cm正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b701963f-025e-4612-af31-f7997c04094a, dc095ff3-98d0-4c26-b6b3-b51b5fcac89f",
"traceId": "48bb3f6f9ae941f09122277efdcb92ce",
"evidence": "types=SMALL,MEDIUM,LARGE,EXTRA_LARGE",
"checkedAt": "2026-08-08T02:15:43.480Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询国内收货仓",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=DOMESTIC"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "43c7b3a0-810e-4566-972b-c0ab53203622, 6c4c9fb8-8370-4744-bdf0-1a2ea0dc2fc9",
"traceId": "8797b3f5ce7c4a18928ba0353691eb41",
"evidence": "",
"checkedAt": "2026-08-08T02:15:44.944Z"
},
{
"module": "orders",
"scenario": null,
"name": "存在可用国内收货仓",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=DOMESTIC"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "43c7b3a0-810e-4566-972b-c0ab53203622, 6c4c9fb8-8370-4744-bdf0-1a2ea0dc2fc9",
"traceId": "8797b3f5ce7c4a18928ba0353691eb41",
"evidence": "count=2",
"checkedAt": "2026-08-08T02:15:44.944Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询 FBA 目的仓",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=FBA"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "26ba298e-5f94-4132-bf84-578083f21fad, bd16e2d8-0559-4b4b-9793-0daec1be7a90",
"traceId": "f0fa3d314cba4f96aaf8ebf4900ef648",
"evidence": "",
"checkedAt": "2026-08-08T02:15:45.113Z"
},
{
"module": "orders",
"scenario": null,
"name": "存在可用 FBA 目的仓",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=FBA"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "26ba298e-5f94-4132-bf84-578083f21fad, bd16e2d8-0559-4b4b-9793-0daec1be7a90",
"traceId": "f0fa3d314cba4f96aaf8ebf4900ef648",
"evidence": "count=200",
"checkedAt": "2026-08-08T02:15:45.113Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询可用计费渠道",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=193000000000200001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b8484f51-9267-4002-9a7b-40d2d4466d38, b3845f30-7193-4c73-b90a-8cb198149137",
"traceId": "7a6a7d17aae1429e8f48162205b7f2fa",
"evidence": "",
"checkedAt": "2026-08-08T02:15:45.192Z"
},
{
"module": "orders",
"scenario": null,
"name": "存在带计价税制的可用渠道",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=193000000000200001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b8484f51-9267-4002-9a7b-40d2d4466d38, b3845f30-7193-4c73-b90a-8cb198149137",
"traceId": "7a6a7d17aae1429e8f48162205b7f2fa",
"evidence": "count=15",
"checkedAt": "2026-08-08T02:15:45.193Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询发货客户候选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/partners/candidates?type=SHIPPER"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c0310692-e6a1-4dde-9828-c7f084c75cbe, 513ea693-c31e-47e7-a746-b8a9499ff48a",
"traceId": "5955689a145d42a8ad2b293b67b8b3fb",
"evidence": "",
"checkedAt": "2026-08-08T02:15:45.289Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "Client 上传产品图片",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "POST",
"path": "/api/merchant/v1/files/product-images"
},
"http": 503,
"code": null,
"message": null,
"requestId": "700de02f-10",
"traceId": null,
"evidence": "contentType=image/png",
"checkedAt": "2026-08-08T02:15:45.413Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "产品图片上传返回字符串文件 ID",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "POST",
"path": "/api/merchant/v1/files/product-images"
},
"http": 503,
"code": null,
"message": null,
"requestId": "700de02f-10",
"traceId": null,
"evidence": "fileIdType=undefined, contentType=missing",
"checkedAt": "2026-08-08T02:15:45.414Z"
},
{
"module": "orders",
"scenario": null,
"name": "模块执行",
"passed": false,
"deploymentBlocker": false,
"request": null,
"http": null,
"code": null,
"message": null,
"requestId": null,
"traceId": null,
"evidence": "产品图片新契约未部署,无法创建带图片的隔离订单",
"checkedAt": "2026-08-08T02:15:45.414Z"
},
{
"module": "inbound-weight-ocr",
"scenario": "weight-image-recognition-and-confirmation",
"name": "仅选择带本轮测试标记的可编辑入库单",
"passed": false,
"deploymentBlocker": false,
"request": null,
"http": null,
"code": null,
"message": null,
"requestId": null,
"traceId": null,
"evidence": "marker=ADMIN-API-20260808021540611, 未找到本轮创建且状态为 WAIT_MEASURE/MEASURING/MEASURE_REJECTED 的入库单",
"checkedAt": "2026-08-08T02:15:45.804Z"
}
]
}
@@ -0,0 +1,780 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808021651990",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T02:17:14.712Z",
"startedAt": "2026-08-08T02:16:51.990Z",
"success": false,
"fatal": null,
"summary": {
"total": 39,
"passed": 36,
"failed": 3
},
"modules": [
{
"name": "orders",
"startedAt": "2026-08-08T02:16:52.371Z",
"finishedAt": "2026-08-08T02:17:14.620Z",
"success": false,
"passed": 35,
"failed": 2,
"fatal": "订单审核后的入库单契约未部署,无法向 OCR/墨家模块交接"
},
{
"name": "inbound-weight-ocr",
"startedAt": "2026-08-08T02:17:14.621Z",
"finishedAt": "2026-08-08T02:17:14.712Z",
"success": false,
"passed": 0,
"failed": 1,
"fatal": null
}
],
"scenarios": [
{
"name": "weight-image-recognition-and-confirmation",
"module": "inbound-weight-ocr",
"success": false,
"deploymentBlocked": false,
"passed": 0,
"failed": 1
}
],
"deploymentBlockers": [
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "审核后生成隔离入库单",
"evidence": "20秒内未返回 inboundId",
"code": "S0503",
"requestId": "40ba574d-240f-47d9-a6cb-2aecc922b0ff",
"traceId": "75a3df822dce4d60a7b02e8906c487e2"
}
],
"createdResources": {
"orders": {
"marker": "ADMIN-API-20260808021651990",
"tenantId": "10001",
"orderId": "2085913101392740353",
"productImageFileId": "2085913100780371970",
"boxTypes": [
"SMALL",
"MEDIUM",
"LARGE",
"EXTRA_LARGE"
],
"inboundId": null,
"orderNo": "FL2085913101392740353"
}
},
"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": "e272618f-4e86-4b6a-ba0c-487af0c7ac6d, 49ea627a-9612-431a-8884-72fc25ffe15c",
"traceId": "36c286458df84c698ec00318c68f23dd",
"evidence": "",
"checkedAt": "2026-08-08T02:16:52.369Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 测试账号登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "12dbaf22-d81e-4a3a-9fc3-49958a9bd3ae, f224dadb-eefc-4d98-8b8e-79ef0c0414d0",
"traceId": "0f9813a6b2f24803a71c74a665acd36e",
"evidence": "username=merchant_admin",
"checkedAt": "2026-08-08T02:16:52.719Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询当前企业资料",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/iam/tenant/profile"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3368029c-78c4-4a7e-bf64-dfa159720729, b3bf6a65-de45-41f1-b211-d02f46ade1be",
"traceId": "c45e982e250c467a9483081e349c9487",
"evidence": "",
"checkedAt": "2026-08-08T02:16:52.745Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 测试企业已审核且客户 ID 有效",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/iam/tenant/profile"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3368029c-78c4-4a7e-bf64-dfa159720729, b3bf6a65-de45-41f1-b211-d02f46ade1be",
"traceId": "c45e982e250c467a9483081e349c9487",
"evidence": "tenantId=10001, status=ACTIVE",
"checkedAt": "2026-08-08T02:16:52.745Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "Admin 查询客户固定箱型",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "a54b86f9-cd41-493c-b02e-d8ca707b8142, 633dc5d0-7a56-49a2-8028-40be150ca3b4",
"traceId": "2b2232099c934fbf876d0b384a54dd2e",
"evidence": "tenantId=10001",
"checkedAt": "2026-08-08T02:16:52.779Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "固定四箱型顺序、内径及每边加2cm正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "a54b86f9-cd41-493c-b02e-d8ca707b8142, 633dc5d0-7a56-49a2-8028-40be150ca3b4",
"traceId": "2b2232099c934fbf876d0b384a54dd2e",
"evidence": "types=SMALL,MEDIUM,LARGE,EXTRA_LARGE",
"checkedAt": "2026-08-08T02:16:52.779Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询国内收货仓",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=DOMESTIC"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "7db19d61-25d3-4105-90d2-12118b7e3ccb, db7843ae-a339-4e87-877c-7be642074ade",
"traceId": "e6f82e098e9641ffb688549437438af6",
"evidence": "",
"checkedAt": "2026-08-08T02:16:52.824Z"
},
{
"module": "orders",
"scenario": null,
"name": "存在可用国内收货仓",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=DOMESTIC"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "7db19d61-25d3-4105-90d2-12118b7e3ccb, db7843ae-a339-4e87-877c-7be642074ade",
"traceId": "e6f82e098e9641ffb688549437438af6",
"evidence": "count=2",
"checkedAt": "2026-08-08T02:16:52.824Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询 FBA 目的仓",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=FBA"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "4e7d7a42-f778-47fa-a45d-128d003fb634, 62bee44e-e55a-4b72-8a6f-f2cc3b8c052c",
"traceId": "18698b25d0004d87bc0e00ea935b1526",
"evidence": "",
"checkedAt": "2026-08-08T02:16:52.897Z"
},
{
"module": "orders",
"scenario": null,
"name": "存在可用 FBA 目的仓",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=FBA"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "4e7d7a42-f778-47fa-a45d-128d003fb634, 62bee44e-e55a-4b72-8a6f-f2cc3b8c052c",
"traceId": "18698b25d0004d87bc0e00ea935b1526",
"evidence": "count=200",
"checkedAt": "2026-08-08T02:16:52.898Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询可用计费渠道",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=193000000000200001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "562d3951-bb94-4b66-810a-26f2026e5330, 2ae0789c-1f0d-4507-90b9-22583a0db68e",
"traceId": "400f283c7e5042caacbe50b60e97bf34",
"evidence": "",
"checkedAt": "2026-08-08T02:16:52.942Z"
},
{
"module": "orders",
"scenario": null,
"name": "存在带计价税制的可用渠道",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=193000000000200001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "562d3951-bb94-4b66-810a-26f2026e5330, 2ae0789c-1f0d-4507-90b9-22583a0db68e",
"traceId": "400f283c7e5042caacbe50b60e97bf34",
"evidence": "count=15",
"checkedAt": "2026-08-08T02:16:52.942Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询发货客户候选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/partners/candidates?type=SHIPPER"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f048d5dd-2640-435a-b765-58198f727648, cd8380eb-c1bd-4089-ac1a-12845ff1a0b7",
"traceId": "e86dfd6fb2254adc9b7065fea9ebd0bc",
"evidence": "",
"checkedAt": "2026-08-08T02:16:52.978Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "Client 上传产品图片",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/merchant/v1/files/product-images"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "801af666-8a7d-43c2-8a35-b0c4caafc377, 6e6b965b-1f72-43ef-b010-b06a657e2a84",
"traceId": "3ed0546f615a455a911a0c6c4f135c71",
"evidence": "contentType=image/png",
"checkedAt": "2026-08-08T02:16:53.015Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "产品图片上传返回字符串文件 ID",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/merchant/v1/files/product-images"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "801af666-8a7d-43c2-8a35-b0c4caafc377, 6e6b965b-1f72-43ef-b010-b06a657e2a84",
"traceId": "3ed0546f615a455a911a0c6c4f135c71",
"evidence": "fileIdType=string, contentType=image/png",
"checkedAt": "2026-08-08T02:16:53.015Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "Client 可下载本轮产品图片",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/files/product-images/2085913100780371970"
},
"http": 200,
"code": null,
"message": null,
"requestId": "2286600c-e017-47a6-97ac-918a72a1aaac, 863ceda5-6595-405e-b840-b3c6cf58921a",
"traceId": "a4ba09bf35fa401bb7791b86c4fa22f5",
"evidence": "bytes=68, contentType=image/png",
"checkedAt": "2026-08-08T02:16:53.055Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "Client 创建四箱型隔离订单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/merchant/v1/orders/first-leg"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1528b351-0fcc-46f6-8cf7-79f377b4b805, 97f0d8fe-6c04-4f21-a097-d65851064fd1",
"traceId": "5f3bfaff8b994082ba048cdd8fa5a86d",
"evidence": "marker=ADMIN-API-20260808021651990",
"checkedAt": "2026-08-08T02:16:53.182Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 创建订单幂等重放",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/merchant/v1/orders/first-leg"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9761aea8-33b4-4bbf-aa91-842e62a7008e, 8b75e2d0-baa4-4cad-a0e0-f04b0e8ee6d1",
"traceId": "09006fcaf41d4c63a7ebc491977b53ae",
"evidence": "",
"checkedAt": "2026-08-08T02:16:53.233Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 创建幂等结果一致",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/merchant/v1/orders/first-leg"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9761aea8-33b4-4bbf-aa91-842e62a7008e, 8b75e2d0-baa4-4cad-a0e0-f04b0e8ee6d1",
"traceId": "09006fcaf41d4c63a7ebc491977b53ae",
"evidence": "orderId=2085913101392740353, replayedId=2085913101392740353",
"checkedAt": "2026-08-08T02:16:53.233Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询隔离订单草稿",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/orders/first-leg/2085913101392740353"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ca544cb8-1e75-421f-aa95-9faed072cb47, 87bcdea7-b151-416d-9d1b-105870a89e5d",
"traceId": "7a4e0dc0d8454770b2cfe718886b367e",
"evidence": "",
"checkedAt": "2026-08-08T02:16:53.273Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 草稿身份、marker 和状态正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/orders/first-leg/2085913101392740353"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ca544cb8-1e75-421f-aa95-9faed072cb47, 87bcdea7-b151-416d-9d1b-105870a89e5d",
"traceId": "7a4e0dc0d8454770b2cfe718886b367e",
"evidence": "orderNo=FL2085913101392740353, status=DRAFT",
"checkedAt": "2026-08-08T02:16:53.273Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "四箱型产品字段及图片契约完整回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/orders/first-leg/2085913101392740353"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ca544cb8-1e75-421f-aa95-9faed072cb47, 87bcdea7-b151-416d-9d1b-105870a89e5d",
"traceId": "7a4e0dc0d8454770b2cfe718886b367e",
"evidence": "cargoes=4, imageFileId=2085913100780371970",
"checkedAt": "2026-08-08T02:16:53.273Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 提交隔离订单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/merchant/v1/orders/first-leg/2085913101392740353/submit"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "8ee44952-0e26-4b01-8959-b5bbbe6cace3, 8f685c45-7a23-4bfa-9cf8-c57a18f17d23",
"traceId": "eee77623ea4448b092825f0f1d0eb999",
"evidence": "orderId=2085913101392740353",
"checkedAt": "2026-08-08T02:16:53.386Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 回查已提交订单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/orders/first-leg/2085913101392740353"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2da02ea1-cfcf-47d2-a10f-c39764c6b657, ff179a27-7e26-48c7-a409-202265885d86",
"traceId": "9399c417117d43ce9a6bcd02c517c0f2",
"evidence": "",
"checkedAt": "2026-08-08T02:16:53.431Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 订单已进入待审核状态",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/orders/first-leg/2085913101392740353"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2da02ea1-cfcf-47d2-a10f-c39764c6b657, ff179a27-7e26-48c7-a409-202265885d86",
"traceId": "9399c417117d43ce9a6bcd02c517c0f2",
"evidence": "status=SUBMITTED",
"checkedAt": "2026-08-08T02:16:53.431Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 查询统一订单列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders?page=1&size=100&tenantId=10001&orderNo=FL2085913101392740353"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "454e5e07-4344-4de8-afdb-99c366e263cf, abade6f4-90e8-4bee-bc96-45ec2a5897b9",
"traceId": "09b015cf86aa4e548e80ee93b9582ff3",
"evidence": "",
"checkedAt": "2026-08-08T02:16:53.527Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 统一订单列表只核对本轮订单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders?page=1&size=100&tenantId=10001&orderNo=FL2085913101392740353"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "454e5e07-4344-4de8-afdb-99c366e263cf, abade6f4-90e8-4bee-bc96-45ec2a5897b9",
"traceId": "09b015cf86aa4e548e80ee93b9582ff3",
"evidence": "orderId=2085913101392740353, found=true",
"checkedAt": "2026-08-08T02:16:53.527Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 查询统一订单详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/2085913101392740353?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c91190bc-2731-4c60-b569-f2058a410841, c07b4a89-d538-4f02-b3e0-9f60843c27ff",
"traceId": "a64f30af38884040973011a84d7f0954",
"evidence": "",
"checkedAt": "2026-08-08T02:16:53.587Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 统一订单详情身份与待审核状态正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/2085913101392740353?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c91190bc-2731-4c60-b569-f2058a410841, c07b4a89-d538-4f02-b3e0-9f60843c27ff",
"traceId": "a64f30af38884040973011a84d7f0954",
"evidence": "mainStatus=PENDING_REVIEW, timeline=2",
"checkedAt": "2026-08-08T02:16:53.587Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 查询头程订单列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=10001&keyword=FL2085913101392740353&page=1&size=100"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9d86bda2-951e-4c39-806d-9ea740d9d46b, 33437fe3-3f4b-4ab7-9248-eb222f1714a8",
"traceId": "e830d79cacc64cfcbccbeeeef37202b5",
"evidence": "",
"checkedAt": "2026-08-08T02:16:53.681Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 头程订单列表只核对本轮订单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=10001&keyword=FL2085913101392740353&page=1&size=100"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9d86bda2-951e-4c39-806d-9ea740d9d46b, 33437fe3-3f4b-4ab7-9248-eb222f1714a8",
"traceId": "e830d79cacc64cfcbccbeeeef37202b5",
"evidence": "orderId=2085913101392740353, found=true",
"checkedAt": "2026-08-08T02:16:53.682Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 查询头程订单详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/2085913101392740353?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3c6e7418-6ade-4094-ab9b-4a655fcb69c0, 106d28b4-800b-4cab-95d8-c0295d55fbd8",
"traceId": "4eb1b902666b4e52a42f851694ffdb56",
"evidence": "",
"checkedAt": "2026-08-08T02:16:53.732Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 头程订单详情聚合结构完整",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/2085913101392740353?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3c6e7418-6ade-4094-ab9b-4a655fcb69c0, 106d28b4-800b-4cab-95d8-c0295d55fbd8",
"traceId": "4eb1b902666b4e52a42f851694ffdb56",
"evidence": "cargoes=4, history=2",
"checkedAt": "2026-08-08T02:16:53.732Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "四箱型产品字段及图片契约完整回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/2085913101392740353?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3c6e7418-6ade-4094-ab9b-4a655fcb69c0, 106d28b4-800b-4cab-95d8-c0295d55fbd8",
"traceId": "4eb1b902666b4e52a42f851694ffdb56",
"evidence": "cargoes=4, imageFileId=2085913100780371970",
"checkedAt": "2026-08-08T02:16:53.732Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 为本轮订单添加内部备注",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/orders/first-leg/2085913101392740353/internal-notes?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6d9e2123-2645-405f-a151-50be16b7a9b9, 8a8ccea0-019d-47e7-8d02-3a1157cc56cc",
"traceId": "f9c9ab1efed24902a8961d211de3a53b",
"evidence": "",
"checkedAt": "2026-08-08T02:16:53.802Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 审核通过本轮订单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/orders/first-leg/2085913101392740353/approve?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ded05598-6114-4a8c-81db-a6ca56aa15cc, 0f3e0813-94c5-47a5-b5bd-f7a6761191e3",
"traceId": "05d5aab5d5ba45be8087bc4571b11385",
"evidence": "orderId=2085913101392740353",
"checkedAt": "2026-08-08T02:16:53.891Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "审核后生成隔离入库单",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/2085913101392740353?tenantId=10001"
},
"http": 200,
"code": "S0503",
"message": "统一订单履约详情响应无效",
"requestId": "40ba574d-240f-47d9-a6cb-2aecc922b0ff",
"traceId": "75a3df822dce4d60a7b02e8906c487e2",
"evidence": "20秒内未返回 inboundId",
"checkedAt": "2026-08-08T02:17:14.620Z"
},
{
"module": "orders",
"scenario": null,
"name": "模块执行",
"passed": false,
"deploymentBlocker": false,
"request": null,
"http": null,
"code": null,
"message": null,
"requestId": null,
"traceId": null,
"evidence": "订单审核后的入库单契约未部署,无法向 OCR/墨家模块交接",
"checkedAt": "2026-08-08T02:17:14.620Z"
},
{
"module": "inbound-weight-ocr",
"scenario": "weight-image-recognition-and-confirmation",
"name": "仅选择带本轮测试标记的可编辑入库单",
"passed": false,
"deploymentBlocker": false,
"request": null,
"http": null,
"code": null,
"message": null,
"requestId": null,
"traceId": null,
"evidence": "marker=ADMIN-API-20260808021651990, 未找到本轮创建且状态为 WAIT_MEASURE/MEASURING/MEASURE_REJECTED 的入库单",
"checkedAt": "2026-08-08T02:17:14.712Z"
}
]
}
File diff suppressed because it is too large Load Diff
+197
View File
@@ -61,6 +61,8 @@ const report = {
tenantId: null,
membershipId: null,
reviewId: null,
businessLicenseQualificationId: null,
businessLicenseDocumentUrl: null,
productImageFileId: null,
enterpriseAddressId: null,
receivingWarehouseId: null,
@@ -367,6 +369,8 @@ async function merchantAfterApprovalModule() {
const businessLicense = arrayData(approvedQualifications.json.data)
.find((item) => item.qualificationType === 'BUSINESS_LICENSE')
assertPresent(businessLicense?.documentUrl, 'business license documentUrl')
report.resources.businessLicenseQualificationId = businessLicense.id ?? businessLicense.qualificationId
report.resources.businessLicenseDocumentUrl = businessLicense.documentUrl
await apiStep('merchant-session', 'preview approved qualification file', {
method: 'GET', path: apiPath(businessLicense.documentUrl), token: merchantToken, responseType: 'bytes',
}, ({ status, bytes, headers }) => {
@@ -524,6 +528,9 @@ async function rbacAndMemberModule() {
.filter((item) => item.status === 'ACTIVE' && desiredCodes.includes(item.permissionCode))
assertEqual(grantedPermissions.length, desiredCodes.length, 'order permission count')
const permissionIds = grantedPermissions.map((item) => item.id)
const orderListPermissionId = grantedPermissions
.find((item) => item.permissionCode === 'merchant:order:list')?.id
assertPresent(orderListPermissionId, 'merchant:order:list permission id')
const ownerPermissions = await apiStep('menu-permissions', 'load owner effective permissions', {
method: 'GET', path: '/api/merchant/v1/iam/authorization/permissions', token: merchantToken,
@@ -773,6 +780,55 @@ async function rbacAndMemberModule() {
assertSetEqual(visibleClientMenus(arrayData(json.data)), ['account:profile:view', 'dashboard:view', 'order:first-leg:list'], 'visible Client menus')
}, ({ json }) => ({ backendPermissions: json.data, visibleClientMenus: visibleClientMenus(arrayData(json.data)) }))
await apiStep('menu-permissions', 'remove order view permission from assigned role', {
method: 'PUT', path: '/api/merchant/v1/iam/roles/permissions', token: merchantToken,
body: { roleId, permissionIds: [orderListPermissionId] },
}, ({ json }) => requireSuccess(json), () => ({ roleId, permissionCodes: [desiredCodes[0]] }))
await softApiStep('menu-permissions', 'permission removal revokes stale member token', {
method: 'GET', path: `/api/merchant/v1/orders/first-leg/${report.resources.orderIds[0]}`, token: memberToken,
}, ({ json, status }) => {
assertTrue([200, 401, 403].includes(status), `unexpected HTTP status ${status}`)
assertTrue(['A0401', 'A0403'].includes(String(json?.code)), `expected A0401/A0403, actual=${json?.code}`)
}, ({ json }) => ({ rejectionCode: json?.code, message: json?.message }))
const reducedPermissionLogin = await apiStep('menu-permissions', 'member re-login after permission removal', {
method: 'POST', path: '/api/auth/login', body: { username: memberUsername, password: memberPassword },
}, ({ json }) => {
requireSuccess(json)
assertPresent(json.data?.accessToken, 'member accessToken')
}, () => ({ username: memberUsername }))
memberToken = reducedPermissionLogin.json.data.accessToken
await apiStep('menu-permissions', 'permission removal is reflected after re-login', {
method: 'GET', path: '/api/merchant/v1/iam/authorization/permissions', token: memberToken,
}, ({ json }) => {
requireSuccess(json)
assertSetEqual(arrayData(json.data), [desiredCodes[0]], 'reduced member permissions')
}, ({ json }) => ({ backendPermissions: json.data }))
await apiStep('menu-permissions', 'member without view permission cannot read order detail', {
method: 'GET', path: `/api/merchant/v1/orders/first-leg/${report.resources.orderIds[0]}`, token: memberToken,
}, ({ json, status }) => {
assertTrue([200, 403].includes(status), `unexpected HTTP status ${status}`)
assertEqual(String(json?.code), 'A0403', 'business code')
}, ({ json }) => ({ rejectionCode: json?.code }))
await apiStep('menu-permissions', 'restore assigned role permissions', {
method: 'PUT', path: '/api/merchant/v1/iam/roles/permissions', token: merchantToken,
body: { roleId, permissionIds },
}, ({ json }) => requireSuccess(json), () => ({ roleId, permissionCodes: desiredCodes }))
const restoredPermissionLogin = await apiStep('menu-permissions', 'member re-login after permission restoration', {
method: 'POST', path: '/api/auth/login', body: { username: memberUsername, password: memberPassword },
}, ({ json }) => {
requireSuccess(json)
assertPresent(json.data?.accessToken, 'member accessToken')
}, () => ({ username: memberUsername }))
memberToken = restoredPermissionLogin.json.data.accessToken
await apiStep('menu-permissions', 'restored permission allows order detail again', {
method: 'GET', path: `/api/merchant/v1/orders/first-leg/${report.resources.orderIds[0]}`, token: memberToken,
}, ({ json }) => {
requireSuccess(json)
assertEqual(String(json.data?.order?.id), String(report.resources.orderIds[0]), 'order id')
}, ({ json }) => ({ orderId: json.data.order.id }))
const editedMemberBody = {
displayName: `已编辑订单成员-${marker}`.slice(0, 64),
phone: memberBody.phone,
@@ -811,6 +867,12 @@ async function rbacAndMemberModule() {
requireSuccess(json)
assertEqual(json.data?.status, 'DISABLED', 'member status')
}, ({ json }) => ({ membershipId: json.data.membershipId, status: json.data.status }))
await softApiStep('rbac-member', 'disabled member stale token is rejected', {
method: 'GET', path: '/api/merchant/v1/orders/first-leg?page=1&size=20', token: memberToken,
}, ({ json, status }) => {
assertTrue([200, 401, 403].includes(status), `unexpected HTTP status ${status}`)
assertTrue(['A0401', 'A0403'].includes(String(json?.code)), `expected A0401/A0403, actual=${json?.code}`)
}, ({ json }) => ({ membershipId, rejectionCode: json?.code }))
await apiStep('rbac-member', 'disabled member login is rejected', {
method: 'POST', path: '/api/auth/login', body: { username: memberUsername, password: resetMemberPassword },
}, ({ json, status }) => {
@@ -824,6 +886,17 @@ async function rbacAndMemberModule() {
assertEqual(json.data?.status, 'ACTIVE', 'member status')
}, ({ json }) => ({ membershipId: json.data.membershipId, status: json.data.status }))
const enabledMemberLogin = await apiStep('rbac-member', 'member re-login after account is enabled', {
method: 'POST', path: '/api/auth/login', body: { username: memberUsername, password: resetMemberPassword },
}, ({ json }) => {
requireSuccess(json)
assertPresent(json.data?.accessToken, 'member accessToken')
}, () => ({ username: memberUsername }))
memberToken = enabledMemberLogin.json.data.accessToken
await apiStep('rbac-member', 're-enabled member can list orders before roles are cleared', {
method: 'GET', path: '/api/merchant/v1/orders/first-leg?page=1&size=20', token: memberToken,
}, ({ json }) => requireSuccess(json), ({ json }) => ({ total: json.data?.total }))
await apiStep('rbac-member', 'clear enterprise member roles', {
method: 'PUT', path: '/api/merchant/v1/iam/members/roles', token: merchantToken,
body: { membershipId, roleIds: [] },
@@ -882,6 +955,33 @@ async function orderLifecycleModule() {
assertEqual(String(json?.code), 'A0400', 'business code')
}, ({ json }) => ({ rejectionCode: json?.code, message: json?.message }))
const emptyImageForm = new FormData()
emptyImageForm.append('file', new Blob([], { type: 'image/png' }), `empty-product-${marker}.png`)
await softApiStep('order-product-image', 'empty product image is rejected', {
method: 'POST', path: '/api/merchant/v1/files/product-images', token: merchantToken, body: emptyImageForm,
}, ({ json, status }) => {
assertEqual(status, 200, 'HTTP status')
assertEqual(String(json?.code), 'A0400', 'business code')
}, ({ json }) => ({ rejectionCode: json?.code, message: json?.message }))
const forgedImageForm = new FormData()
forgedImageForm.append('file', new Blob(['not-a-real-png'], { type: 'image/png' }), `forged-product-${marker}.png`)
await softApiStep('order-product-image', 'forged product image signature is rejected', {
method: 'POST', path: '/api/merchant/v1/files/product-images', token: merchantToken, body: forgedImageForm,
}, ({ json, status }) => {
assertEqual(status, 200, 'HTTP status')
assertEqual(String(json?.code), 'A0400', 'business code')
}, ({ json }) => ({ rejectionCode: json?.code, message: json?.message }))
const oversizedImageForm = new FormData()
oversizedImageForm.append('file', new Blob([Buffer.alloc(5 * 1024 * 1024 + 1)], { type: 'image/png' }), `oversized-product-${marker}.png`)
await softApiStep('order-product-image', 'product image over 5MB is rejected', {
method: 'POST', path: '/api/merchant/v1/files/product-images', token: merchantToken, body: oversizedImageForm,
}, ({ json, status }) => {
assertEqual(status, 200, 'HTTP status')
assertEqual(String(json?.code), 'A0400', 'business code')
}, ({ json }) => ({ rejectionCode: json?.code, message: json?.message }))
await apiStep('order-lifecycle', 'download own product image', {
method: 'GET', path: `/api/merchant/v1/files/product-images/${productImageFileId}`, token: merchantToken,
responseType: 'bytes',
@@ -925,6 +1025,18 @@ async function orderLifecycleModule() {
name: 'invalid product image file ID is rejected',
mutate(value) { value.cargoes[0].productImageFileId = -1 },
},
{
name: 'zero package count weight and volume are rejected',
mutate(value) {
value.cargoes[0].packageCount = 0
value.cargoes[0].grossWeightKg = 0
value.cargoes[0].volumeCbm = 0
},
},
{
name: 'customer remark over 1000 characters is rejected',
mutate(value) { value.customerRemark = 'x'.repeat(1001) },
},
]
for (const testCase of invalidOrderCases) {
const invalidPayload = structuredClone(payload)
@@ -937,6 +1049,19 @@ async function orderLifecycleModule() {
assertEqual(String(json?.code), 'A0422', 'business code')
}, ({ json }) => ({ rejectionCode: json?.code, message: json?.message }))
}
await softApiStep('order-validation', 'missing Idempotency-Key is rejected', {
method: 'POST', path: '/api/merchant/v1/orders/first-leg', token: merchantToken, body: payload,
}, ({ json, status }) => {
assertTrue([200, 400].includes(status), `unexpected HTTP status ${status}`)
assertEqual(String(json?.code), 'A0400', 'business code')
}, ({ json }) => ({ rejectionCode: json?.code, message: json?.message }))
await softApiStep('order-validation', 'Idempotency-Key over 128 characters is rejected', {
method: 'POST', path: '/api/merchant/v1/orders/first-leg', token: merchantToken,
headers: { 'Idempotency-Key': 'x'.repeat(129) }, body: payload,
}, ({ json, status }) => {
assertEqual(status, 200, 'HTTP status')
assertEqual(String(json?.code), 'A0422', 'business code')
}, ({ json }) => ({ rejectionCode: json?.code, message: json?.message }))
const createKey = newIdempotencyKey('approved-create')
const created = await apiStep('order-lifecycle', 'create valid draft after approval', {
method: 'POST', path: '/api/merchant/v1/orders/first-leg', token: merchantToken,
@@ -1044,6 +1169,41 @@ async function orderLifecycleModule() {
assertPresent(pageRecords(json.data).find((item) => String(item.orderId) === String(orderId)), 'submitted status-filtered order')
}, ({ json }) => ({ total: json.data?.total, orderId, status: 'SUBMITTED' }))
await apiStep('order-filters', 'warehouse channel and destination filters contain submitted order', {
method: 'GET',
path: `/api/merchant/v1/orders/first-leg?page=1&size=50&receivingWarehouseId=${payload.receivingWarehouseId}`
+ `&channelName=${encodeURIComponent(payload.channelName)}&destinationType=${payload.destinationType}`
+ `&destinationWarehouseId=${payload.destinationWarehouseId}`,
token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertPresent(pageRecords(json.data).find((item) => String(item.orderId) === String(orderId)), 'combined-filtered order')
}, ({ json }) => ({ total: json.data?.total, orderId }))
await apiStep('order-filters', 'sender phone filter contains submitted order', {
method: 'GET', path: `/api/merchant/v1/orders/first-leg?page=1&size=50&customerContact=${encodeURIComponent(legalPhone)}`, token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertPresent(pageRecords(json.data).find((item) => String(item.orderId) === String(orderId)), 'sender-phone-filtered order')
}, ({ json }) => ({ total: json.data?.total, orderId }))
await apiStep('order-filters', 'unmatched keyword returns an empty order page', {
method: 'GET', path: `/api/merchant/v1/orders/first-leg?page=1&size=50&keyword=${randomUUID()}`, token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertEqual(pageRecords(json.data).length, 0, 'unmatched order count')
}, ({ json }) => ({ total: json.data?.total }))
await softApiStep('order-validation', 'invalid order status filter is rejected', {
method: 'GET', path: '/api/merchant/v1/orders/first-leg?page=1&size=20&status=UNKNOWN', token: merchantToken,
}, ({ json, status }) => {
assertTrue([200, 400].includes(status), `unexpected HTTP status ${status}`)
assertEqual(String(json?.code), 'A0400', 'business code')
}, ({ json }) => ({ rejectionCode: json?.code, message: json?.message }))
await apiStep('order-boundary', 'unknown order detail is rejected', {
method: 'GET', path: '/api/merchant/v1/orders/first-leg/1', token: merchantToken,
}, ({ json, status }) => {
assertEqual(status, 200, 'HTTP status')
assertEqual(String(json?.code), 'A0404', 'business code')
}, ({ json }) => ({ orderId: 1, rejectionCode: json?.code }))
await softApiStep('order-boundary', 'submitted order cannot be edited by client', {
method: 'PUT', path: `/api/merchant/v1/orders/first-leg/${orderId}`, token: merchantToken, body: editedPayload,
}, ({ json, status }) => {
@@ -1142,9 +1302,37 @@ async function isolationModule() {
assertTrue([200, 403, 404].includes(status), `unexpected HTTP status ${status}`)
assertTrue(['A0403', 'A0404'].includes(String(json?.code)), `expected A0403/A0404, actual=${json?.code}`)
}, ({ json }) => ({ fileId: report.resources.productImageFileId, rejectionCode: json?.code }))
await softApiStep('isolation', 'cross-client member detail access is rejected', {
method: 'GET', path: `/api/merchant/v1/iam/members/${report.resources.testMembershipId}`, token: existingClientToken,
}, ({ json, status }) => {
assertEqual(status, 200, 'HTTP status')
assertTrue(['A0403', 'A0404'].includes(String(json?.code)), `expected A0403/A0404, actual=${json?.code}`)
}, ({ json }) => ({ membershipId: report.resources.testMembershipId, rejectionCode: json?.code }))
await apiStep('isolation', 'existing client address list excludes new enterprise address', {
method: 'GET', path: '/api/merchant/v1/iam/tenant/addresses', token: existingClientToken,
}, ({ json }) => {
requireSuccess(json)
assertTrue(!arrayData(json.data).some((item) => String(item.id) === String(report.resources.enterpriseAddressId)),
'cross-client address must not be visible')
}, ({ json }) => ({ addressCount: arrayData(json.data).length, excludedAddressId: report.resources.enterpriseAddressId }))
await apiStep('isolation', 'existing client qualification list excludes new enterprise qualification', {
method: 'GET', path: '/api/merchant/v1/iam/tenant/qualifications', token: existingClientToken,
}, ({ json }) => {
requireSuccess(json)
assertTrue(!arrayData(json.data).some((item) => String(item.id ?? item.qualificationId)
=== String(report.resources.businessLicenseQualificationId)), 'cross-client qualification must not be visible')
}, ({ json }) => ({ qualificationCount: arrayData(json.data).length,
excludedQualificationId: report.resources.businessLicenseQualificationId }))
await softApiStep('isolation', 'cross-client qualification file access is rejected', {
method: 'GET', path: apiPath(report.resources.businessLicenseDocumentUrl), token: existingClientToken,
}, ({ json, status }) => {
assertTrue([200, 403, 404].includes(status), `unexpected HTTP status ${status}`)
assertTrue(['A0403', 'A0404'].includes(String(json?.code)), `expected A0403/A0404, actual=${json?.code}`)
}, ({ json }) => ({ qualificationId: report.resources.businessLicenseQualificationId, rejectionCode: json?.code }))
}
async function accountSecurityModule() {
const consumedRefreshToken = merchantRefreshToken
const rotated = await apiStep('account-security', 'rotate refresh token', {
method: 'POST', path: '/api/auth/refresh', body: { refreshToken: merchantRefreshToken },
}, ({ json }) => {
@@ -1154,6 +1342,12 @@ async function accountSecurityModule() {
}, () => ({ rotated: true }))
merchantToken = rotated.json.data.accessToken
merchantRefreshToken = rotated.json.data.refreshToken
await apiStep('account-security', 'consumed refresh token cannot be replayed', {
method: 'POST', path: '/api/auth/refresh', body: { refreshToken: consumedRefreshToken },
}, ({ json, status }) => {
assertEqual(status, 200, 'HTTP status')
assertEqual(String(json?.code), 'A0401', 'business code')
}, ({ json }) => ({ rejectionCode: json?.code }))
temporaryOwnerPassword = `Temporary${randomUUID().replaceAll('-', '').slice(0, 14)}6!`
await apiStep('account-security', 'change owner password to temporary value', {
@@ -1204,6 +1398,9 @@ async function accountSecurityModule() {
assertEqual(status, 200, 'HTTP status')
assertEqual(String(json?.code), 'A0401', 'business code')
}, ({ json }) => ({ rejectionCode: json.code }))
await apiStep('account-security', 'repeated logout remains idempotent', {
method: 'POST', path: '/api/auth/logout', body: { refreshToken: loggedOutRefreshToken },
}, ({ json }) => requireSuccess(json), () => ({ repeated: true }))
}
async function recoverOwnerPassword() {