This commit is contained in:
2026-08-08 10:11:04 +08:00
parent f3c2bafec8
commit fc14856ea3
64 changed files with 50932 additions and 30 deletions
@@ -55,6 +55,12 @@ public class InternalAccountController {
@PathVariable Long id, @Valid @RequestBody UpdateAccountStatusRequest request) {
verify(token); service.updateStatus(id, request); return ApiResponse.success(null);
}
@PostMapping("/sessions/revoke")
public ApiResponse<Void> revokeSessions(
@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
@Valid @RequestBody RevokeAccountSessionsRequest request) {
verify(token); service.revokeSessions(request.accountIds()); return ApiResponse.success(null);
}
@PutMapping("/{id}/profile")
public ApiResponse<Void> profile(@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
@PathVariable Long id, @Valid @RequestBody UpdateAccountProfileRequest request) {
@@ -2,10 +2,12 @@ package com.baiyue.auth.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import jakarta.validation.constraints.Pattern;
import java.time.LocalDateTime;
import java.util.Set;
/** 账号安全及内部账号管理契约。 */
public final class AccountManagementDtos {
@@ -35,6 +37,12 @@ public final class AccountManagementDtos {
}
public record UpdateAccountProfileRequest(@NotBlank @Size(max = 64) String displayName) { }
public record UpdateAccountStatusRequest(@NotBlank String status) { }
public record RevokeAccountSessionsRequest(
@NotEmpty @Size(max = 500) Set<@NotNull Long> accountIds) {
public RevokeAccountSessionsRequest {
accountIds = Set.copyOf(accountIds);
}
}
public record ResetPasswordRequest(@NotBlank @Size(min = 8, max = 64)
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).+$", message = "密码必须同时包含字母和数字") String newPassword,
Boolean forceChangePassword) {
@@ -13,6 +13,7 @@ import com.baiyue.common.audit.BusinessAudit;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
/** 账号生命周期管理,所有密码只以 BCrypt 摘要写入数据库。 */
@Service
@@ -98,6 +99,14 @@ public class AccountManagementService {
bumpVersion(account);
}
@Transactional
@BusinessAudit(event = "ACCOUNT_SESSIONS_REVOKE", resourceType = "ACCOUNT_BATCH",
resourceId = "'RBAC_CHANGE'", description = "RBAC 变更批量撤销账号会话",
captureBefore = false, captureAfter = false)
public void revokeSessions(Set<Long> accountIds) {
accountIds.forEach(accountId -> bumpVersion(required(accountId)));
}
@Transactional
@BusinessAudit(event = "ACCOUNT_PROFILE_CHANGE", resourceType = "ACCOUNT", resourceId = "#accountId",
description = "同步账号显示名称", captureAfter = false)
@@ -118,11 +127,11 @@ public class AccountManagementService {
@BusinessAudit(event = "CUSTOMER_ACCOUNT_ARCHIVE", resourceType = "TENANT", resourceId = "#tenantId",
description = "归档客户并撤销客户账号", captureBefore = false, captureAfter = false)
public void archiveTenant(Long tenantId) {
for (AuthAccount account : accountRepository.findMerchantByTenant(tenantId)) {
for (AuthAccount account : repository.findMerchantByTenant(tenantId)) {
account.setAccountStatus("DISABLED");
account.setPasswordChangeRequired(false);
account.setTokenVersion((account.getTokenVersion() == null ? 0 : account.getTokenVersion()) + 1);
accountRepository.save(account);
repository.save(account);
tokenSessionStore.revokeAll(account.getId());
}
}
@@ -156,7 +156,7 @@ public class AuthService {
@Transactional
@BusinessAudit(event = "CUSTOMER_IMPERSONATION_EXCHANGE", resourceType = "IMPERSONATION",
resourceId = "#code", description = "兑换客户代登录授权码", captureBefore = false, captureAfter = false)
resourceId = "'IMPERSONATION_EXCHANGE'", description = "兑换客户代登录授权码", captureBefore = false, captureAfter = false)
public ImpersonationTokenResponse exchangeImpersonationTicket(String code) {
TokenSessionStore.ImpersonationTicket ticket = tokenSessionStore.consumeImpersonationTicket(code);
if (ticket == null) throw new BizException(ResultCode.UNAUTHORIZED, "代登录授权码无效或已过期");
@@ -24,6 +24,7 @@ import jakarta.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.time.LocalDateTime;
import java.util.Set;
@DisplayName("账号管理服务")
@SpringJUnitConfig(classes = {AccountManagementService.class, AccountManagementServiceTest.TestConfig.class})
@@ -154,6 +155,21 @@ class AccountManagementServiceTest {
verify(repository).save(account);
verify(sessions).revokeAll(10L);
}
@Test void rbacChangeRevokesAccessAndRefreshSessionsAndBumpsTokenVersions() {
AuthAccount second = new AuthAccount();
second.setId(11L); second.setTokenVersion(7); second.setAccountStatus("ACTIVE");
when(repository.findById(11L)).thenReturn(second);
service.revokeSessions(Set.of(10L, 11L));
assertEquals(3, account.getTokenVersion());
assertEquals(8, second.getTokenVersion());
verify(repository).save(account);
verify(repository).save(second);
verify(sessions).revokeAll(10L);
verify(sessions).revokeAll(11L);
}
@Configuration
static class TestConfig {
@Bean
@@ -55,6 +55,13 @@ public class InboundMeasurementRepository {
.eq(InboundOrderEntity::getId, inbound.getId())) == 1;
}
public boolean updateInboundWeightSummary(InboundOrderEntity inbound) {
return inboundOrders.update(inbound, new LambdaUpdateWrapper<InboundOrderEntity>()
.eq(InboundOrderEntity::getTenantId, inbound.getTenantId())
.eq(InboundOrderEntity::getId, inbound.getId())
.set(InboundOrderEntity::getActualGrossWeightKg, inbound.getActualGrossWeightKg())) == 1;
}
public InboundMeasurementEntity findMeasurement(long tenantId, long inboundId, long measurementId) {
return measurements.selectOne(new LambdaQueryWrapper<InboundMeasurementEntity>()
.eq(InboundMeasurementEntity::getTenantId, tenantId)
@@ -292,7 +292,7 @@ public class InboundMeasurementService {
inbound.setProcessStatus("MEASURING");
}
inbound.setUpdatedBy(operatorId);
if (!repository.updateInbound(inbound)) {
if (!repository.updateInboundWeightSummary(inbound)) {
throw concurrentModification();
}
}
@@ -2,6 +2,7 @@ package com.baiyue.firstleg.repository;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baiyue.firstleg.entity.FeeItemEntity;
@@ -57,6 +58,7 @@ class TenantScopedMutationRepositoryTest {
static void initializeMetadata() {
initialize(FulfillmentTaskEntity.class);
initialize(InboundWeightResultEntity.class);
initialize(InboundOrderEntity.class);
initialize(InboundFeeDetailEntity.class);
}
@@ -90,6 +92,24 @@ class TenantScopedMutationRepositoryTest {
assertTenantAndId(wrapper.getValue());
}
@Test
@SuppressWarnings({"rawtypes", "unchecked"})
void weightSummaryUpdateExplicitlyClearsNullGrossWeight() {
InboundOrderEntity inbound = new InboundOrderEntity();
inbound.setTenantId(TENANT);
inbound.setId(ID);
inbound.setActualGrossWeightKg(null);
when(inboundOrders.update(any(), any(Wrapper.class))).thenReturn(1);
assertThat(measurementRepository.updateInboundWeightSummary(inbound)).isTrue();
ArgumentCaptor<Wrapper<InboundOrderEntity>> wrapper = ArgumentCaptor.forClass(Wrapper.class);
verify(inboundOrders).update(eq(inbound), wrapper.capture());
assertTenantAndId(wrapper.getValue());
assertThat(((LambdaUpdateWrapper<InboundOrderEntity>) wrapper.getValue()).getSqlSet())
.contains("actual_gross_weight_kg");
}
@Test
@SuppressWarnings({"rawtypes", "unchecked"})
void feeDetailDeleteMatchesTenantAndId() {
@@ -125,7 +125,7 @@ class InboundMeasurementServiceTest {
when(repository.findWeight(1L, 10L, 20L)).thenReturn(current);
when(repository.updateWeight(current)).thenReturn(true);
when(repository.listWeights(1L, 10L)).thenReturn(List.of(current, previous));
when(repository.updateInbound(inbound)).thenReturn(true);
when(repository.updateInboundWeightSummary(inbound)).thenReturn(true);
InboundMeasurementResponses.WeightResult response = service.confirmWeightResult(
1L, 10L, 20L, 9L, "操作员",
@@ -152,7 +152,7 @@ class InboundMeasurementServiceTest {
when(repository.findWeight(1L, 10L, 20L)).thenReturn(weight);
when(repository.updateWeight(weight)).thenReturn(true);
when(repository.listWeights(1L, 10L)).thenReturn(List.of(weight));
when(repository.updateInbound(inbound)).thenReturn(true);
when(repository.updateInboundWeightSummary(inbound)).thenReturn(true);
InboundMeasurementResponses.WeightResult response = service.confirmWeightResult(
1L, 10L, 20L, 9L, "操作员",
@@ -173,7 +173,7 @@ class InboundMeasurementServiceTest {
when(repository.findWeight(1L, 10L, 20L)).thenReturn(weight);
when(repository.updateWeight(weight)).thenReturn(true);
when(repository.listWeights(1L, 10L)).thenReturn(List.of(weight));
when(repository.updateInbound(inbound)).thenReturn(true);
when(repository.updateInboundWeightSummary(inbound)).thenReturn(true);
InboundMeasurementResponses.WeightResult response = service.confirmWeightResult(
1L, 10L, 20L, 9L, "操作员",
@@ -199,7 +199,7 @@ class InboundMeasurementServiceTest {
verify(repository, never()).updateWeight(any());
verify(repository, never()).insertRevision(any());
verify(repository, never()).updateInbound(any());
verify(repository, never()).updateInboundWeightSummary(any());
}
@Test
@@ -217,7 +217,7 @@ class InboundMeasurementServiceTest {
verify(repository, never()).updateWeight(any());
verify(repository, never()).insertRevision(any());
verify(repository, never()).updateInbound(any());
verify(repository, never()).updateInboundWeightSummary(any());
}
@Test
@@ -246,7 +246,7 @@ class InboundMeasurementServiceTest {
when(repository.findWeight(1L, 10L, 20L)).thenReturn(weight);
when(repository.updateWeight(weight)).thenReturn(true);
when(repository.listWeights(1L, 10L)).thenReturn(List.of(weight));
when(repository.updateInbound(inbound)).thenReturn(true);
when(repository.updateInboundWeightSummary(inbound)).thenReturn(true);
InboundMeasurementResponses.WeightResult response = service.confirmWeightResult(
1L, 10L, 20L, 9L, "操作员",
@@ -308,7 +308,7 @@ class InboundMeasurementServiceTest {
verify(repository, never()).updateWeight(any());
verify(repository, never()).insertRevision(any());
verify(repository, never()).updateInbound(any());
verify(repository, never()).updateInboundWeightSummary(any());
}
@Test
@@ -327,7 +327,7 @@ class InboundMeasurementServiceTest {
assertThat(response.version()).isEqualTo(2);
verify(repository, never()).updateWeight(any());
verify(repository, never()).insertRevision(any());
verify(repository, never()).updateInbound(any());
verify(repository, never()).updateInboundWeightSummary(any());
}
@Test
@@ -366,7 +366,7 @@ class InboundMeasurementServiceTest {
verify(repository, times(2)).updateWeight(weight);
verify(repository, times(1)).insertRevision(any(InboundWeightRevisionEntity.class));
verify(repository, never()).listWeights(anyLong(), anyLong());
verify(repository, never()).updateInbound(any());
verify(repository, never()).updateInboundWeightSummary(any());
}
@Test
@@ -383,7 +383,7 @@ class InboundMeasurementServiceTest {
verify(repository).deleteWeight(1L, 10L, 20L, 1);
verify(imageClient).delete("internal-token", 9L, 1L, 10L, 120L);
verify(repository, never()).updateInbound(any());
verify(repository, never()).updateInboundWeightSummary(any());
}
@Test
@@ -396,14 +396,36 @@ class InboundMeasurementServiceTest {
when(repository.findWeight(1L, 10L, 20L)).thenReturn(weight);
when(repository.deleteWeight(1L, 10L, 20L, 2)).thenReturn(true);
when(repository.listWeights(1L, 10L)).thenReturn(List.of());
when(repository.updateInbound(inbound)).thenReturn(true);
when(repository.updateInboundWeightSummary(inbound)).thenReturn(true);
when(imageClient.delete("internal-token", 9L, 1L, 10L, 120L))
.thenReturn(ApiResponse.success(null));
service.deleteWeightResult(1L, 10L, 20L, 2, 9L);
assertThat(inbound.getActualGrossWeightKg()).isNull();
verify(repository).updateInbound(inbound);
verify(repository).updateInboundWeightSummary(inbound);
}
@Test
void deletingConfirmedWeightRecalculatesTotalFromRemainingHistory() {
InboundOrderEntity inbound = inbound("MEASURING");
inbound.setActualGrossWeightKg(new BigDecimal("15.000"));
InboundWeightResultEntity deleted = weight(20L, "10.000", "KG", "CONFIRMED", 2);
deleted.setFinalWeightKg(new BigDecimal("10.000"));
InboundWeightResultEntity remaining = weight(21L, "5.000", "KG", "CONFIRMED", 1);
remaining.setFinalWeightKg(new BigDecimal("5.000"));
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
when(repository.findWeight(1L, 10L, 20L)).thenReturn(deleted);
when(repository.deleteWeight(1L, 10L, 20L, 2)).thenReturn(true);
when(repository.listWeights(1L, 10L)).thenReturn(List.of(remaining));
when(repository.updateInboundWeightSummary(inbound)).thenReturn(true);
when(imageClient.delete("internal-token", 9L, 1L, 10L, 120L))
.thenReturn(ApiResponse.success(null));
service.deleteWeightResult(1L, 10L, 20L, 2, 9L);
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("5.000");
verify(repository).updateInboundWeightSummary(inbound);
}
@Test
@@ -5,6 +5,7 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Set;
/** auth-service 内部账号管理客户端。 */
@FeignClient(name = "auth-service", contextId = "authAccountClient",
@@ -30,6 +31,9 @@ public interface AuthAccountClient {
@RequestBody ImpersonationTicketRequest request);
@PutMapping("/{id}/scope") ApiResponse<Void> scope(@RequestHeader("X-Internal-Service-Token") String token,
@PathVariable Long id, @RequestBody ScopeRequest request);
@PostMapping("/sessions/revoke") ApiResponse<Void> revokeSessions(
@RequestHeader("X-Internal-Service-Token") String token,
@RequestBody RevokeAccountSessionsRequest request);
@DeleteMapping("/{id}/compensation") ApiResponse<Void> compensate(@RequestHeader("X-Internal-Service-Token") String token,
@PathVariable Long id);
@@ -56,6 +60,11 @@ public interface AuthAccountClient {
public PasswordRequest(String newPassword) { this(newPassword, false); }
}
record ScopeRequest(Long tenantId, Long membershipId, String appCode) { }
record RevokeAccountSessionsRequest(Set<Long> accountIds) {
public RevokeAccountSessionsRequest {
accountIds = Set.copyOf(accountIds);
}
}
record ImpersonationTicketRequest(Long targetAuthUserId, Long tenantId, Long membershipId,
Long actorUserId, Long actorMembershipId, String reason) { }
record ImpersonationTicketResponse(String code, String ticketId, long expiresIn) { }
@@ -1,4 +1,29 @@
/**
* 成员角色关联 MyBatis-Plus Mapper,负责 {@code iam_membership_role} 表的基础持久化操作。
*/
package com.baiyue.iam.mapper; import com.baiyue.iam.entity.IamMembershipRole; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @Mapper public interface IamMembershipRoleMapper extends BaseMapper<IamMembershipRole>{}
package com.baiyue.iam.mapper;
import com.baiyue.iam.entity.IamMembershipRole;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface IamMembershipRoleMapper extends BaseMapper<IamMembershipRole> {
@Select("""
SELECT DISTINCT u.auth_user_id
FROM iam_membership_role mr
JOIN iam_membership m ON m.id = mr.membership_id AND m.tenant_id = mr.tenant_id
JOIN iam_user u ON u.id = m.user_id
WHERE mr.tenant_id = #{tenantId}
AND mr.role_id = #{roleId}
AND m.deleted = 0
AND u.deleted = 0
ORDER BY u.auth_user_id
""")
List<Long> findAssignedAuthUserIds(@Param("tenantId") long tenantId,
@Param("roleId") Long roleId);
}
@@ -122,6 +122,10 @@ public class RbacRepository {
.eq(IamMembershipRole::getRoleId, roleId)) > 0;
}
public List<Long> findAssignedAuthUserIds(long tenantId, Long roleId) {
return membershipRoleMapper.findAssignedAuthUserIds(tenantId, roleId);
}
public void deleteRole(long tenantId, String appCode, Long roleId) {
rolePermissionMapper.delete(new LambdaQueryWrapper<IamRolePermission>()
.eq(IamRolePermission::getTenantId, tenantId)
@@ -1,8 +1,10 @@
package com.baiyue.iam.service;
import com.baiyue.common.audit.BusinessAudit;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.iam.client.AuthAccountClient;
import com.baiyue.iam.dto.RbacDtos.*;
import com.baiyue.iam.entity.IamMembership;
import com.baiyue.iam.entity.IamPermission;
@@ -11,6 +13,8 @@ import com.baiyue.iam.repository.MemberRepository;
import com.baiyue.iam.repository.RbacRepository;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import jakarta.annotation.Resource;
import feign.FeignException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.dao.DataIntegrityViolationException;
@@ -25,8 +29,12 @@ import java.util.Comparator;
/** 双端 RBAC 应用服务,严格按租户和应用域隔离。 */
@Service
public class RbacService {
private static final int SESSION_REVOCATION_BATCH_SIZE = 500;
@Resource private RbacRepository repository;
@Resource private MemberRepository memberRepository;
@Resource private AuthAccountClient authClient;
@Value("${baiyue.security.internal-service-token}")
private String internalToken;
public List<RoleResponse> roles(long tenantId, String appCode) {
return repository.findRoles(tenantId, appCode).stream()
@@ -75,11 +83,13 @@ public class RbacService {
@BusinessAudit(event = "ROLE_STATUS_CHANGE", resourceType = "ROLE", resourceId = "#roleId",
description = "启停角色", captureBefore = true, captureAfter = true)
@Transactional
public RoleResponse changeStatus(long tenantId, String appCode, Long roleId,
ChangeRoleStatusRequest request) {
IamRole role = requireCustomRole(tenantId, appCode, roleId);
role.setStatus(request.status());
repository.updateRole(role);
revokeAssignedSessions(tenantId, roleId);
return response(role, repository.findRolePermissionIds(tenantId, roleId));
}
@@ -224,6 +234,22 @@ public class RbacService {
}
}
repository.replaceRolePermissions(tenantId, request.roleId(), request.permissionIds());
revokeAssignedSessions(tenantId, request.roleId());
}
private void revokeAssignedSessions(long tenantId, Long roleId) {
List<Long> accountIds = repository.findAssignedAuthUserIds(tenantId, roleId);
for (int start = 0; start < accountIds.size(); start += SESSION_REVOCATION_BATCH_SIZE) {
Set<Long> batch = Set.copyOf(accountIds.subList(start,
Math.min(start + SESSION_REVOCATION_BATCH_SIZE, accountIds.size())));
try {
ApiResponse<Void> response = authClient.revokeSessions(internalToken,
new AuthAccountClient.RevokeAccountSessionsRequest(batch));
if (response == null || !response.successful()) throw sessionRevocationUnavailable();
} catch (FeignException exception) {
throw sessionRevocationUnavailable();
}
}
}
private IamRole requireRole(long tenantId, String appCode, Long roleId) {
@@ -284,4 +310,9 @@ public class RbacService {
private BizException forbidden() {
return new BizException(ResultCode.FORBIDDEN, "禁止跨租户或跨应用访问");
}
private BizException sessionRevocationUnavailable() {
return new BizException(ResultCode.SERVICE_UNAVAILABLE,
"认证会话撤销失败,角色变更未生效");
}
}
@@ -93,4 +93,21 @@ class AuthorizationSnapshotServiceTest {
assertThat(result).isEmpty();
}
@Test
void newLoginPermissionsDisappearWhileRoleDisabledAndReturnAfterReenable() {
when(mapper.countActiveIdentity(42L, 7L, 9L)).thenReturn(1);
when(mapper.findRoles(42L, 7L, 9L, "MERCHANT"))
.thenReturn(List.of("ORDER_VIEWER"), List.of(), List.of("ORDER_VIEWER"));
when(mapper.findPermissions(42L, 7L, 9L, "MERCHANT"))
.thenReturn(List.of("merchant:order:list", "merchant:order:view"), List.of(),
List.of("merchant:order:list", "merchant:order:view"));
SnapshotRequest request = new SnapshotRequest(42L, 7L, 9L, "MERCHANT");
assertThat(service.load(request).getPermissions())
.containsExactlyInAnyOrder("merchant:order:list", "merchant:order:view");
assertThat(service.load(request).getPermissions()).isEmpty();
assertThat(service.load(request).getPermissions())
.containsExactlyInAnyOrder("merchant:order:list", "merchant:order:view");
}
}
@@ -1,7 +1,9 @@
package com.baiyue.iam.service;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.iam.client.AuthAccountClient;
import com.baiyue.iam.dto.RbacDtos.AssignRolesRequest;
import com.baiyue.iam.dto.RbacDtos.GrantPermissionsRequest;
import com.baiyue.iam.dto.RbacDtos.CreateRoleRequest;
@@ -19,6 +21,7 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.TestPropertySource;
import jakarta.annotation.Resource;
@@ -30,9 +33,11 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@SpringJUnitConfig(RbacService.class)
@TestPropertySource(properties = "baiyue.security.internal-service-token=test-internal-token")
class RbacServiceTest {
@MockBean private RbacRepository repository;
@MockBean private MemberRepository memberRepository;
@MockBean private AuthAccountClient authClient;
@Resource private RbacService service;
@Test
@@ -311,6 +316,53 @@ class RbacServiceTest {
verify(repository).deletePermission(custom);
}
@Test
void roleDisableAndEnableRevokeAllAssignedAccountSessions() {
IamRole role = role(11L, 100L, "MERCHANT", "CUSTOM");
when(repository.findRole(11L, 100L, "MERCHANT")).thenReturn(role);
when(repository.findRolePermissionIds(100L, 11L)).thenReturn(Set.of(21L));
when(repository.findAssignedAuthUserIds(100L, 11L)).thenReturn(List.of(501L, 502L));
when(authClient.revokeSessions(any(), any())).thenReturn(ApiResponse.success(null));
service.changeStatus(100L, "MERCHANT", 11L, new ChangeRoleStatusRequest("DISABLED"));
service.changeStatus(100L, "MERCHANT", 11L, new ChangeRoleStatusRequest("ACTIVE"));
ArgumentCaptor<AuthAccountClient.RevokeAccountSessionsRequest> captor =
ArgumentCaptor.forClass(AuthAccountClient.RevokeAccountSessionsRequest.class);
verify(authClient, times(2)).revokeSessions(eq("test-internal-token"), captor.capture());
assertEquals(Set.of(501L, 502L), captor.getAllValues().get(0).accountIds());
assertEquals(Set.of(501L, 502L), captor.getAllValues().get(1).accountIds());
}
@Test
void rolePermissionReplacementRevokesAllAssignedAccountSessions() {
when(repository.findRole(11L, 100L, "MERCHANT"))
.thenReturn(role(11L, 100L, "MERCHANT", "CUSTOM"));
when(repository.findAssignedAuthUserIds(100L, 11L)).thenReturn(List.of(501L));
when(authClient.revokeSessions(any(), any())).thenReturn(ApiResponse.success(null));
service.grantPermissions(100L, "MERCHANT", new GrantPermissionsRequest(11L, Set.of()));
verify(repository).replaceRolePermissions(100L, 11L, Set.of());
verify(authClient).revokeSessions(eq("test-internal-token"),
eq(new AuthAccountClient.RevokeAccountSessionsRequest(Set.of(501L))));
}
@Test
void authFailureRejectsRoleStatusChangeSoIamTransactionCanRollBack() {
IamRole role = role(11L, 100L, "MERCHANT", "CUSTOM");
when(repository.findRole(11L, 100L, "MERCHANT")).thenReturn(role);
when(repository.findAssignedAuthUserIds(100L, 11L)).thenReturn(List.of(501L));
when(authClient.revokeSessions(any(), any())).thenReturn(ApiResponse.failure(
ResultCode.SERVICE_UNAVAILABLE, "auth unavailable", null, null));
BizException exception = assertThrows(BizException.class, () -> service.changeStatus(
100L, "MERCHANT", 11L, new ChangeRoleStatusRequest("DISABLED")));
assertEquals(ResultCode.SERVICE_UNAVAILABLE, exception.getResultCode());
assertEquals("认证会话撤销失败,角色变更未生效", exception.getMessage());
}
private IamRole role(Long id, Long tenantId, String appCode, String roleType) {
IamRole role = new IamRole();
role.setId(id);
@@ -3,6 +3,7 @@ package com.baiyue.iam.service;
import com.baiyue.iam.client.AuthAccountClient;
import com.baiyue.iam.entity.IamTenant;
import com.baiyue.iam.repository.ProfileResourceRepository;
import com.baiyue.iam.repository.MemberRepository;
import com.baiyue.iam.repository.TenantRepository;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
@@ -21,6 +22,7 @@ import static org.mockito.Mockito.when;
class TenantCustomerExportTest {
@MockBean private TenantRepository repository;
@MockBean private ProfileResourceRepository profileRepository;
@MockBean private MemberRepository memberRepository;
@MockBean private AuthAccountClient authAccountClient;
@MockBean private TenantApprovalInitializationCoordinator approvalInitialization;
@MockBean private CustomerDimensionRuleInitializationService dimensionRuleInitialization;
@@ -1,6 +1,7 @@
package com.baiyue.order.service;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.order.dto.OrderDetailResponse;
import com.baiyue.order.dto.OrderRequests;
@@ -87,7 +88,7 @@ public class OrderApplicationService {
OrderStatus before = order.getStatus();
if (before != OrderStatus.DRAFT && before != OrderStatus.REJECTED
&& !(allowSubmitted && before == OrderStatus.SUBMITTED)) {
throw new BizException("当前订单状态不允许编辑订单资料");
throw new BizException(ResultCode.CONFLICT, "当前订单状态不允许编辑订单资料");
}
pricingChannelGuard.requireAvailable(tenantId, request.receivingWarehouseId(), request.channelCode());
apply(order, request, snapshots); order.setStatus(OrderStatus.DRAFT); order.setUpdatedBy(userId);
@@ -181,20 +182,22 @@ public class OrderApplicationService {
private void validatePrototypeFields(OrderRequests.SaveRequest request) {
if ("PICKUP".equals(request.pickupMethod()) && request.sender() == null) {
throw new BizException("上门提货时必须填写发货信息");
throw new BizException(ResultCode.VALIDATION_FAILED, "上门提货时必须填写发货信息");
}
if ("PRIVATE".equals(request.destinationType()) && request.receiver() == null) {
throw new BizException("私人地址必须填写收货信息");
throw new BizException(ResultCode.VALIDATION_FAILED, "私人地址必须填写收货信息");
}
if ("FBA".equals(request.destinationType())) {
if (!StringUtils.hasText(request.amazonWarehouseCode()) || !StringUtils.hasText(request.shipmentId())
|| !StringUtils.hasText(request.referenceId())) {
throw new BizException("FBA orders require Amazon warehouse code, Shipment ID and Reference ID");
throw new BizException(ResultCode.VALIDATION_FAILED,
"FBA orders require Amazon warehouse code, Shipment ID and Reference ID");
}
for (var cargo : request.cargoes()) {
if (!StringUtils.hasText(cargo.skuNo()) || !StringUtils.hasText(cargo.upcNo())
|| !StringUtils.hasText(cargo.shipmentId()) || !StringUtils.hasText(cargo.referenceId())) {
throw new BizException("FBA cargo requires SKU NO., UPC NO., Shipment ID and Reference ID.");
throw new BizException(ResultCode.VALIDATION_FAILED,
"FBA cargo requires SKU NO., UPC NO., Shipment ID and Reference ID.");
}
}
}
@@ -216,21 +219,23 @@ public class OrderApplicationService {
if (cargoes == null || cargoes.isEmpty()) throw new BizException("At least one cargo line is required");
var parties = repository.findParties(tenantId, order.getId());
if ("PICKUP".equals(order.getPickupMethod()) && !hasParty(parties, "SENDER")) {
throw new BizException("上门提货时必须填写发货信息");
throw new BizException(ResultCode.VALIDATION_FAILED, "上门提货时必须填写发货信息");
}
if ("PRIVATE".equals(order.getDestinationType()) && !hasParty(parties, "RECEIVER")) {
throw new BizException("私人地址必须填写收货信息");
throw new BizException(ResultCode.VALIDATION_FAILED, "私人地址必须填写收货信息");
}
if ("FBA".equals(order.getDestinationType())) {
if (!StringUtils.hasText(order.getAmazonWarehouseCode()) || !StringUtils.hasText(order.getShipmentId())
|| !StringUtils.hasText(order.getReferenceId())) {
throw new BizException("FBA orders require Amazon warehouse code, Shipment ID and Reference ID");
throw new BizException(ResultCode.VALIDATION_FAILED,
"FBA orders require Amazon warehouse code, Shipment ID and Reference ID");
}
for (var cargo : cargoes) {
if (!StringUtils.hasText(cargo.getSkuNo()) || !StringUtils.hasText(cargo.getUpcNo())
|| !StringUtils.hasText(cargo.getShipmentId())
|| !StringUtils.hasText(cargo.getReferenceId())) {
throw new BizException("FBA cargo requires SKU NO., UPC NO., Shipment ID and Reference ID.");
throw new BizException(ResultCode.VALIDATION_FAILED,
"FBA cargo requires SKU NO., UPC NO., Shipment ID and Reference ID.");
}
}
}
@@ -310,7 +310,9 @@ class OrderApplicationServiceStateTest {
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class), masterData)
.updateDraft(10001L, 2L, 1L, request()))
.isInstanceOf(BizException.class)
.hasMessageContaining("不允许编辑");
.hasMessage("当前订单状态不允许编辑订单资料")
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.CONFLICT);
verify(repository, never()).saveOrder(anyLong(), any());
}
@@ -379,7 +381,9 @@ class OrderApplicationServiceStateTest {
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class), masterData)
.createDraft(20002L, 2L, request, "pickup-without-sender"))
.isInstanceOf(BizException.class)
.hasMessageContaining("上门提货时必须填写发货信息");
.hasMessage("上门提货时必须填写发货信息")
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.VALIDATION_FAILED);
verifyNoInteractions(masterData);
}
@@ -393,7 +397,41 @@ class OrderApplicationServiceStateTest {
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class), masterData)
.createDraft(20002L, 2L, request, "private-without-receiver"))
.isInstanceOf(BizException.class)
.hasMessageContaining("私人地址必须填写收货信息");
.hasMessage("私人地址必须填写收货信息")
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.VALIDATION_FAILED);
verifyNoInteractions(masterData);
}
@Test
void fbaOrderStillRequiresAmazonWarehouseShipmentAndReferenceIds() {
OrderRepository repository = mock(OrderRepository.class);
OrderMasterDataGuard masterData = mock(OrderMasterDataGuard.class);
OrderRequests.SaveRequest request = fbaRequest(null, null, null);
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class), masterData)
.createDraft(20002L, 2L, request, "fba-without-order-identifiers"))
.isInstanceOf(BizException.class)
.hasMessage("FBA orders require Amazon warehouse code, Shipment ID and Reference ID")
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.VALIDATION_FAILED);
verifyNoInteractions(masterData);
}
@Test
void fbaCargoStillRequiresSkuUpcShipmentAndReferenceIds() {
OrderRepository repository = mock(OrderRepository.class);
OrderMasterDataGuard masterData = mock(OrderMasterDataGuard.class);
OrderRequests.SaveRequest request = fbaRequest("ONT8", "FBA-SHIPMENT-1", "REF-1");
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class), masterData)
.createDraft(20002L, 2L, request, "fba-without-cargo-identifiers"))
.isInstanceOf(BizException.class)
.hasMessage("FBA cargo requires SKU NO., UPC NO., Shipment ID and Reference ID.")
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.VALIDATION_FAILED);
verifyNoInteractions(masterData);
}
@@ -410,7 +448,9 @@ class OrderApplicationServiceStateTest {
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class), mock(OrderMasterDataGuard.class))
.transit(20002L, 2L, "u", 1L, OrderStatus.SUBMITTED, null))
.isInstanceOf(BizException.class)
.hasMessageContaining("上门提货时必须填写发货信息");
.hasMessage("上门提货时必须填写发货信息")
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.VALIDATION_FAILED);
verify(repository, never()).saveOrder(anyLong(), any());
}
@@ -469,6 +509,19 @@ class OrderApplicationServiceStateTest {
"BUY_DECLARATION", "TAX_INCLUDED", "LCL", false, destinationType, null, null, null);
}
private OrderRequests.SaveRequest fbaRequest(String amazonWarehouseCode, String shipmentId,
String referenceId) {
OrderRequests.SaveRequest source = request(null, "SELF_EXPRESS", null, null, "FBA");
return new OrderRequests.SaveRequest(
source.customerId(), source.customerName(), source.transportMode(), source.tradeTerm(),
source.destinationCountryCode(), source.destinationWarehouseId(), source.destinationWarehouseName(),
source.expectedShipDate(), source.customerRemark(), source.sender(), source.receiver(), source.cargoes(),
source.pickupMethod(), source.receivingWarehouseId(), source.receivingWarehouseName(),
source.channelCode(), source.channelName(), source.carrierId(), source.carrierName(), source.taxMode(),
source.pricingTaxMode(), source.containerMode(), source.customsInspection(), source.destinationType(),
amazonWarehouseCode, shipmentId, referenceId);
}
private OrderMasterDataGuard.Snapshots snapshots() {
return snapshots(new PartnerClient.Partner(7L, 0L, "客户", "ACTIVE", "SHIPPER", List.of("SHIPPER")));
}
@@ -0,0 +1,714 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808013719156",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:37:20.808Z",
"startedAt": "2026-08-08T01:37:19.156Z",
"success": false,
"fatal": null,
"summary": {
"total": 36,
"passed": 28,
"failed": 8
},
"modules": [
{
"name": "customers",
"startedAt": "2026-08-08T01:37:19.675Z",
"finishedAt": "2026-08-08T01:37:20.808Z",
"success": false,
"passed": 27,
"failed": 8,
"fatal": null
}
],
"scenarios": [
{
"name": "customer-dimension-rules",
"module": "customers",
"success": false,
"deploymentBlocked": true,
"passed": 10,
"failed": 1
}
],
"deploymentBlockers": [
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "规则行 ID、tenantId 为字符串且初始版本为0",
"evidence": "groupTenantIdType=string, rows=string/undefined/v0,string/undefined/v0,string/undefined/v0,string/undefined/v0",
"code": "200",
"requestId": "21161286-1dac-4fd6-838e-5ff5a3366392, 243dcd3d-3ea2-4bfa-b7bd-976f13eb3fbf",
"traceId": "e7a484b82ca844ac933c14482228c3ce"
}
],
"createdResources": {
"customer": {
"tenantId": "2085903147058450433",
"marker": "ADMIN-API-20260808013719156",
"contactPhone": "16153039675"
}
},
"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": "b32bc7f2-2d2e-4db2-b3d2-a487433106de, 06ecaa92-97c9-4ad7-afa4-94e79cc325d8",
"traceId": "b9c74369c3aa4120836782c7bd47c9cf",
"evidence": "",
"checkedAt": "2026-08-08T01:37:19.674Z"
},
{
"module": "customers",
"scenario": null,
"name": "检查测试联系电话可用性",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/contact-phone-availability?contactPhone=16153039675"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "279a04a4-642b-47d3-9e86-530c3e473aeb, 08930ade-8e5f-4ec4-bc07-944c224c86a2",
"traceId": "4cd28b1dcdbe4f979157a834d7d4331b",
"evidence": "attempt=1",
"checkedAt": "2026-08-08T01:37:19.790Z"
},
{
"module": "customers",
"scenario": null,
"name": "创建客户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5fafeaeb-3e78-4bd4-9287-1ec9cb01ffe1, 9d539168-3722-4b4b-b80f-3bbdb77239e9",
"traceId": "319ec1d18c124a1eb1b4dc0e4d83e4c3",
"evidence": "",
"checkedAt": "2026-08-08T01:37:19.874Z"
},
{
"module": "customers",
"scenario": null,
"name": "创建响应客户字段完整",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5fafeaeb-3e78-4bd4-9287-1ec9cb01ffe1, 9d539168-3722-4b4b-b80f-3bbdb77239e9",
"traceId": "319ec1d18c124a1eb1b4dc0e4d83e4c3",
"evidence": "missing=unifiedSocialCreditCode,approvedAt",
"checkedAt": "2026-08-08T01:37:19.874Z"
},
{
"module": "customers",
"scenario": null,
"name": "新客户基础字段正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5fafeaeb-3e78-4bd4-9287-1ec9cb01ffe1, 9d539168-3722-4b4b-b80f-3bbdb77239e9",
"traceId": "319ec1d18c124a1eb1b4dc0e4d83e4c3",
"evidence": "tenantId=2085903147058450433, status=PROFILE_REQUIRED",
"checkedAt": "2026-08-08T01:37:19.874Z"
},
{
"module": "customers",
"scenario": null,
"name": "创建客户生成初始密码",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5fafeaeb-3e78-4bd4-9287-1ec9cb01ffe1, 9d539168-3722-4b4b-b80f-3bbdb77239e9",
"traceId": "319ec1d18c124a1eb1b4dc0e4d83e4c3",
"evidence": "generated=false, length=0",
"checkedAt": "2026-08-08T01:37:19.874Z"
},
{
"module": "customers",
"scenario": null,
"name": "管理员用户名默认使用联系电话",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5fafeaeb-3e78-4bd4-9287-1ec9cb01ffe1, 9d539168-3722-4b4b-b80f-3bbdb77239e9",
"traceId": "319ec1d18c124a1eb1b4dc0e4d83e4c3",
"evidence": "matches=false",
"checkedAt": "2026-08-08T01:37:19.874Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "新客户尺寸规则可查询",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "21161286-1dac-4fd6-838e-5ff5a3366392, 243dcd3d-3ea2-4bfa-b7bd-976f13eb3fbf",
"traceId": "e7a484b82ca844ac933c14482228c3ce",
"evidence": "tenantId=2085903147058450433",
"checkedAt": "2026-08-08T01:37:19.920Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "固定四箱规顺序完整且无重复",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "21161286-1dac-4fd6-838e-5ff5a3366392, 243dcd3d-3ea2-4bfa-b7bd-976f13eb3fbf",
"traceId": "e7a484b82ca844ac933c14482228c3ce",
"evidence": "count=4, order=SMALL,MEDIUM,LARGE,EXTRA_LARGE",
"checkedAt": "2026-08-08T01:37:19.920Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "四箱规默认内径、加边和备注正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "21161286-1dac-4fd6-838e-5ff5a3366392, 243dcd3d-3ea2-4bfa-b7bd-976f13eb3fbf",
"traceId": "e7a484b82ca844ac933c14482228c3ce",
"evidence": "defaults=matched",
"checkedAt": "2026-08-08T01:37:19.920Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "规则行 ID、tenantId 为字符串且初始版本为0",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "21161286-1dac-4fd6-838e-5ff5a3366392, 243dcd3d-3ea2-4bfa-b7bd-976f13eb3fbf",
"traceId": "e7a484b82ca844ac933c14482228c3ce",
"evidence": "groupTenantIdType=string, rows=string/undefined/v0,string/undefined/v0,string/undefined/v0,string/undefined/v0",
"checkedAt": "2026-08-08T01:37:19.920Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "默认规则外径与 HALF_UP 三位 CBM 正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "21161286-1dac-4fd6-838e-5ff5a3366392, 243dcd3d-3ea2-4bfa-b7bd-976f13eb3fbf",
"traceId": "e7a484b82ca844ac933c14482228c3ce",
"evidence": "derivedRows=4",
"checkedAt": "2026-08-08T01:37:19.920Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "PUT 每行只发送约定7字段并整组保存",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "21161286-1dac-4fd6-838e-5ff5a3366392, 243dcd3d-3ea2-4bfa-b7bd-976f13eb3fbf",
"traceId": "e7a484b82ca844ac933c14482228c3ce",
"evidence": "fields=boxType,innerLengthCm,innerWidthCm,innerHeightCm,edgeAdditionCm,remark,version",
"checkedAt": "2026-08-08T01:37:19.920Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "一次 PUT 保存四条三位小数规则",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2da22620-8e23-4cb5-830d-09b0c7abebd8, e01048dc-e5b7-41dc-9537-ff47e713dcb6",
"traceId": "5d1ecdf1289a4d55a90368d1a397ec8e",
"evidence": "rows=4",
"checkedAt": "2026-08-08T01:37:19.993Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "修改后四行版本+1且派生外径/CBM正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2da22620-8e23-4cb5-830d-09b0c7abebd8, e01048dc-e5b7-41dc-9537-ff47e713dcb6",
"traceId": "5d1ecdf1289a4d55a90368d1a397ec8e",
"evidence": "versions=1,1,1,1",
"checkedAt": "2026-08-08T01:37:19.993Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "旧 version 重放 PUT 返回冲突",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085903147058450433"
},
"http": 200,
"code": "A0409",
"message": "箱规尺寸配置已被修改,请刷新后重试",
"requestId": "9dc714d8-e24e-4466-a2a1-0916180b4b2e",
"traceId": "907d64457fb2470f8c536b394efe67d6",
"evidence": "HTTP=200, code=A0409",
"checkedAt": "2026-08-08T01:37:20.072Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "冲突后重新查询四条规则",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "70ee21a4-f9bb-4b47-b102-9161cad9e95c, c54ecf0e-a981-432f-9cfe-84dd2e92eca5",
"traceId": "c593da8cca8840d398a1fec59e792ae9",
"evidence": "",
"checkedAt": "2026-08-08T01:37:20.125Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "冲突请求未造成四行部分更新",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "70ee21a4-f9bb-4b47-b102-9161cad9e95c, c54ecf0e-a981-432f-9cfe-84dd2e92eca5",
"traceId": "c593da8cca8840d398a1fec59e792ae9",
"evidence": "unchangedRows=4",
"checkedAt": "2026-08-08T01:37:20.125Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants?page=1&size=20&keyword=%E5%90%8E%E5%8F%B0%E5%AE%A2%E6%88%B7%E9%AA%8C%E6%94%B6-808013719156"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "197c5762-b2f4-4d72-a576-5ab1b7db124e, abfe6a1b-b6f8-4723-858a-cfe509448f33",
"traceId": "a91f3982e2c3472ea094033eacb08832",
"evidence": "",
"checkedAt": "2026-08-08T01:37:20.168Z"
},
{
"module": "customers",
"scenario": null,
"name": "列表包含新客户且字段完整",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants?page=1&size=20&keyword=%E5%90%8E%E5%8F%B0%E5%AE%A2%E6%88%B7%E9%AA%8C%E6%94%B6-808013719156"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "197c5762-b2f4-4d72-a576-5ab1b7db124e, abfe6a1b-b6f8-4723-858a-cfe509448f33",
"traceId": "a91f3982e2c3472ea094033eacb08832",
"evidence": "found=true, missing=unifiedSocialCreditCode,approvedAt",
"checkedAt": "2026-08-08T01:37:20.168Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6fc40aa0-8e0e-448f-9585-ea1e906d416d, c7649135-2a3e-4400-bb90-72075c23e23c",
"traceId": "4d208b9fa2ea4268834af84d42aac20d",
"evidence": "",
"checkedAt": "2026-08-08T01:37:20.240Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户详情基本信息正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6fc40aa0-8e0e-448f-9585-ea1e906d416d, c7649135-2a3e-4400-bb90-72075c23e23c",
"traceId": "4d208b9fa2ea4268834af84d42aac20d",
"evidence": "tenantId=2085903147058450433",
"checkedAt": "2026-08-08T01:37:20.240Z"
},
{
"module": "customers",
"scenario": null,
"name": "编辑客户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2eabab50-7229-4ae0-8792-eca6e6d35bc5, 5dc4a056-69d1-458b-aa73-b62a1768f508",
"traceId": "f151d4a7d60b4393848113c0a2e47c8a",
"evidence": "",
"checkedAt": "2026-08-08T01:37:20.340Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2eabab50-7229-4ae0-8792-eca6e6d35bc5, 5dc4a056-69d1-458b-aa73-b62a1768f508",
"traceId": "f151d4a7d60b4393848113c0a2e47c8a",
"evidence": "tenantId=2085903147058450433",
"checkedAt": "2026-08-08T01:37:20.340Z"
},
{
"module": "customers",
"scenario": null,
"name": "回查客户编辑结果",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c99e7d20-30f3-453e-adf7-81617c0202be, 5e4b928d-dc03-40a9-b7ac-b57928563861",
"traceId": "8fc0982f732a4cbeb01f75b1ce2fa200",
"evidence": "",
"checkedAt": "2026-08-08T01:37:20.501Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户编辑已持久化",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c99e7d20-30f3-453e-adf7-81617c0202be, 5e4b928d-dc03-40a9-b7ac-b57928563861",
"traceId": "8fc0982f732a4cbeb01f75b1ce2fa200",
"evidence": "tenantId=2085903147058450433",
"checkedAt": "2026-08-08T01:37:20.501Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询资质",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085903147058450433/qualifications"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d87dbe3e-de44-441d-816c-e1ee1a371b29, bee8d393-a7f5-4341-94ce-eaf673f193ce",
"traceId": "756ebff2cc9f4f1eac1e4f9d8a633a76",
"evidence": "",
"checkedAt": "2026-08-08T01:37:20.554Z"
},
{
"module": "customers",
"scenario": null,
"name": "资质概况结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085903147058450433/qualifications"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d87dbe3e-de44-441d-816c-e1ee1a371b29, bee8d393-a7f5-4341-94ce-eaf673f193ce",
"traceId": "756ebff2cc9f4f1eac1e4f9d8a633a76",
"evidence": "count=0",
"checkedAt": "2026-08-08T01:37:20.554Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询成员",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085903147058450433/members"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2439e6c5-6ae9-4fb7-819d-93cca240bae6, 43d3143c-ff79-4de0-9cf7-575d214c8a07",
"traceId": "be4bfdad721641ca8fd24db9ebfc85cb",
"evidence": "",
"checkedAt": "2026-08-08T01:37:20.643Z"
},
{
"module": "customers",
"scenario": null,
"name": "联系电话账号是企业管理员",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085903147058450433/members"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2439e6c5-6ae9-4fb7-819d-93cca240bae6, 43d3143c-ff79-4de0-9cf7-575d214c8a07",
"traceId": "be4bfdad721641ca8fd24db9ebfc85cb",
"evidence": "ownerFound=false, matches=false",
"checkedAt": "2026-08-08T01:37:20.643Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询运单数量",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/tenant-counts?tenantIds=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "4ba32e71-b9ea-412d-8d26-bade4ce94874, 5e539bbf-cddb-43bf-9d65-f89060e2c12d",
"traceId": "4906a891015d469a976af95d08f3be10",
"evidence": "",
"checkedAt": "2026-08-08T01:37:20.705Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况返回当前客户运单数",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/tenant-counts?tenantIds=2085903147058450433"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "4ba32e71-b9ea-412d-8d26-bade4ce94874, 5e539bbf-cddb-43bf-9d65-f89060e2c12d",
"traceId": "4906a891015d469a976af95d08f3be10",
"evidence": "orderCount=0",
"checkedAt": "2026-08-08T01:37:20.705Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户运单记录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=2085903147058450433&page=1&size=10"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6cf876bf-1fd5-4f72-a655-3ad61ec38758, efcc42ca-fd26-4346-a8c5-cb3b1378442b",
"traceId": "bb1beb549cd342d4a54afa9279507525",
"evidence": "",
"checkedAt": "2026-08-08T01:37:20.754Z"
},
{
"module": "customers",
"scenario": null,
"name": "运单记录分页结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=2085903147058450433&page=1&size=10"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6cf876bf-1fd5-4f72-a655-3ad61ec38758, efcc42ca-fd26-4346-a8c5-cb3b1378442b",
"traceId": "bb1beb549cd342d4a54afa9279507525",
"evidence": "records=0, total=0",
"checkedAt": "2026-08-08T01:37:20.754Z"
},
{
"module": "customers",
"scenario": null,
"name": "停用客户",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085903147058450433/status"
},
"http": 200,
"code": "A0409",
"message": "Tenant status must be changed through review / 当前租户状态必须通过审核流程变更",
"requestId": "9bc5cbb7-0a5b-4dfc-b560-7c07af10fd7c",
"traceId": "592ca8eb0d354c82b041b8fcdcca6f39",
"evidence": "",
"checkedAt": "2026-08-08T01:37:20.808Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户停用状态正确",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085903147058450433/status"
},
"http": 200,
"code": "A0409",
"message": "Tenant status must be changed through review / 当前租户状态必须通过审核流程变更",
"requestId": "9bc5cbb7-0a5b-4dfc-b560-7c07af10fd7c",
"traceId": "592ca8eb0d354c82b041b8fcdcca6f39",
"evidence": "status=missing",
"checkedAt": "2026-08-08T01:37:20.808Z"
}
]
}
@@ -0,0 +1,811 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808014241953",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:42:44.907Z",
"startedAt": "2026-08-08T01:42:41.953Z",
"success": false,
"fatal": null,
"summary": {
"total": 42,
"passed": 38,
"failed": 4
},
"modules": [
{
"name": "system",
"startedAt": "2026-08-08T01:42:42.321Z",
"finishedAt": "2026-08-08T01:42:44.907Z",
"success": false,
"passed": 37,
"failed": 4,
"fatal": null
}
],
"scenarios": [],
"deploymentBlockers": [],
"createdResources": {
"systemPermission": {
"id": "2085904500002508801",
"code": "platform:acceptance:808014241953"
},
"systemRole": {
"id": "2085904500719734785",
"code": "ADMIN_ACCEPT_808014241953"
},
"systemUser": {
"membershipId": "2085904502246461444",
"username": "admin_accept_808014241953",
"finalStatus": "DISABLED"
}
},
"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": "4c49a105-97ea-4dea-8fee-0736c80c118a, 72186a54-d723-44bb-9d17-fc19f6ea8a02",
"traceId": "67f637fdf2d441748c50d3de0b8cb354",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.320Z"
},
{
"module": "system",
"scenario": null,
"name": "查询菜单权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e36f26a6-bc74-4948-9af7-bf3c492a40e0, a591dd23-109a-4a02-9279-19b6d0227c59",
"traceId": "8377b21d52d44447911e167eae2ac593",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.376Z"
},
{
"module": "system",
"scenario": null,
"name": "权限资源返回数组",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e36f26a6-bc74-4948-9af7-bf3c492a40e0, a591dd23-109a-4a02-9279-19b6d0227c59",
"traceId": "8377b21d52d44447911e167eae2ac593",
"evidence": "count=117",
"checkedAt": "2026-08-08T01:42:42.376Z"
},
{
"module": "system",
"scenario": null,
"name": "创建自定义权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f48c31c1-3964-4472-bf35-c95b0dd825fe, 7d789d67-c137-4ff3-9320-f2c401480661",
"traceId": "5de16dae865440ddaae00d5fe095c028",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.422Z"
},
{
"module": "system",
"scenario": null,
"name": "权限创建字段正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f48c31c1-3964-4472-bf35-c95b0dd825fe, 7d789d67-c137-4ff3-9320-f2c401480661",
"traceId": "5de16dae865440ddaae00d5fe095c028",
"evidence": "permissionId=2085904500002508801",
"checkedAt": "2026-08-08T01:42:42.422Z"
},
{
"module": "system",
"scenario": null,
"name": "编辑自定义权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085904500002508801"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "11a26c6b-6bee-41ec-8cd7-3d9fac7dca0f, 0c63ba1f-5132-4b4b-a16a-a7fa213872d0",
"traceId": "585636101cf84d279a9970c7f1dbd719",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.469Z"
},
{
"module": "system",
"scenario": null,
"name": "权限编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085904500002508801"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "11a26c6b-6bee-41ec-8cd7-3d9fac7dca0f, 0c63ba1f-5132-4b4b-a16a-a7fa213872d0",
"traceId": "585636101cf84d279a9970c7f1dbd719",
"evidence": "permissionId=2085904500002508801",
"checkedAt": "2026-08-08T01:42:42.470Z"
},
{
"module": "system",
"scenario": null,
"name": "停用自定义权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085904500002508801/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f62d27df-0be8-41b8-88c3-73cecd3181ee, ea26caa1-a419-446c-b69a-379e623a57d0",
"traceId": "142ba41e18454d84893bf68e1d283d4d",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.512Z"
},
{
"module": "system",
"scenario": null,
"name": "权限状态回显 DISABLED",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085904500002508801/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f62d27df-0be8-41b8-88c3-73cecd3181ee, ea26caa1-a419-446c-b69a-379e623a57d0",
"traceId": "142ba41e18454d84893bf68e1d283d4d",
"evidence": "status=DISABLED",
"checkedAt": "2026-08-08T01:42:42.512Z"
},
{
"module": "system",
"scenario": null,
"name": "启用自定义权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085904500002508801/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "313b41b0-85e4-4c89-9cd1-00170e341923, 3db0b9af-4cb3-4150-825f-3cfafd49afb9",
"traceId": "37f04009ef6c4689b3c9079d58017cf1",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.557Z"
},
{
"module": "system",
"scenario": null,
"name": "权限状态回显 ACTIVE",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085904500002508801/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "313b41b0-85e4-4c89-9cd1-00170e341923, 3db0b9af-4cb3-4150-825f-3cfafd49afb9",
"traceId": "37f04009ef6c4689b3c9079d58017cf1",
"evidence": "status=ACTIVE",
"checkedAt": "2026-08-08T01:42:42.557Z"
},
{
"module": "system",
"scenario": null,
"name": "创建平台自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "8511affb-21aa-4323-9f68-1685c6f236cb, 46d1d027-ce93-46f2-b0a1-6d7eb866d4b8",
"traceId": "a1a643e9f6754cde95fc3c2c41bd7ba8",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.595Z"
},
{
"module": "system",
"scenario": null,
"name": "角色创建字段正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "8511affb-21aa-4323-9f68-1685c6f236cb, 46d1d027-ce93-46f2-b0a1-6d7eb866d4b8",
"traceId": "a1a643e9f6754cde95fc3c2c41bd7ba8",
"evidence": "roleId=2085904500719734785",
"checkedAt": "2026-08-08T01:42:42.595Z"
},
{
"module": "system",
"scenario": null,
"name": "编辑平台自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085904500719734785"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "fce1fefc-38bb-4ccd-99a9-fc26ad81ec82, 7c15617e-2dab-48d7-b3ab-ecf90d56e5f2",
"traceId": "dbc1a05a1a7f44eea694561a34fe227d",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.644Z"
},
{
"module": "system",
"scenario": null,
"name": "角色编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085904500719734785"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "fce1fefc-38bb-4ccd-99a9-fc26ad81ec82, 7c15617e-2dab-48d7-b3ab-ecf90d56e5f2",
"traceId": "dbc1a05a1a7f44eea694561a34fe227d",
"evidence": "roleId=2085904500719734785",
"checkedAt": "2026-08-08T01:42:42.644Z"
},
{
"module": "system",
"scenario": null,
"name": "角色全量保存权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "fcb6411b-ba1c-4bf3-977d-ef826667c70a, f4205f34-944d-49a4-870b-f2e301a68f98",
"traceId": "64504abb7fd248a288d14251aec09d16",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.690Z"
},
{
"module": "system",
"scenario": null,
"name": "回查平台角色列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d06e1ae9-d9db-49a2-bbc0-09fcbea5d3d9, 62f3ee28-4b68-4de5-b702-be727085ab5a",
"traceId": "3dae813f3d9a4433b2244c2e142ac4f8",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.722Z"
},
{
"module": "system",
"scenario": null,
"name": "角色权限 ID 正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d06e1ae9-d9db-49a2-bbc0-09fcbea5d3d9, 62f3ee28-4b68-4de5-b702-be727085ab5a",
"traceId": "3dae813f3d9a4433b2244c2e142ac4f8",
"evidence": "permissionIds=2085904500002508801",
"checkedAt": "2026-08-08T01:42:42.723Z"
},
{
"module": "system",
"scenario": null,
"name": "停用自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085904500719734785/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ca6f833c-988a-4a5f-b6ee-22c83382d1f5, 1dd1994b-cf4e-4f29-b339-12472455c830",
"traceId": "ff4a67a10c31417c81c9104a0711a7f8",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.768Z"
},
{
"module": "system",
"scenario": null,
"name": "角色状态回显 DISABLED",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085904500719734785/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ca6f833c-988a-4a5f-b6ee-22c83382d1f5, 1dd1994b-cf4e-4f29-b339-12472455c830",
"traceId": "ff4a67a10c31417c81c9104a0711a7f8",
"evidence": "status=DISABLED",
"checkedAt": "2026-08-08T01:42:42.768Z"
},
{
"module": "system",
"scenario": null,
"name": "启用自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085904500719734785/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "cadd6dab-03cb-444a-ba06-fdf5b86b590f, 63df40e8-b0db-4a47-bd03-5c0c368d2cb5",
"traceId": "06d30283bd724e808f13cfad977a2774",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.809Z"
},
{
"module": "system",
"scenario": null,
"name": "角色状态回显 ACTIVE",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085904500719734785/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "cadd6dab-03cb-444a-ba06-fdf5b86b590f, 63df40e8-b0db-4a47-bd03-5c0c368d2cb5",
"traceId": "06d30283bd724e808f13cfad977a2774",
"evidence": "status=ACTIVE",
"checkedAt": "2026-08-08T01:42:42.809Z"
},
{
"module": "system",
"scenario": null,
"name": "查询内部用户可选平台角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users/options"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e33e566e-3092-46da-86cb-c474c8df6d0b, c29248bd-1584-4274-a8d6-14ed0968cf1c",
"traceId": "793d2b8ee3ed4a179ee1476be55ca06b",
"evidence": "",
"checkedAt": "2026-08-08T01:42:42.853Z"
},
{
"module": "system",
"scenario": null,
"name": "用户选项包含平台与测试角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users/options"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e33e566e-3092-46da-86cb-c474c8df6d0b, c29248bd-1584-4274-a8d6-14ed0968cf1c",
"traceId": "793d2b8ee3ed4a179ee1476be55ca06b",
"evidence": "tenants=1, roles=5",
"checkedAt": "2026-08-08T01:42:42.853Z"
},
{
"module": "system",
"scenario": null,
"name": "检查测试联系电话可用性",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/contact-phone-availability?contactPhone=16153362853"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "71fbc173-8c2f-42cd-a90f-66da41cd0f90, fdc437e8-6a73-4da1-8825-0da398a57849",
"traceId": "d8b5129ade20418fa8b33dfb7d70d994",
"evidence": "attempt=1",
"checkedAt": "2026-08-08T01:42:42.908Z"
},
{
"module": "system",
"scenario": null,
"name": "创建平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/users"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f62c3b70-358e-4c0d-9ab8-87818d12fcb7, ef8398dd-3382-4746-a392-b514e79bcf36",
"traceId": "07c0df68cf2c470f816cbf06d1220f29",
"evidence": "",
"checkedAt": "2026-08-08T01:42:43.300Z"
},
{
"module": "system",
"scenario": null,
"name": "内部用户账号、平台范围和角色正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/users"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f62c3b70-358e-4c0d-9ab8-87818d12fcb7, ef8398dd-3382-4746-a392-b514e79bcf36",
"traceId": "07c0df68cf2c470f816cbf06d1220f29",
"evidence": "membershipId=2085904502246461444",
"checkedAt": "2026-08-08T01:42:43.300Z"
},
{
"module": "system",
"scenario": null,
"name": "按账号查询平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users?page=1&size=20&keyword=admin_accept_808014241953"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "24d51f86-af43-4a2a-b953-f9bcb57f1639, dfe7e908-1437-48dd-ba63-ac82d87291ff",
"traceId": "553ba9d384834849aa038240a317c3e3",
"evidence": "",
"checkedAt": "2026-08-08T01:42:43.346Z"
},
{
"module": "system",
"scenario": null,
"name": "用户列表包含新账号且字段完整",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users?page=1&size=20&keyword=admin_accept_808014241953"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "24d51f86-af43-4a2a-b953-f9bcb57f1639, dfe7e908-1437-48dd-ba63-ac82d87291ff",
"traceId": "553ba9d384834849aa038240a317c3e3",
"evidence": "records=1",
"checkedAt": "2026-08-08T01:42:43.346Z"
},
{
"module": "system",
"scenario": null,
"name": "编辑平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085904502246461444"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "68357647-6e2c-477b-bca6-355c2ac8c336, 8abe4456-e213-449d-8f95-f5a7aa8cd269",
"traceId": "3db6a0906cbe4a84a6857fb4e7f36590",
"evidence": "",
"checkedAt": "2026-08-08T01:42:43.511Z"
},
{
"module": "system",
"scenario": null,
"name": "用户编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085904502246461444"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "68357647-6e2c-477b-bca6-355c2ac8c336, 8abe4456-e213-449d-8f95-f5a7aa8cd269",
"traceId": "3db6a0906cbe4a84a6857fb4e7f36590",
"evidence": "membershipId=2085904502246461444",
"checkedAt": "2026-08-08T01:42:43.511Z"
},
{
"module": "system",
"scenario": null,
"name": "新建内部用户可使用初始密码登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5511e82f-3d3e-4684-b19e-fd546995d376, 922bfcd5-e74f-4301-8109-341b9f0f4c70",
"traceId": "cbb48c8b80814e8ab54653d07691886d",
"evidence": "username=admin_accept_808014241953",
"checkedAt": "2026-08-08T01:42:43.854Z"
},
{
"module": "system",
"scenario": null,
"name": "重置平台内部用户密码",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085904502246461444/password"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6c82688d-5c61-42b0-898e-df93f46472c1, f4814fac-39a0-4274-9d7e-1b4622b3c253",
"traceId": "7a921093f9254c74bdee43dbb4d4fed4",
"evidence": "",
"checkedAt": "2026-08-08T01:42:44.270Z"
},
{
"module": "system",
"scenario": null,
"name": "内部用户使用重置密码登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f0ba5839-c929-46b1-b88d-3b9efd55631f, a1f453b3-e0e4-48e1-af8b-f39e8e329d33",
"traceId": "29e4cadeb808446bbea7ce36dc0e8c1d",
"evidence": "username=admin_accept_808014241953",
"checkedAt": "2026-08-08T01:42:44.608Z"
},
{
"module": "system",
"scenario": null,
"name": "清空测试用户角色",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085904502246461444"
},
"http": 200,
"code": "A0400",
"message": "请选择角色",
"requestId": "395f3729-59d8-4318-8635-2e982d65689b",
"traceId": "939f0b64578644f48ad600c6c71945ef",
"evidence": "",
"checkedAt": "2026-08-08T01:42:44.635Z"
},
{
"module": "system",
"scenario": null,
"name": "测试用户角色已清空",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085904502246461444"
},
"http": 200,
"code": "A0400",
"message": "请选择角色",
"requestId": "395f3729-59d8-4318-8635-2e982d65689b",
"traceId": "939f0b64578644f48ad600c6c71945ef",
"evidence": "roleCount=n/a",
"checkedAt": "2026-08-08T01:42:44.635Z"
},
{
"module": "system",
"scenario": null,
"name": "停用平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085904502246461444/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "40821960-2d23-48db-8e38-a8ccf2e86bfa, abe3d3aa-b6e5-4c0c-ac15-e504bf62755e",
"traceId": "8fd0f9b1fd6c43a188373514dd8608a1",
"evidence": "",
"checkedAt": "2026-08-08T01:42:44.763Z"
},
{
"module": "system",
"scenario": null,
"name": "内部用户停用状态正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085904502246461444/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "40821960-2d23-48db-8e38-a8ccf2e86bfa, abe3d3aa-b6e5-4c0c-ac15-e504bf62755e",
"traceId": "8fd0f9b1fd6c43a188373514dd8608a1",
"evidence": "status=DISABLED",
"checkedAt": "2026-08-08T01:42:44.763Z"
},
{
"module": "system",
"scenario": null,
"name": "停用用户禁止登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "A0403",
"message": "账号已禁用",
"requestId": "1f6bc9f9-775d-48fe-98da-ce14dda250bd",
"traceId": "2dcc3aa5601b45ed9156cc891a552355",
"evidence": "HTTP=200, code=A0403",
"checkedAt": "2026-08-08T01:42:44.784Z"
},
{
"module": "system",
"scenario": null,
"name": "清空测试角色权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "bbbeb1a1-67aa-460e-98b1-a1812794bca2, 10868182-906f-46ac-8748-ca18bfce29a8",
"traceId": "ebe468e6eb214e0d8e34d2e2ba59c883",
"evidence": "",
"checkedAt": "2026-08-08T01:42:44.829Z"
},
{
"module": "system",
"scenario": null,
"name": "删除临时测试角色",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "DELETE",
"path": "/api/platform/v1/iam/roles/2085904500719734785"
},
"http": 200,
"code": "A0409",
"message": "角色已分配给成员,不能删除",
"requestId": "b3b98661-969b-415c-a3f9-2a1f70157be7",
"traceId": "0e2cbc17e3a74107af588bda5a93af0e",
"evidence": "",
"checkedAt": "2026-08-08T01:42:44.870Z"
},
{
"module": "system",
"scenario": null,
"name": "删除临时权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "DELETE",
"path": "/api/platform/v1/iam/permissions/2085904500002508801"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "eef358f2-dcd2-40db-86ce-bf615ad78de7, b6858e45-82fc-4e16-94ac-2b25c056d16f",
"traceId": "921ba4d6c2cd46779d63851ae9b1617e",
"evidence": "",
"checkedAt": "2026-08-08T01:42:44.907Z"
}
]
}
@@ -0,0 +1,740 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808014935078",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:49:35.989Z",
"startedAt": "2026-08-08T01:49:35.078Z",
"success": false,
"fatal": null,
"summary": {
"total": 38,
"passed": 31,
"failed": 7
},
"modules": [
{
"name": "customers",
"startedAt": "2026-08-08T01:49:35.449Z",
"finishedAt": "2026-08-08T01:49:35.989Z",
"success": false,
"passed": 30,
"failed": 7,
"fatal": null
}
],
"scenarios": [
{
"name": "customer-dimension-rules",
"module": "customers",
"success": true,
"deploymentBlocked": false,
"passed": 13,
"failed": 0
}
],
"deploymentBlockers": [],
"createdResources": {
"customer": {
"tenantId": "2085906232656908290",
"marker": "ADMIN-API-20260808014935078",
"contactPhone": "16153775449"
}
},
"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": "a720a97c-bbdd-4de5-89de-b48a88173ee3, 5b4d1342-f103-468c-a99f-88cb1fba5f1b",
"traceId": "af52909ad7e145c9a94cbe28b895aa3b",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.449Z"
},
{
"module": "customers",
"scenario": null,
"name": "检查测试联系电话可用性",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/contact-phone-availability?contactPhone=16153775449"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "a1184143-913e-4721-8321-74a93b3a71ee, bbd21c85-e5ed-4011-9414-638be1043067",
"traceId": "2821f39b6a94496ebc93303e54f46dae",
"evidence": "attempt=1",
"checkedAt": "2026-08-08T01:49:35.491Z"
},
{
"module": "customers",
"scenario": null,
"name": "创建客户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9cda04a4-3646-4f69-9c01-aebd7118fd22, bab83655-d9b4-4420-aecf-aa01328d6a30",
"traceId": "b6aca188f51f4455a145a1782677f971",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.531Z"
},
{
"module": "customers",
"scenario": null,
"name": "创建响应客户字段完整",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9cda04a4-3646-4f69-9c01-aebd7118fd22, bab83655-d9b4-4420-aecf-aa01328d6a30",
"traceId": "b6aca188f51f4455a145a1782677f971",
"evidence": "missing=unifiedSocialCreditCode,approvedAt",
"checkedAt": "2026-08-08T01:49:35.531Z"
},
{
"module": "customers",
"scenario": null,
"name": "新客户基础字段正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9cda04a4-3646-4f69-9c01-aebd7118fd22, bab83655-d9b4-4420-aecf-aa01328d6a30",
"traceId": "b6aca188f51f4455a145a1782677f971",
"evidence": "tenantId=2085906232656908290, status=PROFILE_REQUIRED",
"checkedAt": "2026-08-08T01:49:35.531Z"
},
{
"module": "customers",
"scenario": null,
"name": "创建客户生成初始密码",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9cda04a4-3646-4f69-9c01-aebd7118fd22, bab83655-d9b4-4420-aecf-aa01328d6a30",
"traceId": "b6aca188f51f4455a145a1782677f971",
"evidence": "generated=false, length=0",
"checkedAt": "2026-08-08T01:49:35.531Z"
},
{
"module": "customers",
"scenario": null,
"name": "管理员用户名默认使用联系电话",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9cda04a4-3646-4f69-9c01-aebd7118fd22, bab83655-d9b4-4420-aecf-aa01328d6a30",
"traceId": "b6aca188f51f4455a145a1782677f971",
"evidence": "matches=false",
"checkedAt": "2026-08-08T01:49:35.531Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "新客户尺寸规则可查询",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3a9fab6f-d233-49ce-9671-ee18d65c6d17, 14909cd4-8d05-4406-8ef3-1bff5303a410",
"traceId": "837808a650b4498dabac6a48b9c8d91b",
"evidence": "tenantId=2085906232656908290",
"checkedAt": "2026-08-08T01:49:35.569Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "固定四箱规顺序完整且无重复",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3a9fab6f-d233-49ce-9671-ee18d65c6d17, 14909cd4-8d05-4406-8ef3-1bff5303a410",
"traceId": "837808a650b4498dabac6a48b9c8d91b",
"evidence": "count=4, order=SMALL,MEDIUM,LARGE,EXTRA_LARGE",
"checkedAt": "2026-08-08T01:49:35.569Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "四箱规默认内径、加边和备注正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3a9fab6f-d233-49ce-9671-ee18d65c6d17, 14909cd4-8d05-4406-8ef3-1bff5303a410",
"traceId": "837808a650b4498dabac6a48b9c8d91b",
"evidence": "defaults=matched",
"checkedAt": "2026-08-08T01:49:35.569Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "客户 ID、规则 ID 为字符串且初始版本为0",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3a9fab6f-d233-49ce-9671-ee18d65c6d17, 14909cd4-8d05-4406-8ef3-1bff5303a410",
"traceId": "837808a650b4498dabac6a48b9c8d91b",
"evidence": "groupTenantIdType=string, rows=string/v0,string/v0,string/v0,string/v0",
"checkedAt": "2026-08-08T01:49:35.569Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "默认规则外径与 HALF_UP 三位 CBM 正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3a9fab6f-d233-49ce-9671-ee18d65c6d17, 14909cd4-8d05-4406-8ef3-1bff5303a410",
"traceId": "837808a650b4498dabac6a48b9c8d91b",
"evidence": "derivedRows=4",
"checkedAt": "2026-08-08T01:49:35.569Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "PUT 每行只发送约定7字段并整组保存",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3a9fab6f-d233-49ce-9671-ee18d65c6d17, 14909cd4-8d05-4406-8ef3-1bff5303a410",
"traceId": "837808a650b4498dabac6a48b9c8d91b",
"evidence": "fields=boxType,innerLengthCm,innerWidthCm,innerHeightCm,edgeAdditionCm,remark,version",
"checkedAt": "2026-08-08T01:49:35.569Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "一次 PUT 保存四条三位小数规则",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "0a02678d-9b08-481c-83fd-e49aca8f95d1, f80c147e-57d2-48d2-87bd-41e7cfc20ec9",
"traceId": "d7d775ad69294342a0fa7ecab51f0371",
"evidence": "rows=4",
"checkedAt": "2026-08-08T01:49:35.610Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "修改后四行版本+1且派生外径/CBM正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "0a02678d-9b08-481c-83fd-e49aca8f95d1, f80c147e-57d2-48d2-87bd-41e7cfc20ec9",
"traceId": "d7d775ad69294342a0fa7ecab51f0371",
"evidence": "versions=1,1,1,1",
"checkedAt": "2026-08-08T01:49:35.610Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "旧 version 重放 PUT 返回冲突",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "A0409",
"message": "箱规尺寸配置已被修改,请刷新后重试",
"requestId": "66ea1f92-f817-495f-b853-7cd32cca4c6c",
"traceId": "c7eccaf37e5541f6bd7e862858e585e1",
"evidence": "HTTP=200, code=A0409",
"checkedAt": "2026-08-08T01:49:35.648Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "冲突后重新查询四条规则",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "333d1f5e-57a0-4ff8-a206-8f928bae7aa3, 4029e9ab-3e95-46e5-856e-687d3769e02b",
"traceId": "0d610b96e002491fa9877bf1e8a17531",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.674Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "冲突请求未造成四行部分更新",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "333d1f5e-57a0-4ff8-a206-8f928bae7aa3, 4029e9ab-3e95-46e5-856e-687d3769e02b",
"traceId": "0d610b96e002491fa9877bf1e8a17531",
"evidence": "unchangedRows=4",
"checkedAt": "2026-08-08T01:49:35.674Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "恢复本轮修改前的四箱规配置",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "94733cd5-bdae-4180-8718-28e6ab4a1403, bc0f1db1-9cee-49ec-94e3-ebb3edce4a6e",
"traceId": "bc83db99ee44415c88262aeda0c5986e",
"evidence": "restore=original-values",
"checkedAt": "2026-08-08T01:49:35.718Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "尺寸规则恢复后回显与初始值一致",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "94733cd5-bdae-4180-8718-28e6ab4a1403, bc0f1db1-9cee-49ec-94e3-ebb3edce4a6e",
"traceId": "bc83db99ee44415c88262aeda0c5986e",
"evidence": "restoredRows=4",
"checkedAt": "2026-08-08T01:49:35.718Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants?page=1&size=20&keyword=%E5%90%8E%E5%8F%B0%E5%AE%A2%E6%88%B7%E9%AA%8C%E6%94%B6-808014935078"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b15d5d78-27cb-4f70-9539-bc9782fa9d4d, becba750-039d-45f3-9232-d234a0822d75",
"traceId": "b164e91b4b5d4a71b0e33f7a58547796",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.761Z"
},
{
"module": "customers",
"scenario": null,
"name": "列表包含新客户且字段完整",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants?page=1&size=20&keyword=%E5%90%8E%E5%8F%B0%E5%AE%A2%E6%88%B7%E9%AA%8C%E6%94%B6-808014935078"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b15d5d78-27cb-4f70-9539-bc9782fa9d4d, becba750-039d-45f3-9232-d234a0822d75",
"traceId": "b164e91b4b5d4a71b0e33f7a58547796",
"evidence": "found=true, missing=unifiedSocialCreditCode,approvedAt",
"checkedAt": "2026-08-08T01:49:35.761Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "aa331679-497b-4007-a2fb-da56e3cbbad4, f446df9f-2e1b-4d83-add1-89bd202e044b",
"traceId": "8b3768d937bf40fc95d56ad6d113a018",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.786Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户详情基本信息正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "aa331679-497b-4007-a2fb-da56e3cbbad4, f446df9f-2e1b-4d83-add1-89bd202e044b",
"traceId": "8b3768d937bf40fc95d56ad6d113a018",
"evidence": "tenantId=2085906232656908290",
"checkedAt": "2026-08-08T01:49:35.786Z"
},
{
"module": "customers",
"scenario": null,
"name": "编辑客户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "52b27de8-d911-4e28-bf4e-6f0336de347a, 8fd191d9-9086-4947-809b-9566cfa8ef3f",
"traceId": "60335ad877ce4f2aab22a4b7a46ddea6",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.822Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "52b27de8-d911-4e28-bf4e-6f0336de347a, 8fd191d9-9086-4947-809b-9566cfa8ef3f",
"traceId": "60335ad877ce4f2aab22a4b7a46ddea6",
"evidence": "tenantId=2085906232656908290",
"checkedAt": "2026-08-08T01:49:35.822Z"
},
{
"module": "customers",
"scenario": null,
"name": "回查客户编辑结果",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "dddb9214-4e49-4700-a8c1-1b3251c1f770, c178d727-427f-498e-b9a2-53c147ce1d0f",
"traceId": "fa9b09a40152434ca0b84fed609b8135",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.847Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户编辑已持久化",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "dddb9214-4e49-4700-a8c1-1b3251c1f770, c178d727-427f-498e-b9a2-53c147ce1d0f",
"traceId": "fa9b09a40152434ca0b84fed609b8135",
"evidence": "tenantId=2085906232656908290",
"checkedAt": "2026-08-08T01:49:35.847Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询资质",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085906232656908290/qualifications"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "128ff201-0f6a-414e-9795-de0096fb3651, cd73f145-57fe-4868-ac80-2f17fe43e736",
"traceId": "3f8cc6b956e642bfae01be933562bcb5",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.868Z"
},
{
"module": "customers",
"scenario": null,
"name": "资质概况结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085906232656908290/qualifications"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "128ff201-0f6a-414e-9795-de0096fb3651, cd73f145-57fe-4868-ac80-2f17fe43e736",
"traceId": "3f8cc6b956e642bfae01be933562bcb5",
"evidence": "count=0",
"checkedAt": "2026-08-08T01:49:35.868Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询成员",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085906232656908290/members"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "86b6120a-8875-4d54-8d31-be87136912a3, 004d2843-40b5-4f24-90d5-0a50fbfbff6a",
"traceId": "f209e87c6de4499d88c225d417aae147",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.895Z"
},
{
"module": "customers",
"scenario": null,
"name": "联系电话账号是企业管理员",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085906232656908290/members"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "86b6120a-8875-4d54-8d31-be87136912a3, 004d2843-40b5-4f24-90d5-0a50fbfbff6a",
"traceId": "f209e87c6de4499d88c225d417aae147",
"evidence": "ownerFound=false, matches=false",
"checkedAt": "2026-08-08T01:49:35.895Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询运单数量",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/tenant-counts?tenantIds=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "a1628f1c-7c0e-487e-9c73-bdb947e5b097, 7a56c8b1-45b6-43d1-a375-04b7931031d1",
"traceId": "a4702c0bc393445d9c87382fcb0b0967",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.927Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况返回当前客户运单数",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/tenant-counts?tenantIds=2085906232656908290"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "a1628f1c-7c0e-487e-9c73-bdb947e5b097, 7a56c8b1-45b6-43d1-a375-04b7931031d1",
"traceId": "a4702c0bc393445d9c87382fcb0b0967",
"evidence": "orderCount=0",
"checkedAt": "2026-08-08T01:49:35.927Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户运单记录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=2085906232656908290&page=1&size=10"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "a451cd7f-d4ee-47e2-97cd-5a901cd94601, adc8098d-f2d4-4d63-aaf1-4de7bb39b09b",
"traceId": "6d7fff06ac24421e81fa29ff76d01452",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.961Z"
},
{
"module": "customers",
"scenario": null,
"name": "运单记录分页结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=2085906232656908290&page=1&size=10"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "a451cd7f-d4ee-47e2-97cd-5a901cd94601, adc8098d-f2d4-4d63-aaf1-4de7bb39b09b",
"traceId": "6d7fff06ac24421e81fa29ff76d01452",
"evidence": "records=0, total=0",
"checkedAt": "2026-08-08T01:49:35.961Z"
},
{
"module": "customers",
"scenario": null,
"name": "停用客户",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085906232656908290/status"
},
"http": 200,
"code": "A0409",
"message": "Tenant status must be changed through review / 当前租户状态必须通过审核流程变更",
"requestId": "523dda29-c36f-4292-b9f6-f8ff11252cf8",
"traceId": "d090c5e0512a485bb22b5747efe031c1",
"evidence": "",
"checkedAt": "2026-08-08T01:49:35.989Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户停用状态正确",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085906232656908290/status"
},
"http": 200,
"code": "A0409",
"message": "Tenant status must be changed through review / 当前租户状态必须通过审核流程变更",
"requestId": "523dda29-c36f-4292-b9f6-f8ff11252cf8",
"traceId": "d090c5e0512a485bb22b5747efe031c1",
"evidence": "status=missing",
"checkedAt": "2026-08-08T01:49:35.989Z"
}
]
}
@@ -0,0 +1,813 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808015022476",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:50:25.152Z",
"startedAt": "2026-08-08T01:50:22.476Z",
"success": false,
"fatal": null,
"summary": {
"total": 42,
"passed": 41,
"failed": 1
},
"modules": [
{
"name": "system",
"startedAt": "2026-08-08T01:50:22.852Z",
"finishedAt": "2026-08-08T01:50:25.152Z",
"success": false,
"passed": 40,
"failed": 1,
"fatal": null
}
],
"scenarios": [],
"deploymentBlockers": [],
"createdResources": {
"systemPermission": {
"id": "2085906431492083713",
"code": "platform:acceptance:808015022476"
},
"systemRole": {
"id": "2085906432268029954",
"code": "ADMIN_ACCEPT_808015022476",
"finalStatus": "DISABLED",
"retainedBecauseAssigned": true
},
"systemUser": {
"membershipId": "2085906433278857219",
"username": "admin_accept_808015022476",
"finalStatus": "DISABLED"
}
},
"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": "3862d908-e909-4dcf-b5d6-68c35c601997, 647660f1-adf0-4904-944b-825865155384",
"traceId": "05faac27686a4a039e64d757e0edd8e2",
"evidence": "",
"checkedAt": "2026-08-08T01:50:22.851Z"
},
{
"module": "system",
"scenario": null,
"name": "查询菜单权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "bc73da56-d8c6-4615-8925-35bd8c7fa80a, a42ef0ae-7ec7-43a3-855b-6942be136e1e",
"traceId": "2e7511f2161a43768f9657a203c8433b",
"evidence": "",
"checkedAt": "2026-08-08T01:50:22.898Z"
},
{
"module": "system",
"scenario": null,
"name": "权限资源返回数组",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "bc73da56-d8c6-4615-8925-35bd8c7fa80a, a42ef0ae-7ec7-43a3-855b-6942be136e1e",
"traceId": "2e7511f2161a43768f9657a203c8433b",
"evidence": "count=117",
"checkedAt": "2026-08-08T01:50:22.898Z"
},
{
"module": "system",
"scenario": null,
"name": "创建自定义权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "7ea9f445-9c49-4d4e-aa08-da5a1c43625e, 49dd870b-d91c-4d1c-b331-dfc83546aeb7",
"traceId": "eb56fb23c9894fd0be9ad81c740bbef6",
"evidence": "",
"checkedAt": "2026-08-08T01:50:22.924Z"
},
{
"module": "system",
"scenario": null,
"name": "权限创建字段正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "7ea9f445-9c49-4d4e-aa08-da5a1c43625e, 49dd870b-d91c-4d1c-b331-dfc83546aeb7",
"traceId": "eb56fb23c9894fd0be9ad81c740bbef6",
"evidence": "permissionId=2085906431492083713",
"checkedAt": "2026-08-08T01:50:22.924Z"
},
{
"module": "system",
"scenario": null,
"name": "编辑自定义权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906431492083713"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "557ae432-4a69-4fbe-aaec-cfb8aa9dad96, eb9bd580-1281-4374-a1f6-11ef3e688a4e",
"traceId": "1750b227fa944d4cb7da19a7ec3f34bd",
"evidence": "",
"checkedAt": "2026-08-08T01:50:22.962Z"
},
{
"module": "system",
"scenario": null,
"name": "权限编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906431492083713"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "557ae432-4a69-4fbe-aaec-cfb8aa9dad96, eb9bd580-1281-4374-a1f6-11ef3e688a4e",
"traceId": "1750b227fa944d4cb7da19a7ec3f34bd",
"evidence": "permissionId=2085906431492083713",
"checkedAt": "2026-08-08T01:50:22.962Z"
},
{
"module": "system",
"scenario": null,
"name": "停用自定义权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906431492083713/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "416a1c59-2f93-4a5d-97b0-a74fbd8641f5, f4327630-ab04-4de6-8e67-10c6f79026e1",
"traceId": "ecf8f3f2dccd4aeeb1d44720c6a51fbe",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.001Z"
},
{
"module": "system",
"scenario": null,
"name": "权限状态回显 DISABLED",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906431492083713/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "416a1c59-2f93-4a5d-97b0-a74fbd8641f5, f4327630-ab04-4de6-8e67-10c6f79026e1",
"traceId": "ecf8f3f2dccd4aeeb1d44720c6a51fbe",
"evidence": "status=DISABLED",
"checkedAt": "2026-08-08T01:50:23.001Z"
},
{
"module": "system",
"scenario": null,
"name": "启用自定义权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906431492083713/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b2c70c80-3b83-47b5-836f-34ab2c25e844, 88c210bd-8da4-4918-a4ad-864d3b5f5feb",
"traceId": "b3aa4cb6548f474e81170fa29ee8adfb",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.069Z"
},
{
"module": "system",
"scenario": null,
"name": "权限状态回显 ACTIVE",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906431492083713/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b2c70c80-3b83-47b5-836f-34ab2c25e844, 88c210bd-8da4-4918-a4ad-864d3b5f5feb",
"traceId": "b3aa4cb6548f474e81170fa29ee8adfb",
"evidence": "status=ACTIVE",
"checkedAt": "2026-08-08T01:50:23.069Z"
},
{
"module": "system",
"scenario": null,
"name": "创建平台自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b8cd050d-4490-4e67-a8b1-ae1863ae9a19, 185c5c9e-00f9-4eb2-879e-ef1c6787ed8a",
"traceId": "c4bee06eed6643259949d1b59cae4b58",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.109Z"
},
{
"module": "system",
"scenario": null,
"name": "角色创建字段正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b8cd050d-4490-4e67-a8b1-ae1863ae9a19, 185c5c9e-00f9-4eb2-879e-ef1c6787ed8a",
"traceId": "c4bee06eed6643259949d1b59cae4b58",
"evidence": "roleId=2085906432268029954",
"checkedAt": "2026-08-08T01:50:23.109Z"
},
{
"module": "system",
"scenario": null,
"name": "编辑平台自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906432268029954"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "de9e5362-ba53-464a-b075-b5f4a4a1b2f9, a7db41c4-a7a4-4b1d-8da3-82eb84976501",
"traceId": "8bf00a4b81db436f8f8e0d9eb4fc7504",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.143Z"
},
{
"module": "system",
"scenario": null,
"name": "角色编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906432268029954"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "de9e5362-ba53-464a-b075-b5f4a4a1b2f9, a7db41c4-a7a4-4b1d-8da3-82eb84976501",
"traceId": "8bf00a4b81db436f8f8e0d9eb4fc7504",
"evidence": "roleId=2085906432268029954",
"checkedAt": "2026-08-08T01:50:23.143Z"
},
{
"module": "system",
"scenario": null,
"name": "角色全量保存权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "34652c0f-68e0-409b-a81b-a3fde1b0d5c7, 8098de9a-eb28-45df-ba7e-4fcc9da6a94f",
"traceId": "2d873144d1764ace820c412ef5e066f7",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.173Z"
},
{
"module": "system",
"scenario": null,
"name": "回查平台角色列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9c1d3e76-699d-4062-aa05-a829f59f981b, cf50013d-e46a-46fa-8ab8-2f44916365de",
"traceId": "471a3378e73449f9bd7cd364cf348ba8",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.202Z"
},
{
"module": "system",
"scenario": null,
"name": "角色权限 ID 正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9c1d3e76-699d-4062-aa05-a829f59f981b, cf50013d-e46a-46fa-8ab8-2f44916365de",
"traceId": "471a3378e73449f9bd7cd364cf348ba8",
"evidence": "permissionIds=2085906431492083713",
"checkedAt": "2026-08-08T01:50:23.202Z"
},
{
"module": "system",
"scenario": null,
"name": "停用自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906432268029954/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c00aeb09-dfe3-4b2b-86e4-867d6fb6a3da, 04173951-61d9-404e-ba2d-82e280dec6c0",
"traceId": "4a3dd5ce3e6b47ce898a1de3c8db5929",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.234Z"
},
{
"module": "system",
"scenario": null,
"name": "角色状态回显 DISABLED",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906432268029954/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c00aeb09-dfe3-4b2b-86e4-867d6fb6a3da, 04173951-61d9-404e-ba2d-82e280dec6c0",
"traceId": "4a3dd5ce3e6b47ce898a1de3c8db5929",
"evidence": "status=DISABLED",
"checkedAt": "2026-08-08T01:50:23.234Z"
},
{
"module": "system",
"scenario": null,
"name": "启用自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906432268029954/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "90d3d0d3-6278-419d-80dc-8dd8e1871441, 5967fcdb-e915-453e-ad2e-659684c2e5a5",
"traceId": "d26b4ae497674a8a807fa665e299bf66",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.266Z"
},
{
"module": "system",
"scenario": null,
"name": "角色状态回显 ACTIVE",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906432268029954/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "90d3d0d3-6278-419d-80dc-8dd8e1871441, 5967fcdb-e915-453e-ad2e-659684c2e5a5",
"traceId": "d26b4ae497674a8a807fa665e299bf66",
"evidence": "status=ACTIVE",
"checkedAt": "2026-08-08T01:50:23.266Z"
},
{
"module": "system",
"scenario": null,
"name": "查询内部用户可选平台角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users/options"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "897baa98-b6e3-4342-9663-ae755aae520d, 211f9d91-4ddd-4bcf-b772-79ca675fc7b2",
"traceId": "f18aa7f583a647dfae2e8c48f9a1b489",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.285Z"
},
{
"module": "system",
"scenario": null,
"name": "用户选项包含平台与测试角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users/options"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "897baa98-b6e3-4342-9663-ae755aae520d, 211f9d91-4ddd-4bcf-b772-79ca675fc7b2",
"traceId": "f18aa7f583a647dfae2e8c48f9a1b489",
"evidence": "tenants=1, roles=6",
"checkedAt": "2026-08-08T01:50:23.285Z"
},
{
"module": "system",
"scenario": null,
"name": "检查测试联系电话可用性",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/contact-phone-availability?contactPhone=16153823285"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "fbf33fe4-75f6-438c-99ba-47549ce8805d, ad6022b9-2efb-473c-8c3e-ae48d7aec604",
"traceId": "c98156b1b6474dfba62afe560033c39d",
"evidence": "attempt=1",
"checkedAt": "2026-08-08T01:50:23.325Z"
},
{
"module": "system",
"scenario": null,
"name": "创建平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/users"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "05181309-123d-48e7-88b1-d7c1515dfd85, 906d753a-6606-4bf8-a021-3546ac271d68",
"traceId": "ef3b6ac399b04f3ba218a91ddaff4011",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.667Z"
},
{
"module": "system",
"scenario": null,
"name": "内部用户账号、平台范围和角色正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/users"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "05181309-123d-48e7-88b1-d7c1515dfd85, 906d753a-6606-4bf8-a021-3546ac271d68",
"traceId": "ef3b6ac399b04f3ba218a91ddaff4011",
"evidence": "membershipId=2085906433278857219",
"checkedAt": "2026-08-08T01:50:23.667Z"
},
{
"module": "system",
"scenario": null,
"name": "按账号查询平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users?page=1&size=20&keyword=admin_accept_808015022476"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f7e19766-1a3b-453d-8da7-2ee7c6bb9510, 2d8f661c-adac-4b65-b387-c32fe416cf99",
"traceId": "e9f4feff9de64dda8249da5c4f5c2997",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.706Z"
},
{
"module": "system",
"scenario": null,
"name": "用户列表包含新账号且字段完整",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users?page=1&size=20&keyword=admin_accept_808015022476"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f7e19766-1a3b-453d-8da7-2ee7c6bb9510, 2d8f661c-adac-4b65-b387-c32fe416cf99",
"traceId": "e9f4feff9de64dda8249da5c4f5c2997",
"evidence": "records=1, missing=lastLoginAt",
"checkedAt": "2026-08-08T01:50:23.706Z"
},
{
"module": "system",
"scenario": null,
"name": "编辑平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085906433278857219"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "dab4bc55-e9b9-43ac-a441-60c5db913e8b, 68579859-a9ec-4c0f-b88a-dfe3c6ecb943",
"traceId": "bf625720e0ed48d7be260c96e3cc52dd",
"evidence": "",
"checkedAt": "2026-08-08T01:50:23.835Z"
},
{
"module": "system",
"scenario": null,
"name": "用户编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085906433278857219"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "dab4bc55-e9b9-43ac-a441-60c5db913e8b, 68579859-a9ec-4c0f-b88a-dfe3c6ecb943",
"traceId": "bf625720e0ed48d7be260c96e3cc52dd",
"evidence": "membershipId=2085906433278857219",
"checkedAt": "2026-08-08T01:50:23.835Z"
},
{
"module": "system",
"scenario": null,
"name": "新建内部用户可使用初始密码登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2900cbe1-03b4-47b0-8a36-9d35f8b501a5, d9ca3bfd-63a5-4f55-a4f0-23f787c9fad9",
"traceId": "ff4db6c1b70143a0896632987c607028",
"evidence": "username=admin_accept_808015022476",
"checkedAt": "2026-08-08T01:50:24.172Z"
},
{
"module": "system",
"scenario": null,
"name": "重置平台内部用户密码",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085906433278857219/password"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5394ac6a-39a7-4cbd-8585-15e5e4b7b5b2, 5a848ca0-e0be-4202-90b8-349ea139b66d",
"traceId": "b7400d39555e4e6882c48fb51881962d",
"evidence": "",
"checkedAt": "2026-08-08T01:50:24.555Z"
},
{
"module": "system",
"scenario": null,
"name": "内部用户使用重置密码登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ef5a94f0-5441-4949-b1b9-a35b13b91171, 0d027de5-7a79-48ea-8e9a-67520119945e",
"traceId": "bdc5956b70af4c07a21d1a920a6a03cb",
"evidence": "username=admin_accept_808015022476",
"checkedAt": "2026-08-08T01:50:24.896Z"
},
{
"module": "system",
"scenario": null,
"name": "停用平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085906433278857219/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c33b0953-838a-4588-a812-3a1562b1a152, 1d240a0b-da43-4022-8448-f665d545a340",
"traceId": "b63b36a345524296b83418827c1e0c74",
"evidence": "",
"checkedAt": "2026-08-08T01:50:25.006Z"
},
{
"module": "system",
"scenario": null,
"name": "内部用户停用状态正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085906433278857219/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c33b0953-838a-4588-a812-3a1562b1a152, 1d240a0b-da43-4022-8448-f665d545a340",
"traceId": "b63b36a345524296b83418827c1e0c74",
"evidence": "status=DISABLED",
"checkedAt": "2026-08-08T01:50:25.006Z"
},
{
"module": "system",
"scenario": null,
"name": "停用用户禁止登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "A0403",
"message": "账号已禁用",
"requestId": "7cdffebc-71dc-4936-af2b-c84587e57d35",
"traceId": "e9e240b4f20648bcb9a3cfaf5b635936",
"evidence": "HTTP=200, code=A0403",
"checkedAt": "2026-08-08T01:50:25.038Z"
},
{
"module": "system",
"scenario": null,
"name": "清空测试角色权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ca6af757-c870-4edb-ae7e-b52f66a660f7, 51a8a91f-b0ca-4604-a589-2d96ae867f0b",
"traceId": "715a9dde2e6f4506ae9d119022912fd7",
"evidence": "",
"checkedAt": "2026-08-08T01:50:25.063Z"
},
{
"module": "system",
"scenario": "system-role-protection",
"name": "已分配成员的角色禁止删除",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "DELETE",
"path": "/api/platform/v1/iam/roles/2085906432268029954"
},
"http": 200,
"code": "A0409",
"message": "角色已分配给成员,不能删除",
"requestId": "36ff0f03-e18a-460d-bb4f-74f9840abb0a",
"traceId": "9a3784e25d9e4caf9f5fd5b1a6a6c589",
"evidence": "HTTP=200, code=A0409",
"checkedAt": "2026-08-08T01:50:25.086Z"
},
{
"module": "system",
"scenario": null,
"name": "清理时停用测试角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906432268029954/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9f6fe407-f7f4-48ad-bf20-321b4f51940b, 5122341b-9884-40b1-bd83-efd4aa63ddf7",
"traceId": "2c07306a4f8341c8a50a1b622b0f782d",
"evidence": "",
"checkedAt": "2026-08-08T01:50:25.113Z"
},
{
"module": "system",
"scenario": null,
"name": "保留测试角色处于停用状态",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906432268029954/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9f6fe407-f7f4-48ad-bf20-321b4f51940b, 5122341b-9884-40b1-bd83-efd4aa63ddf7",
"traceId": "2c07306a4f8341c8a50a1b622b0f782d",
"evidence": "status=DISABLED",
"checkedAt": "2026-08-08T01:50:25.113Z"
},
{
"module": "system",
"scenario": null,
"name": "删除临时权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "DELETE",
"path": "/api/platform/v1/iam/permissions/2085906431492083713"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9a131c57-5b58-4520-b57b-4ce11b97c6c1, 87535aa2-341f-42d0-bb2f-9d2473e2c2d6",
"traceId": "d38da0cbdff043e2a02941e7c22b5aa7",
"evidence": "",
"checkedAt": "2026-08-08T01:50:25.152Z"
}
]
}
@@ -0,0 +1,813 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808015100919",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:51:03.556Z",
"startedAt": "2026-08-08T01:51:00.919Z",
"success": true,
"fatal": null,
"summary": {
"total": 42,
"passed": 42,
"failed": 0
},
"modules": [
{
"name": "system",
"startedAt": "2026-08-08T01:51:01.288Z",
"finishedAt": "2026-08-08T01:51:03.556Z",
"success": true,
"passed": 41,
"failed": 0,
"fatal": null
}
],
"scenarios": [],
"deploymentBlockers": [],
"createdResources": {
"systemPermission": {
"id": "2085906592846958593",
"code": "platform:acceptance:808015100919"
},
"systemRole": {
"id": "2085906593333497857",
"code": "ADMIN_ACCEPT_808015100919",
"finalStatus": "DISABLED",
"retainedBecauseAssigned": true
},
"systemUser": {
"membershipId": "2085906594386268164",
"username": "admin_accept_808015100919",
"finalStatus": "DISABLED"
}
},
"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": "c46fca94-1309-46aa-95e9-1b784448858e, ad409e6c-e6cf-4d53-a116-9158de60164d",
"traceId": "7fd072801f5642758e684535cda2a566",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.287Z"
},
{
"module": "system",
"scenario": null,
"name": "查询菜单权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "fcb82450-2a21-40f2-9d97-527c3e5a6d3c, 3e8612e4-9644-4124-a1ad-5b73fc0c5583",
"traceId": "eb96c9d48c3044d1a2930934b117397b",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.357Z"
},
{
"module": "system",
"scenario": null,
"name": "权限资源返回数组",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "fcb82450-2a21-40f2-9d97-527c3e5a6d3c, 3e8612e4-9644-4124-a1ad-5b73fc0c5583",
"traceId": "eb96c9d48c3044d1a2930934b117397b",
"evidence": "count=117",
"checkedAt": "2026-08-08T01:51:01.357Z"
},
{
"module": "system",
"scenario": null,
"name": "创建自定义权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "659e3afe-fa3c-497e-88f2-c2837c98cbb4, d974ee1d-789f-4d85-a526-c983f88ddb50",
"traceId": "0650e78de8a149469f0bad90d49744b4",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.394Z"
},
{
"module": "system",
"scenario": null,
"name": "权限创建字段正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "659e3afe-fa3c-497e-88f2-c2837c98cbb4, d974ee1d-789f-4d85-a526-c983f88ddb50",
"traceId": "0650e78de8a149469f0bad90d49744b4",
"evidence": "permissionId=2085906592846958593",
"checkedAt": "2026-08-08T01:51:01.394Z"
},
{
"module": "system",
"scenario": null,
"name": "编辑自定义权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906592846958593"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d15875fe-d1a6-49f9-85d5-5eea9cf63784, 47bcb4f1-6e67-40ca-bf51-5c211b201830",
"traceId": "9ec6e12e915d4ee6b25f294ecab3fa04",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.423Z"
},
{
"module": "system",
"scenario": null,
"name": "权限编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906592846958593"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d15875fe-d1a6-49f9-85d5-5eea9cf63784, 47bcb4f1-6e67-40ca-bf51-5c211b201830",
"traceId": "9ec6e12e915d4ee6b25f294ecab3fa04",
"evidence": "permissionId=2085906592846958593",
"checkedAt": "2026-08-08T01:51:01.423Z"
},
{
"module": "system",
"scenario": null,
"name": "停用自定义权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906592846958593/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f0ffa2ab-b69a-40ba-85a9-d086b04fe728, 93c33865-bcf0-4343-9211-c4a8409dcf2b",
"traceId": "0e02ae7bf28f4a82a345d2b7ed48f0af",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.452Z"
},
{
"module": "system",
"scenario": null,
"name": "权限状态回显 DISABLED",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906592846958593/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f0ffa2ab-b69a-40ba-85a9-d086b04fe728, 93c33865-bcf0-4343-9211-c4a8409dcf2b",
"traceId": "0e02ae7bf28f4a82a345d2b7ed48f0af",
"evidence": "status=DISABLED",
"checkedAt": "2026-08-08T01:51:01.452Z"
},
{
"module": "system",
"scenario": null,
"name": "启用自定义权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906592846958593/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d4d761c9-49f0-4ade-84df-0a1e49a35719, 909949c9-bab9-40e3-a55b-f2b1caa70fd7",
"traceId": "310e92cf8eda446680090988b6d445b6",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.483Z"
},
{
"module": "system",
"scenario": null,
"name": "权限状态回显 ACTIVE",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/permissions/2085906592846958593/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d4d761c9-49f0-4ade-84df-0a1e49a35719, 909949c9-bab9-40e3-a55b-f2b1caa70fd7",
"traceId": "310e92cf8eda446680090988b6d445b6",
"evidence": "status=ACTIVE",
"checkedAt": "2026-08-08T01:51:01.483Z"
},
{
"module": "system",
"scenario": null,
"name": "创建平台自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "245ccb89-2c4f-4e4b-90e2-b71e56e15853, d676f5fa-865b-4dc9-b41a-6f04fae2d40b",
"traceId": "95d1980bca0b4eebbc29e54ca1f2fb80",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.510Z"
},
{
"module": "system",
"scenario": null,
"name": "角色创建字段正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "245ccb89-2c4f-4e4b-90e2-b71e56e15853, d676f5fa-865b-4dc9-b41a-6f04fae2d40b",
"traceId": "95d1980bca0b4eebbc29e54ca1f2fb80",
"evidence": "roleId=2085906593333497857",
"checkedAt": "2026-08-08T01:51:01.510Z"
},
{
"module": "system",
"scenario": null,
"name": "编辑平台自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906593333497857"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "99058303-5cc8-4284-b7ea-c600de0ad937, fa5ead52-af51-45a6-bde0-1baa763ad519",
"traceId": "b989dc3a137e4c72b99853b066c1eca4",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.547Z"
},
{
"module": "system",
"scenario": null,
"name": "角色编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906593333497857"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "99058303-5cc8-4284-b7ea-c600de0ad937, fa5ead52-af51-45a6-bde0-1baa763ad519",
"traceId": "b989dc3a137e4c72b99853b066c1eca4",
"evidence": "roleId=2085906593333497857",
"checkedAt": "2026-08-08T01:51:01.547Z"
},
{
"module": "system",
"scenario": null,
"name": "角色全量保存权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3a5b503f-d9bc-4dc2-87e8-6bda92f02bb0, b0ec509c-b230-4d27-9c2c-5d40105f3a66",
"traceId": "47e3da73f3bc44e9bcd984ab00e14637",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.579Z"
},
{
"module": "system",
"scenario": null,
"name": "回查平台角色列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9858f207-aca4-4ece-b3b3-cea34d3ba860, a4e495b3-9f0a-42dc-98a0-af5fe656b966",
"traceId": "e63feb304286400a8fa373ca7591ad07",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.607Z"
},
{
"module": "system",
"scenario": null,
"name": "角色权限 ID 正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/roles"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9858f207-aca4-4ece-b3b3-cea34d3ba860, a4e495b3-9f0a-42dc-98a0-af5fe656b966",
"traceId": "e63feb304286400a8fa373ca7591ad07",
"evidence": "permissionIds=2085906592846958593",
"checkedAt": "2026-08-08T01:51:01.607Z"
},
{
"module": "system",
"scenario": null,
"name": "停用自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906593333497857/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ec78442e-7ba6-4be6-b69c-8ea64185c5bf, 8eb982bb-61d9-4dc3-9c96-91f92875bda4",
"traceId": "8ab508205d914b2284375d8da6b7970a",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.633Z"
},
{
"module": "system",
"scenario": null,
"name": "角色状态回显 DISABLED",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906593333497857/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ec78442e-7ba6-4be6-b69c-8ea64185c5bf, 8eb982bb-61d9-4dc3-9c96-91f92875bda4",
"traceId": "8ab508205d914b2284375d8da6b7970a",
"evidence": "status=DISABLED",
"checkedAt": "2026-08-08T01:51:01.633Z"
},
{
"module": "system",
"scenario": null,
"name": "启用自定义角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906593333497857/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5c80676b-4394-4961-858c-f497c4b27081, 7ec30864-f9a5-4df0-addc-2c0f840feb17",
"traceId": "c384c297cf864eefa3566b7d09b11fee",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.670Z"
},
{
"module": "system",
"scenario": null,
"name": "角色状态回显 ACTIVE",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906593333497857/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "5c80676b-4394-4961-858c-f497c4b27081, 7ec30864-f9a5-4df0-addc-2c0f840feb17",
"traceId": "c384c297cf864eefa3566b7d09b11fee",
"evidence": "status=ACTIVE",
"checkedAt": "2026-08-08T01:51:01.670Z"
},
{
"module": "system",
"scenario": null,
"name": "查询内部用户可选平台角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users/options"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "13acee9f-902f-4539-8da0-99f3797b5bc1, 8b394968-d7ff-4258-91b2-c31a5a2d7909",
"traceId": "d1c5595ce5cb4442bf6bd0e1b0e25870",
"evidence": "",
"checkedAt": "2026-08-08T01:51:01.696Z"
},
{
"module": "system",
"scenario": null,
"name": "用户选项包含平台与测试角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users/options"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "13acee9f-902f-4539-8da0-99f3797b5bc1, 8b394968-d7ff-4258-91b2-c31a5a2d7909",
"traceId": "d1c5595ce5cb4442bf6bd0e1b0e25870",
"evidence": "tenants=1, roles=7",
"checkedAt": "2026-08-08T01:51:01.696Z"
},
{
"module": "system",
"scenario": null,
"name": "检查测试联系电话可用性",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/contact-phone-availability?contactPhone=16153861696"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d1698183-b833-48f2-9c0d-4505d4378b23, 402a4e7c-72e6-441c-8270-f2b45cb9b513",
"traceId": "f644e76d31f34c8ba554b469333e968a",
"evidence": "attempt=1",
"checkedAt": "2026-08-08T01:51:01.729Z"
},
{
"module": "system",
"scenario": null,
"name": "创建平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/users"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "cdd4805b-c311-44c9-bf48-06574d45cf22, b3489347-6716-4e7a-a08f-03a4c96b8268",
"traceId": "e0a99588c6354a2a90b29003ace78b98",
"evidence": "",
"checkedAt": "2026-08-08T01:51:02.087Z"
},
{
"module": "system",
"scenario": null,
"name": "内部用户账号、平台范围和角色正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/users"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "cdd4805b-c311-44c9-bf48-06574d45cf22, b3489347-6716-4e7a-a08f-03a4c96b8268",
"traceId": "e0a99588c6354a2a90b29003ace78b98",
"evidence": "membershipId=2085906594386268164",
"checkedAt": "2026-08-08T01:51:02.088Z"
},
{
"module": "system",
"scenario": null,
"name": "按账号查询平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users?page=1&size=20&keyword=admin_accept_808015100919"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "8c02a4f1-3c13-4a6e-a4d9-b034b0080015, ddeac472-31bd-4d30-8a7e-d57b8189414d",
"traceId": "ae4a6a1ad8304042b7d736fbd3a065a7",
"evidence": "",
"checkedAt": "2026-08-08T01:51:02.133Z"
},
{
"module": "system",
"scenario": null,
"name": "用户列表包含新账号且字段完整",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/users?page=1&size=20&keyword=admin_accept_808015100919"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "8c02a4f1-3c13-4a6e-a4d9-b034b0080015, ddeac472-31bd-4d30-8a7e-d57b8189414d",
"traceId": "ae4a6a1ad8304042b7d736fbd3a065a7",
"evidence": "records=1, missing=none, lastLoginAt=omitted",
"checkedAt": "2026-08-08T01:51:02.133Z"
},
{
"module": "system",
"scenario": null,
"name": "编辑平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085906594386268164"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d578d25c-e92b-4a49-a31d-568814b1eb61, 6e5ba8ec-e4a3-47ca-8b13-7d87c705e965",
"traceId": "cd97d23a1d3f461ca100bf5f12230ca5",
"evidence": "",
"checkedAt": "2026-08-08T01:51:02.243Z"
},
{
"module": "system",
"scenario": null,
"name": "用户编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085906594386268164"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d578d25c-e92b-4a49-a31d-568814b1eb61, 6e5ba8ec-e4a3-47ca-8b13-7d87c705e965",
"traceId": "cd97d23a1d3f461ca100bf5f12230ca5",
"evidence": "membershipId=2085906594386268164",
"checkedAt": "2026-08-08T01:51:02.243Z"
},
{
"module": "system",
"scenario": null,
"name": "新建内部用户可使用初始密码登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "8571d9e4-f96a-4443-a87c-32eb55f091cb, b784af8d-f8c7-4090-8214-c12baf001725",
"traceId": "144d89ec70de4973978f6c74459281b1",
"evidence": "username=admin_accept_808015100919",
"checkedAt": "2026-08-08T01:51:02.583Z"
},
{
"module": "system",
"scenario": null,
"name": "重置平台内部用户密码",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085906594386268164/password"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "eb8fd540-afb8-471b-9f07-d4ccc67a4116, 976d8f22-2ead-4d73-8453-29d7bf275026",
"traceId": "4c6f4ce003d34a0c9f8664ba0827d15c",
"evidence": "",
"checkedAt": "2026-08-08T01:51:02.962Z"
},
{
"module": "system",
"scenario": null,
"name": "内部用户使用重置密码登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "124f7055-ac54-477b-a05f-66bfbc99dde8, 382be9db-ccfe-4768-a3bc-1c01488b50dd",
"traceId": "b5997c34877e40fb9e3b537bc0dfc6d9",
"evidence": "username=admin_accept_808015100919",
"checkedAt": "2026-08-08T01:51:03.296Z"
},
{
"module": "system",
"scenario": null,
"name": "停用平台内部用户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085906594386268164/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "af59e590-1033-410a-aa8b-fd8eb16c3dcc, 272075fb-6736-4a83-af07-35984e48208f",
"traceId": "5d1830a60ecd43aab91ebcfd1a5c84c0",
"evidence": "",
"checkedAt": "2026-08-08T01:51:03.397Z"
},
{
"module": "system",
"scenario": null,
"name": "内部用户停用状态正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/users/2085906594386268164/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "af59e590-1033-410a-aa8b-fd8eb16c3dcc, 272075fb-6736-4a83-af07-35984e48208f",
"traceId": "5d1830a60ecd43aab91ebcfd1a5c84c0",
"evidence": "status=DISABLED",
"checkedAt": "2026-08-08T01:51:03.397Z"
},
{
"module": "system",
"scenario": null,
"name": "停用用户禁止登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "A0403",
"message": "账号已禁用",
"requestId": "29944df8-91e3-44a1-9fd6-db763cf82590",
"traceId": "ea8ef8687c444375998445cbeb888643",
"evidence": "HTTP=200, code=A0403",
"checkedAt": "2026-08-08T01:51:03.426Z"
},
{
"module": "system",
"scenario": null,
"name": "清空测试角色权限",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/permissions"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d3537c80-d23e-46e8-b18d-3d4447cc4083, e1285a3b-6ae6-47ca-bff3-da2ee98ae697",
"traceId": "6ce1bb2efc2c43bebe413f83096e969c",
"evidence": "",
"checkedAt": "2026-08-08T01:51:03.469Z"
},
{
"module": "system",
"scenario": "system-role-protection",
"name": "已分配成员的角色禁止删除",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "DELETE",
"path": "/api/platform/v1/iam/roles/2085906593333497857"
},
"http": 200,
"code": "A0409",
"message": "角色已分配给成员,不能删除",
"requestId": "b03df360-5159-4d7d-aee6-56b8b17f96aa",
"traceId": "0742aa6eee464fbba7dd2ba3f4551636",
"evidence": "HTTP=200, code=A0409",
"checkedAt": "2026-08-08T01:51:03.491Z"
},
{
"module": "system",
"scenario": null,
"name": "清理时停用测试角色",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906593333497857/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "7cb0f80b-5a00-4c22-9c4f-28b3b67ade9f, 70a6b1f3-1d1d-4646-b099-b26b14d794fd",
"traceId": "e070173b00714448b1e969e4ed63a2a9",
"evidence": "",
"checkedAt": "2026-08-08T01:51:03.527Z"
},
{
"module": "system",
"scenario": null,
"name": "保留测试角色处于停用状态",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/roles/2085906593333497857/status"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "7cb0f80b-5a00-4c22-9c4f-28b3b67ade9f, 70a6b1f3-1d1d-4646-b099-b26b14d794fd",
"traceId": "e070173b00714448b1e969e4ed63a2a9",
"evidence": "status=DISABLED",
"checkedAt": "2026-08-08T01:51:03.527Z"
},
{
"module": "system",
"scenario": null,
"name": "删除临时权限资源",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "DELETE",
"path": "/api/platform/v1/iam/permissions/2085906592846958593"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1878c927-e1b6-41ef-8c4b-db2c6226fcc2, d6376a43-5105-4ddc-a4fb-e703b59bfcc1",
"traceId": "a9968e4afb2c403aa3956be6624f7fe0",
"evidence": "",
"checkedAt": "2026-08-08T01:51:03.556Z"
}
]
}
@@ -0,0 +1,841 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808015112686",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:51:15.306Z",
"startedAt": "2026-08-08T01:51:12.686Z",
"success": true,
"fatal": null,
"summary": {
"total": 43,
"passed": 43,
"failed": 0
},
"modules": [
{
"name": "master-data",
"startedAt": "2026-08-08T01:51:13.053Z",
"finishedAt": "2026-08-08T01:51:15.306Z",
"success": true,
"passed": 42,
"failed": 0,
"fatal": null
}
],
"scenarios": [],
"deploymentBlockers": [],
"createdResources": {
"warehouse": {
"id": "2085906642714542081",
"code": "AW808015112686",
"finalStatus": "INACTIVE"
},
"location": {
"id": "2085906643662454786",
"warehouseId": "2085906642714542081",
"deleted": false
},
"pallet": {
"id": "2085906644723613697",
"warehouseId": "2085906642714542081",
"deleted": true
},
"expressCompany": {
"id": "2085906645604417538",
"code": "EX808015112686",
"finalStatus": "INACTIVE"
},
"partner": {
"id": "2085906646594273281",
"code": "PT808015112686",
"finalStatus": "INACTIVE"
}
},
"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": "1ef641c7-79de-4ed6-a839-c0d7b54d717c, 99bc9d62-a3ad-44ed-b466-e370c97bac9f",
"traceId": "187deb804f5b4f28aa6e0e7c19ba604e",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.052Z"
},
{
"module": "master-data",
"scenario": null,
"name": "查询平台仓库列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin?page=1&size=20"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1ae69a26-fab4-463a-bd0a-f879e5b7f042, 2ecb0dd3-d8df-476c-a3a8-078f7a321ab0",
"traceId": "6b0170d887dd4f84b75cef71c8a1f10b",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.136Z"
},
{
"module": "master-data",
"scenario": null,
"name": "仓库列表分页结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin?page=1&size=20"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1ae69a26-fab4-463a-bd0a-f879e5b7f042, 2ecb0dd3-d8df-476c-a3a8-078f7a321ab0",
"traceId": "6b0170d887dd4f84b75cef71c8a1f10b",
"evidence": "total=266",
"checkedAt": "2026-08-08T01:51:13.136Z"
},
{
"module": "master-data",
"scenario": null,
"name": "创建平台公共仓库",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/warehouse-admin"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1fe78ad4-d10c-42f7-93a7-261ca921c160, e1526cc7-30cc-4cef-b55b-0482ade19bcc",
"traceId": "3de321d1858a43ada9fb2b8967ed6658",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.308Z"
},
{
"module": "master-data",
"scenario": null,
"name": "仓库创建返回字符串兼容 ID",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/warehouse-admin"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1fe78ad4-d10c-42f7-93a7-261ca921c160, e1526cc7-30cc-4cef-b55b-0482ade19bcc",
"traceId": "3de321d1858a43ada9fb2b8967ed6658",
"evidence": "warehouseId=2085906642714542081",
"checkedAt": "2026-08-08T01:51:13.308Z"
},
{
"module": "master-data",
"scenario": null,
"name": "查询平台仓库详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin/2085906642714542081?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "802a5696-3011-4321-8e04-ca01d1b9ce73, b98b5f8e-bd09-4a62-bb5a-cf434c76286c",
"traceId": "d47debad2dfa4ebc95ccef7e543513a9",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.339Z"
},
{
"module": "master-data",
"scenario": null,
"name": "仓库详情、地址和能力正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin/2085906642714542081?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "802a5696-3011-4321-8e04-ca01d1b9ce73, b98b5f8e-bd09-4a62-bb5a-cf434c76286c",
"traceId": "d47debad2dfa4ebc95ccef7e543513a9",
"evidence": "addresses=1, capabilities=1",
"checkedAt": "2026-08-08T01:51:13.339Z"
},
{
"module": "master-data",
"scenario": null,
"name": "编辑平台仓库",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/warehouse-admin/2085906642714542081?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "7f3b45a4-01ef-47cf-aa46-68381336214a, 3168722f-b184-45c2-be32-05445e6d7184",
"traceId": "5abea76955804f938bd792150e700dba",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.414Z"
},
{
"module": "master-data",
"scenario": null,
"name": "回查编辑后的平台仓库",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin/2085906642714542081?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "283efed9-6562-4e6e-bc9f-0f469b01c5b3, fc291ee4-c263-422c-8c03-54e7178ecd06",
"traceId": "78b247e6f3f94a8887b57e7a92544b19",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.445Z"
},
{
"module": "master-data",
"scenario": null,
"name": "仓库编辑已持久化",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin/2085906642714542081?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "283efed9-6562-4e6e-bc9f-0f469b01c5b3, fc291ee4-c263-422c-8c03-54e7178ecd06",
"traceId": "78b247e6f3f94a8887b57e7a92544b19",
"evidence": "warehouseId=2085906642714542081",
"checkedAt": "2026-08-08T01:51:13.445Z"
},
{
"module": "master-data",
"scenario": null,
"name": "创建仓库库位",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/locations?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ba671a58-75ac-43fe-bf44-d12b3092957b, ee3d0f31-1d17-4a5a-9585-64d13a96e08a",
"traceId": "7cb020a4ed944d5db972080de5c78204",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.512Z"
},
{
"module": "master-data",
"scenario": null,
"name": "查询库位详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/locations/2085906643662454786?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "0c11050f-ade1-4bfa-b953-1f19c22afe3e, db448a60-956d-4994-8f47-a6bd9b44ce42",
"traceId": "e14a466068504819b64c8cdf2cd9de69",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.543Z"
},
{
"module": "master-data",
"scenario": null,
"name": "库位详情关联正确仓库",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/locations/2085906643662454786?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "0c11050f-ade1-4bfa-b953-1f19c22afe3e, db448a60-956d-4994-8f47-a6bd9b44ce42",
"traceId": "e14a466068504819b64c8cdf2cd9de69",
"evidence": "locationId=2085906643662454786",
"checkedAt": "2026-08-08T01:51:13.543Z"
},
{
"module": "master-data",
"scenario": null,
"name": "编辑库位",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/locations/2085906643662454786?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "09909b24-5a09-49a1-8440-b3dc6db25dab, dd165f85-9ba9-4b8a-b8ad-cd38890c6b33",
"traceId": "82ebaf3f05f1458da4d226643bf90ca8",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.599Z"
},
{
"module": "master-data",
"scenario": null,
"name": "回查编辑后的库位",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/locations/2085906643662454786?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "48cf9423-205f-4e60-a50f-a5cf2d917ddc, b1e193c6-dae3-45b4-a489-47fb42b18629",
"traceId": "ef46e900c9364dc082a0f5dc4e214940",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.629Z"
},
{
"module": "master-data",
"scenario": null,
"name": "库位编辑与版本递增正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/locations/2085906643662454786?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "48cf9423-205f-4e60-a50f-a5cf2d917ddc, b1e193c6-dae3-45b4-a489-47fb42b18629",
"traceId": "ef46e900c9364dc082a0f5dc4e214940",
"evidence": "version=1",
"checkedAt": "2026-08-08T01:51:13.629Z"
},
{
"module": "master-data",
"scenario": null,
"name": "停用库位",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PATCH",
"path": "/api/platform/v1/locations/2085906643662454786/status?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e1d6ae58-c6ea-4961-b8c7-6d5366f92218, bde78f2c-1629-4c7e-a3ee-a88da44457de",
"traceId": "15cd0ef443b943cba3ff0814e7eaed18",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.667Z"
},
{
"module": "master-data",
"scenario": null,
"name": "删除未占用测试库位",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "DELETE",
"path": "/api/platform/v1/locations/2085906643662454786?tenantId=0&warehouseId=2085906642714542081&version=2"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9be3228a-c600-4f3f-8377-ef51f638f26d, dc648e93-bf03-4816-987a-be105ad9163e",
"traceId": "23726e294a754bb1a201fd0fee6ee8e8",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.703Z"
},
{
"module": "master-data",
"scenario": null,
"name": "创建仓库托盘",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/pallets?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3d8e81f4-5354-4545-af6f-99cab01bbfd8, ca6733c2-f6b6-43bb-b496-119f856589f6",
"traceId": "09ee75d6fb414c1887f4a548479df789",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.762Z"
},
{
"module": "master-data",
"scenario": null,
"name": "查询托盘详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/pallets/2085906644723613697?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "816ef3e3-35ba-40a4-842e-d7b2f6e08c32, cc15e9f1-85c2-4d6b-835e-ed9b355bc6d5",
"traceId": "80bbf30816024e3892436f9b37d80870",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.803Z"
},
{
"module": "master-data",
"scenario": null,
"name": "托盘详情关联正确仓库",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/pallets/2085906644723613697?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "816ef3e3-35ba-40a4-842e-d7b2f6e08c32, cc15e9f1-85c2-4d6b-835e-ed9b355bc6d5",
"traceId": "80bbf30816024e3892436f9b37d80870",
"evidence": "palletId=2085906644723613697",
"checkedAt": "2026-08-08T01:51:13.803Z"
},
{
"module": "master-data",
"scenario": null,
"name": "编辑托盘",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/pallets/2085906644723613697?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1e663a9d-4ad5-4067-931c-524c8f684414, 49952e54-7b84-43a2-b4ba-e5cd66a618a5",
"traceId": "a537bb9bf0ec4b2b89c6d5006cd5159a",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.850Z"
},
{
"module": "master-data",
"scenario": null,
"name": "回查编辑后的托盘",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/pallets/2085906644723613697?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6b85ce32-cd57-4969-a7fe-7fa5a73a1d6d, b1ba3cb3-eebc-491a-9e4a-9d71fc43eda6",
"traceId": "712dc29aacfb4621ad11cd93feb22617",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.873Z"
},
{
"module": "master-data",
"scenario": null,
"name": "托盘编辑已持久化",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/pallets/2085906644723613697?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6b85ce32-cd57-4969-a7fe-7fa5a73a1d6d, b1ba3cb3-eebc-491a-9e4a-9d71fc43eda6",
"traceId": "712dc29aacfb4621ad11cd93feb22617",
"evidence": "palletId=2085906644723613697",
"checkedAt": "2026-08-08T01:51:13.873Z"
},
{
"module": "master-data",
"scenario": null,
"name": "删除未使用测试托盘",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "DELETE",
"path": "/api/platform/v1/pallets/2085906644723613697?tenantId=0&warehouseId=2085906642714542081"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "bef950ad-0c47-446a-ac22-dfe274c94e9f, 178851f2-542f-4eaa-90ca-7292edeaf2c5",
"traceId": "57a9b30f3b2643428f10e07e04f27514",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.915Z"
},
{
"module": "master-data",
"scenario": null,
"name": "创建快递公司",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/express-companies?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d45dac88-ddf2-4fcf-8d1f-32903d3603aa, 06b5cf45-bddc-48eb-8e35-8c37f702a7ca",
"traceId": "39e3e7beddb34ca8a685dacd1ac8bdc8",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.973Z"
},
{
"module": "master-data",
"scenario": null,
"name": "查询快递公司详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/express-companies/2085906645604417538?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3e791401-a620-45cb-8a98-6133ede11869, 9d0c5a8c-d86a-464d-aceb-e6d7302dfb27",
"traceId": "d6c304ef339e4be9ad2e9575c4eff3ea",
"evidence": "",
"checkedAt": "2026-08-08T01:51:13.999Z"
},
{
"module": "master-data",
"scenario": null,
"name": "快递公司详情正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/express-companies/2085906645604417538?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3e791401-a620-45cb-8a98-6133ede11869, 9d0c5a8c-d86a-464d-aceb-e6d7302dfb27",
"traceId": "d6c304ef339e4be9ad2e9575c4eff3ea",
"evidence": "expressId=2085906645604417538",
"checkedAt": "2026-08-08T01:51:13.999Z"
},
{
"module": "master-data",
"scenario": null,
"name": "编辑快递公司",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/express-companies/2085906645604417538?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c5a387ec-8b2d-4f92-b9fc-2da20d060feb, 70157926-52be-4e58-9307-59052ae5b78c",
"traceId": "4ecc9cc89233433bb6e5a6f2ddaab2dd",
"evidence": "",
"checkedAt": "2026-08-08T01:51:14.038Z"
},
{
"module": "master-data",
"scenario": null,
"name": "停用快递公司",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PATCH",
"path": "/api/platform/v1/express-companies/2085906645604417538/status?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "062601e2-5dba-487c-bb19-a428aa763c78, bac5717a-fc6e-4313-bfda-5464f0fcefcc",
"traceId": "fe0d135c3fc04fcd827df6c6908714c6",
"evidence": "",
"checkedAt": "2026-08-08T01:51:14.085Z"
},
{
"module": "master-data",
"scenario": null,
"name": "筛选快递公司列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/express-companies?page=1&size=20&keyword=EX808015112686"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "98298dab-4669-4fce-924e-697302d27af8, a1ae79b5-8300-492c-9bff-0da660607e0b",
"traceId": "d2b00bf84ec6475685c4d5be1c264c62",
"evidence": "",
"checkedAt": "2026-08-08T01:51:14.145Z"
},
{
"module": "master-data",
"scenario": null,
"name": "快递公司列表回显停用状态",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/express-companies?page=1&size=20&keyword=EX808015112686"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "98298dab-4669-4fce-924e-697302d27af8, a1ae79b5-8300-492c-9bff-0da660607e0b",
"traceId": "d2b00bf84ec6475685c4d5be1c264c62",
"evidence": "records=1",
"checkedAt": "2026-08-08T01:51:14.146Z"
},
{
"module": "master-data",
"scenario": null,
"name": "创建往来单位",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/partners?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c9af27f3-7013-4d13-a72a-2d6eed35db71, 359738bc-ee6f-48c4-8f85-ab615a070995",
"traceId": "3566029c1c784e91a85c4b9d31eb832d",
"evidence": "",
"checkedAt": "2026-08-08T01:51:14.211Z"
},
{
"module": "master-data",
"scenario": null,
"name": "查询往来单位详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/partners/2085906646594273281?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1730bb54-5f1c-4f0c-bf13-4ea61e620314, 1e4b9158-35d2-42a7-a7ad-f53e2569fa7a",
"traceId": "52baa99de51647158df9c0f8fe2b63b3",
"evidence": "",
"checkedAt": "2026-08-08T01:51:14.248Z"
},
{
"module": "master-data",
"scenario": null,
"name": "往来单位详情正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/partners/2085906646594273281?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1730bb54-5f1c-4f0c-bf13-4ea61e620314, 1e4b9158-35d2-42a7-a7ad-f53e2569fa7a",
"traceId": "52baa99de51647158df9c0f8fe2b63b3",
"evidence": "partnerId=2085906646594273281",
"checkedAt": "2026-08-08T01:51:14.248Z"
},
{
"module": "master-data",
"scenario": null,
"name": "编辑往来单位",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/partners/2085906646594273281?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ac0bef92-9d28-4a11-aa2c-5a5dbd33f6a1, fd05b54a-716c-42e3-9434-a0630dd7457c",
"traceId": "e9f7a126a0a14e95bd04bc6b1bc78f3a",
"evidence": "",
"checkedAt": "2026-08-08T01:51:14.293Z"
},
{
"module": "master-data",
"scenario": null,
"name": "停用往来单位",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PATCH",
"path": "/api/platform/v1/partners/2085906646594273281/status?tenantId=0"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d0f760bf-9f17-4eb1-a780-f8820b87a9a1, d2a490be-2378-48e1-a3d0-7ab449e5f951",
"traceId": "a736d8e144f345a2bf4f9c48d1def321",
"evidence": "",
"checkedAt": "2026-08-08T01:51:14.332Z"
},
{
"module": "master-data",
"scenario": null,
"name": "筛选往来单位列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/partners?page=1&size=20&keyword=PT808015112686"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "907f44fe-f8b8-44bf-85ab-a0c69f2478ad, 7e717103-216c-4b38-a994-9531746c2940",
"traceId": "cdf53bd68d3c409c95146f6c7b559124",
"evidence": "",
"checkedAt": "2026-08-08T01:51:14.377Z"
},
{
"module": "master-data",
"scenario": null,
"name": "往来单位列表回显编辑与停用状态",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/partners?page=1&size=20&keyword=PT808015112686"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "907f44fe-f8b8-44bf-85ab-a0c69f2478ad, 7e717103-216c-4b38-a994-9531746c2940",
"traceId": "cdf53bd68d3c409c95146f6c7b559124",
"evidence": "records=1",
"checkedAt": "2026-08-08T01:51:14.377Z"
},
{
"module": "master-data",
"scenario": null,
"name": "下载仓库导入模板",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin/import-template"
},
"http": 200,
"code": null,
"message": "3852 bytes (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)",
"requestId": "b2f6a827-8145-47f9-9278-3a487a7949dc, bb227b6e-4e7c-4edd-b397-c5688c23a629",
"traceId": "e1a8c949a58c45a2857eb13320a1bd84",
"evidence": "bytes=3852, contentType=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"checkedAt": "2026-08-08T01:51:14.927Z"
},
{
"module": "master-data",
"scenario": null,
"name": "下载快递公司导入模板",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/express-companies/import-template"
},
"http": 200,
"code": null,
"message": "3691 bytes (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)",
"requestId": "ae4dd7e8-09be-45d4-9726-300b6b2825fc, 7a6fb2f3-9d61-4262-890a-1b2b4edd8284",
"traceId": "b8f1e4fdceff484a8f2c8bd33a49bc94",
"evidence": "bytes=3691, contentType=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"checkedAt": "2026-08-08T01:51:15.223Z"
},
{
"module": "master-data",
"scenario": null,
"name": "下载往来单位导入模板",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/partners/import-template"
},
"http": 200,
"code": null,
"message": "3829 bytes (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)",
"requestId": "da616cb4-1d1c-4b3f-a425-8a9dd76f79b0, fdf996ea-64b5-4d96-a957-9ce287d5b044",
"traceId": "c1e4777d7d48439990fba23346a450df",
"evidence": "bytes=3829, contentType=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"checkedAt": "2026-08-08T01:51:15.270Z"
},
{
"module": "master-data",
"scenario": null,
"name": "停用测试仓库",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PATCH",
"path": "/api/platform/v1/warehouse-admin/2085906642714542081/status?tenantId=0&status=INACTIVE"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e4156701-dfb5-477d-a308-bb5705d6d162, b0134658-c2ba-493c-ab64-8fb10c10b25d",
"traceId": "ff955ad956bc49e384fb9435aa3f8ab9",
"evidence": "",
"checkedAt": "2026-08-08T01:51:15.306Z"
}
]
}
@@ -0,0 +1,207 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808015123816",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:51:28.649Z",
"startedAt": "2026-08-08T01:51:23.816Z",
"success": true,
"fatal": null,
"summary": {
"total": 9,
"passed": 9,
"failed": 0
},
"modules": [
{
"name": "downloads",
"startedAt": "2026-08-08T01:51:24.193Z",
"finishedAt": "2026-08-08T01:51:28.649Z",
"success": true,
"passed": 8,
"failed": 0,
"fatal": null
}
],
"scenarios": [],
"deploymentBlockers": [],
"createdResources": {
"exportTask": {
"id": "2085906689023344642"
}
},
"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": "f224e2f6-ed1e-4ce9-9269-9809e39b9c24, 44b6a750-78af-4f48-b492-53014903ebcc",
"traceId": "091531d115f0450c897615089b74c4e8",
"evidence": "",
"checkedAt": "2026-08-08T01:51:24.192Z"
},
{
"module": "downloads",
"scenario": null,
"name": "创建客户列表导出任务",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/files/export-tasks"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6a036de6-e397-42af-85d5-982b86363291, 6aad9d95-aafa-429d-8a31-3d1e96838613",
"traceId": "b6c05bc1c62e4e41af77f633473386b4",
"evidence": "",
"checkedAt": "2026-08-08T01:51:24.339Z"
},
{
"module": "downloads",
"scenario": null,
"name": "导出任务创建字段正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/files/export-tasks"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6a036de6-e397-42af-85d5-982b86363291, 6aad9d95-aafa-429d-8a31-3d1e96838613",
"traceId": "b6c05bc1c62e4e41af77f633473386b4",
"evidence": "taskId=2085906689023344642, status=PENDING",
"checkedAt": "2026-08-08T01:51:24.339Z"
},
{
"module": "downloads",
"scenario": null,
"name": "查询下载中心任务列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/files/export-tasks?page=1&size=20&exportType=CUSTOMER_LIST"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1b323f47-c5b7-4d9d-869f-d4dbf3e750fe, 1b7f8e83-9958-44c4-940f-ad4079d85fb7",
"traceId": "3dbbff1c6e4140eba6b61a8a14eea612",
"evidence": "",
"checkedAt": "2026-08-08T01:51:24.458Z"
},
{
"module": "downloads",
"scenario": null,
"name": "下载中心列表包含新任务",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/files/export-tasks?page=1&size=20&exportType=CUSTOMER_LIST"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1b323f47-c5b7-4d9d-869f-d4dbf3e750fe, 1b7f8e83-9958-44c4-940f-ad4079d85fb7",
"traceId": "3dbbff1c6e4140eba6b61a8a14eea612",
"evidence": "records=3",
"checkedAt": "2026-08-08T01:51:24.459Z"
},
{
"module": "downloads",
"scenario": null,
"name": "轮询导出任务直到终态",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/files/export-tasks/2085906689023344642"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c5e6c1cb-4ebd-4200-8b1a-b79c1f3c660d, 8d593d8c-ee47-408c-a228-8f978834d7c1",
"traceId": "641aafb4f4664fa38853cf1a44a9ca88",
"evidence": "status=SUCCEEDED, processedRows=0",
"checkedAt": "2026-08-08T01:51:28.568Z"
},
{
"module": "downloads",
"scenario": null,
"name": "查询导出任务详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/files/export-tasks/2085906689023344642"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "af467347-8049-47b4-8033-46630f9dedf9, c6c058a8-03bb-48ec-b196-f873055f4039",
"traceId": "c6111c38b3ad4b2fa1da5730e282a42a",
"evidence": "",
"checkedAt": "2026-08-08T01:51:28.608Z"
},
{
"module": "downloads",
"scenario": null,
"name": "导出详情行数和文件元数据正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/files/export-tasks/2085906689023344642"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "af467347-8049-47b4-8033-46630f9dedf9, c6c058a8-03bb-48ec-b196-f873055f4039",
"traceId": "c6111c38b3ad4b2fa1da5730e282a42a",
"evidence": "processedRows=0, sizeBytes=4066",
"checkedAt": "2026-08-08T01:51:28.608Z"
},
{
"module": "downloads",
"scenario": null,
"name": "下载并校验客户列表 XLSX",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/files/export-tasks/2085906689023344642/download"
},
"http": 200,
"code": null,
"message": "4066 bytes (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)",
"requestId": "575144ab-472a-4aab-9014-2645a5481aa0, a889d47e-0771-4203-9299-a72ccc2717b6",
"traceId": "377bd4490a2b479cb5c27366ca3eb215",
"evidence": "bytes=4066, contentType=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"checkedAt": "2026-08-08T01:51:28.649Z"
}
]
}
@@ -0,0 +1,351 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808015217256",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:52:21.122Z",
"startedAt": "2026-08-08T01:52:17.256Z",
"success": false,
"fatal": null,
"summary": {
"total": 16,
"passed": 15,
"failed": 1
},
"modules": [
{
"name": "pricing",
"startedAt": "2026-08-08T01:52:17.700Z",
"finishedAt": "2026-08-08T01:52:21.122Z",
"success": false,
"passed": 14,
"failed": 1,
"fatal": null
}
],
"scenarios": [
{
"name": "price-book-lifecycle",
"module": "pricing",
"success": false,
"deploymentBlocked": true,
"passed": 14,
"failed": 1
}
],
"deploymentBlockers": [
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "国内起运仓和目的海外仓均精确映射",
"evidence": "origins=none, overseas=none, missing=CN-DONGGUAN-01:0,CN-YIWU-01:0,纽约仓/US-NYC-01:0,洛杉矶仓/US-LAX-01:0",
"code": "200",
"requestId": "cc81fcc4-8273-43a6-b4c8-3e5f130b54d3, a277860d-2568-4a2b-aaad-ff7e9ea37a03",
"traceId": "565abde8751743338bffd42e3b4574ee"
}
],
"createdResources": {
"priceBookVersion": {
"versionId": "2085906913647177729",
"marker": "ADMIN-API-20260808015217256",
"sourceSha256": "3170fd3aa8795cd816ca9600f87a6a2a06b15d0cf6042d4592f33d4b30c7b8ab"
}
},
"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": "f9c60930-f70d-4b01-8bee-98dd018b439d, 531a52ec-c085-4f9c-9edf-6d5928ef1468",
"traceId": "5d1f869c92b1410080fe35c8eff721af",
"evidence": "",
"checkedAt": "2026-08-08T01:52:17.699Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "查询发布前船期价格版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e2a75598-bb18-4337-8fe0-9b59e0ad8aa0, 11c63498-91f8-4886-9e10-aab751fd2e13",
"traceId": "ae8a30fa81fd46a089174f91b00958df",
"evidence": "",
"checkedAt": "2026-08-08T01:52:17.738Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "发布前最多一份生效版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e2a75598-bb18-4337-8fe0-9b59e0ad8aa0, 11c63498-91f8-4886-9e10-aab751fd2e13",
"traceId": "ae8a30fa81fd46a089174f91b00958df",
"evidence": "versions=6, active=1",
"checkedAt": "2026-08-08T01:52:17.738Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "船期价格测试文件已隔离",
"passed": true,
"deploymentBlocker": false,
"request": null,
"http": null,
"code": null,
"message": null,
"requestId": null,
"traceId": null,
"evidence": "fileName=ADMIN-API-20260808015217256-2026.08.03柏悦价格表 船期2026.08.12.xlsx, bytes=1343413, sha256=3170fd3aa8795cd816ca9600f87a6a2a06b15d0cf6042d4592f33d4b30c7b8ab",
"checkedAt": "2026-08-08T01:52:17.741Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "上传并解析船期价格表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3cc44632-978c-4738-bcaf-3f01bddefda8, 5e08671c-f7d4-471a-9b01-133551c1b489",
"traceId": "005da6c1da3b42b082f7f5a2834e1b6a",
"evidence": "fileName=ADMIN-API-20260808015217256-2026.08.03柏悦价格表 船期2026.08.12.xlsx, bytes=1343413, sha256=3170fd3aa8795cd816ca9600f87a6a2a06b15d0cf6042d4592f33d4b30c7b8ab",
"checkedAt": "2026-08-08T01:52:20.865Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "上传返回隔离的待确认版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3cc44632-978c-4738-bcaf-3f01bddefda8, 5e08671c-f7d4-471a-9b01-133551c1b489",
"traceId": "005da6c1da3b42b082f7f5a2834e1b6a",
"evidence": "versionId=2085906913647177729, status=PENDING_CONFIRMATION, fileName=ADMIN-API-20260808015217256-2026.08.03柏悦价格表 船期2026.08.12.xlsx, shaMatched=true",
"checkedAt": "2026-08-08T01:52:20.866Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "解析出15个固定渠道且编码唯一",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3cc44632-978c-4738-bcaf-3f01bddefda8, 5e08671c-f7d4-471a-9b01-133551c1b489",
"traceId": "005da6c1da3b42b082f7f5a2834e1b6a",
"evidence": "channelCount=15, unique=15, missing=none",
"checkedAt": "2026-08-08T01:52:20.866Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "解析价格行完整且可追溯到 Excel",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3cc44632-978c-4738-bcaf-3f01bddefda8, 5e08671c-f7d4-471a-9b01-133551c1b489",
"traceId": "005da6c1da3b42b082f7f5a2834e1b6a",
"evidence": "rateCount=2157, declared=2157, valid=true",
"checkedAt": "2026-08-08T01:52:20.866Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "解析阻断仅包含可确认的仓库映射问题",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3cc44632-978c-4738-bcaf-3f01bddefda8, 5e08671c-f7d4-471a-9b01-133551c1b489",
"traceId": "005da6c1da3b42b082f7f5a2834e1b6a",
"evidence": "blocking=0, unexpected=none, infrastructure=none",
"checkedAt": "2026-08-08T01:52:20.866Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "上传后回查价格版本列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "346c77b6-f7cd-48de-90e6-28bf71e0df6e, 44b4f4ed-461e-456a-b2f8-a002dc79a2fe",
"traceId": "e4b0d45964f24590a6f775b8095c8cea",
"evidence": "",
"checkedAt": "2026-08-08T01:52:20.891Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "上传只生成待确认版本且不替换当前生效版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "346c77b6-f7cd-48de-90e6-28bf71e0df6e, 44b4f4ed-461e-456a-b2f8-a002dc79a2fe",
"traceId": "e4b0d45964f24590a6f775b8095c8cea",
"evidence": "pending=PENDING_CONFIRMATION, activeBefore=2085661426268549121, activeAfterUpload=2085661426268549121",
"checkedAt": "2026-08-08T01:52:20.891Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "查询待确认版本渠道和价格预览",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books/2085906913647177729"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "01d816ed-813f-446f-b48b-68d9a06b7214, 4c1182b5-e14b-466d-bbc0-23dff4231773",
"traceId": "ec62c42ad48c404c92ba76991b7050d2",
"evidence": "",
"checkedAt": "2026-08-08T01:52:20.978Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "详情与上传解析结果一致",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books/2085906913647177729"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "01d816ed-813f-446f-b48b-68d9a06b7214, 4c1182b5-e14b-466d-bbc0-23dff4231773",
"traceId": "ec62c42ad48c404c92ba76991b7050d2",
"evidence": "versionId=2085906913647177729, channels=15, rates=2157, issues=2",
"checkedAt": "2026-08-08T01:52:20.978Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "下载原始 XLSX 且内容散列一致",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books/2085906913647177729/source"
},
"http": 200,
"code": null,
"message": "1343413 bytes (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)",
"requestId": "483c4175-e0c6-4546-8881-56867d597bfb, c7e835e6-b14f-4b69-923a-8ffe94fb1454, bdd9f7e1-27ad-410c-9b46-8c43115d5a17",
"traceId": "00d9415284fe452ca76e65907aa804a8, d826c60003f0415abc9328759fa51537",
"evidence": "bytes=1343413, contentType=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, zipMagic=true, sha256=3170fd3aa8795cd816ca9600f87a6a2a06b15d0cf6042d4592f33d4b30c7b8ab",
"checkedAt": "2026-08-08T01:52:21.084Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "查询启用的平台公共仓库映射候选",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin?page=1&size=200&status=ACTIVE"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "cc81fcc4-8273-43a6-b4c8-3e5f130b54d3, a277860d-2568-4a2b-aaad-ff7e9ea37a03",
"traceId": "565abde8751743338bffd42e3b4574ee",
"evidence": "",
"checkedAt": "2026-08-08T01:52:21.121Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "国内起运仓和目的海外仓均精确映射",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin?page=1&size=200&status=ACTIVE"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "cc81fcc4-8273-43a6-b4c8-3e5f130b54d3, a277860d-2568-4a2b-aaad-ff7e9ea37a03",
"traceId": "565abde8751743338bffd42e3b4574ee",
"evidence": "origins=none, overseas=none, missing=CN-DONGGUAN-01:0,CN-YIWU-01:0,纽约仓/US-NYC-01:0,洛杉矶仓/US-LAX-01:0",
"checkedAt": "2026-08-08T01:52:21.121Z"
}
]
}
@@ -0,0 +1,318 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808015313253",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:53:13.958Z",
"startedAt": "2026-08-08T01:53:13.253Z",
"success": false,
"fatal": null,
"summary": {
"total": 14,
"passed": 13,
"failed": 1
},
"modules": [
{
"name": "orders",
"startedAt": "2026-08-08T01:53:13.665Z",
"finishedAt": "2026-08-08T01:53:13.955Z",
"success": true,
"passed": 12,
"failed": 0,
"fatal": null
},
{
"name": "inbound-measurement",
"startedAt": "2026-08-08T01:53:13.957Z",
"finishedAt": "2026-08-08T01:53:13.958Z",
"success": false,
"passed": 0,
"failed": 1,
"fatal": null
}
],
"scenarios": [
{
"name": "mojia-carton-measurement",
"module": "inbound-measurement",
"success": false,
"deploymentBlocked": true,
"passed": 0,
"failed": 1
}
],
"deploymentBlockers": [
{
"module": "inbound-measurement",
"scenario": "mojia-carton-measurement",
"name": "取得隔离的待测量入库单",
"evidence": "orders 模块未提供 marker=ADMIN-API-20260808015313253 的 inboundId/tenantId;为避免修改随机历史订单,本模块停止写入",
"code": null,
"requestId": null,
"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": "86e3206d-1691-432c-9a99-b015db7f8f97, 973c647e-a637-400e-8bb9-5b9ad35079fd",
"traceId": "9f8e6191cbcf4d28a8bc1adb7f8d458f",
"evidence": "",
"checkedAt": "2026-08-08T01:53:13.663Z"
},
{
"module": "orders",
"scenario": null,
"name": "查询统一订单列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders?page=1&size=20"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "13bf6838-66e2-42cf-985a-5a87b5d2fbb4, 3c93f152-23ef-481b-95a0-3ff83c8f81c0",
"traceId": "8e199a8800064fa3bd7d1d04423bad43",
"evidence": "",
"checkedAt": "2026-08-08T01:53:13.750Z"
},
{
"module": "orders",
"scenario": null,
"name": "统一订单分页结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders?page=1&size=20"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "13bf6838-66e2-42cf-985a-5a87b5d2fbb4, 3c93f152-23ef-481b-95a0-3ff83c8f81c0",
"traceId": "8e199a8800064fa3bd7d1d04423bad43",
"evidence": "records=20, total=348",
"checkedAt": "2026-08-08T01:53:13.750Z"
},
{
"module": "orders",
"scenario": null,
"name": "查询统一订单详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/2085906673965793281?tenantId=2085906649595891713"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "4d1c88c9-e5d6-4ce4-aa3c-006604edc15f, 50489bb2-25fd-45c8-ba70-7f3b14dc5fb1",
"traceId": "de649f51c8884eaf979c74908b191558",
"evidence": "",
"checkedAt": "2026-08-08T01:53:13.789Z"
},
{
"module": "orders",
"scenario": null,
"name": "统一订单详情与列表身份一致",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/2085906673965793281?tenantId=2085906649595891713"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "4d1c88c9-e5d6-4ce4-aa3c-006604edc15f, 50489bb2-25fd-45c8-ba70-7f3b14dc5fb1",
"traceId": "de649f51c8884eaf979c74908b191558",
"evidence": "tenantId=2085906649595891713, orderId=2085906673965793281, timeline=3",
"checkedAt": "2026-08-08T01:53:13.789Z"
},
{
"module": "orders",
"scenario": null,
"name": "查询头程订单列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?page=1&size=20"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e02edcaa-3950-4f19-b4cb-a97f62e179ef, 9bbd217c-eb50-427e-a8e5-3917deeb17bf",
"traceId": "fcb8427cce6243d2b100a906627e2b94",
"evidence": "",
"checkedAt": "2026-08-08T01:53:13.838Z"
},
{
"module": "orders",
"scenario": null,
"name": "头程订单分页结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?page=1&size=20"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e02edcaa-3950-4f19-b4cb-a97f62e179ef, 9bbd217c-eb50-427e-a8e5-3917deeb17bf",
"traceId": "fcb8427cce6243d2b100a906627e2b94",
"evidence": "records=20, total=474",
"checkedAt": "2026-08-08T01:53:13.839Z"
},
{
"module": "orders",
"scenario": null,
"name": "查询头程订单详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/2085906676373323777?tenantId=2085906649595891713"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ff604e75-e48e-4478-83e4-73552521ab4b, 15776b66-94cb-4dda-a3be-f90baebfca36",
"traceId": "64264f2946a441ec9c3637a3ded6aabd",
"evidence": "",
"checkedAt": "2026-08-08T01:53:13.879Z"
},
{
"module": "orders",
"scenario": null,
"name": "头程订单详情包含货物、地址、备注和状态历史",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/2085906676373323777?tenantId=2085906649595891713"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ff604e75-e48e-4478-83e4-73552521ab4b, 15776b66-94cb-4dda-a3be-f90baebfca36",
"traceId": "64264f2946a441ec9c3637a3ded6aabd",
"evidence": "orderId=2085906676373323777, cargoes=1, history=2",
"checkedAt": "2026-08-08T01:53:13.879Z"
},
{
"module": "orders",
"scenario": null,
"name": "查询平台入库单列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/inbounds?page=1&size=20"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ad2936d7-8f63-4afd-b04f-9afcd956c63b, 4c8f2435-1a24-4524-86a7-83db13d230a0",
"traceId": "02518a2fe0fc4987855ff4c28d943689",
"evidence": "",
"checkedAt": "2026-08-08T01:53:13.922Z"
},
{
"module": "orders",
"scenario": null,
"name": "入库单分页结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/inbounds?page=1&size=20"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ad2936d7-8f63-4afd-b04f-9afcd956c63b, 4c8f2435-1a24-4524-86a7-83db13d230a0",
"traceId": "02518a2fe0fc4987855ff4c28d943689",
"evidence": "records=20, total=244",
"checkedAt": "2026-08-08T01:53:13.922Z"
},
{
"module": "orders",
"scenario": null,
"name": "查询平台入库单详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/inbounds/2085905439760363522?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1ffa560d-6466-4e55-b66b-12fbb715a8c4, 3e6c3fa5-3b61-4886-aad0-25433133ce3e",
"traceId": "c952a2b76780471187a9b223df9b243f",
"evidence": "",
"checkedAt": "2026-08-08T01:53:13.955Z"
},
{
"module": "orders",
"scenario": null,
"name": "入库单详情包含托盘库位、文件、备注和状态历史",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/inbounds/2085905439760363522?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1ffa560d-6466-4e55-b66b-12fbb715a8c4, 3e6c3fa5-3b61-4886-aad0-25433133ce3e",
"traceId": "c952a2b76780471187a9b223df9b243f",
"evidence": "inboundId=2085905439760363522, status=WAIT_SPLIT",
"checkedAt": "2026-08-08T01:53:13.955Z"
},
{
"module": "inbound-measurement",
"scenario": "mojia-carton-measurement",
"name": "取得隔离的待测量入库单",
"passed": false,
"deploymentBlocker": true,
"request": null,
"http": null,
"code": null,
"message": null,
"requestId": null,
"traceId": null,
"evidence": "orders 模块未提供 marker=ADMIN-API-20260808015313253 的 inboundId/tenantId;为避免修改随机历史订单,本模块停止写入",
"checkedAt": "2026-08-08T01:53:13.957Z"
}
]
}
@@ -0,0 +1,539 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808015323030",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:53:28.059Z",
"startedAt": "2026-08-08T01:53:23.030Z",
"success": true,
"fatal": null,
"summary": {
"total": 27,
"passed": 27,
"failed": 0
},
"modules": [
{
"name": "pricing",
"startedAt": "2026-08-08T01:53:23.467Z",
"finishedAt": "2026-08-08T01:53:28.059Z",
"success": true,
"passed": 26,
"failed": 0,
"fatal": null
}
],
"scenarios": [
{
"name": "price-book-lifecycle",
"module": "pricing",
"success": true,
"deploymentBlocked": false,
"passed": 26,
"failed": 0
}
],
"deploymentBlockers": [],
"createdResources": {
"priceBookVersion": {
"versionId": "2085907189707878402",
"marker": "ADMIN-API-20260808015323030",
"sourceSha256": "3170fd3aa8795cd816ca9600f87a6a2a06b15d0cf6042d4592f33d4b30c7b8ab"
}
},
"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": "ffd1a415-9a05-484f-80bf-3d560ed20a20, bafe323a-38a8-4b50-8215-4720276f5f5a",
"traceId": "aefcc866fdaa4398834019d8c90945c6",
"evidence": "",
"checkedAt": "2026-08-08T01:53:23.464Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "查询发布前船期价格版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "19ba7a2d-8e69-4638-ac35-c9d73681c8a2, fea311a9-9a0f-41f1-b6df-a0ac829420ea",
"traceId": "8813930e89d44b1ca390cd0e6e7c36e1",
"evidence": "",
"checkedAt": "2026-08-08T01:53:23.521Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "发布前最多一份生效版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "19ba7a2d-8e69-4638-ac35-c9d73681c8a2, fea311a9-9a0f-41f1-b6df-a0ac829420ea",
"traceId": "8813930e89d44b1ca390cd0e6e7c36e1",
"evidence": "versions=7, active=1",
"checkedAt": "2026-08-08T01:53:23.521Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "船期价格测试文件已隔离",
"passed": true,
"deploymentBlocker": false,
"request": null,
"http": null,
"code": null,
"message": null,
"requestId": null,
"traceId": null,
"evidence": "fileName=ADMIN-API-20260808015323030-2026.08.03柏悦价格表 船期2026.08.12.xlsx, bytes=1343413, sha256=3170fd3aa8795cd816ca9600f87a6a2a06b15d0cf6042d4592f33d4b30c7b8ab",
"checkedAt": "2026-08-08T01:53:23.530Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "上传并解析船期价格表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "950d0386-ac27-4dfe-a89c-ca96b582d659, 6d5e1be0-30fc-4ca8-a18b-a9c8ca7d51a9",
"traceId": "99f605ff4563477da81996d4d7d9d24c",
"evidence": "fileName=ADMIN-API-20260808015323030-2026.08.03柏悦价格表 船期2026.08.12.xlsx, bytes=1343413, sha256=3170fd3aa8795cd816ca9600f87a6a2a06b15d0cf6042d4592f33d4b30c7b8ab",
"checkedAt": "2026-08-08T01:53:25.408Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "上传返回隔离的待确认版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "950d0386-ac27-4dfe-a89c-ca96b582d659, 6d5e1be0-30fc-4ca8-a18b-a9c8ca7d51a9",
"traceId": "99f605ff4563477da81996d4d7d9d24c",
"evidence": "versionId=2085907189707878402, status=PENDING_CONFIRMATION, fileName=ADMIN-API-20260808015323030-2026.08.03柏悦价格表 船期2026.08.12.xlsx, shaMatched=true",
"checkedAt": "2026-08-08T01:53:25.409Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "解析出15个固定渠道且编码唯一",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "950d0386-ac27-4dfe-a89c-ca96b582d659, 6d5e1be0-30fc-4ca8-a18b-a9c8ca7d51a9",
"traceId": "99f605ff4563477da81996d4d7d9d24c",
"evidence": "channelCount=15, unique=15, missing=none",
"checkedAt": "2026-08-08T01:53:25.409Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "解析价格行完整且可追溯到 Excel",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "950d0386-ac27-4dfe-a89c-ca96b582d659, 6d5e1be0-30fc-4ca8-a18b-a9c8ca7d51a9",
"traceId": "99f605ff4563477da81996d4d7d9d24c",
"evidence": "rateCount=2157, declared=2157, valid=true",
"checkedAt": "2026-08-08T01:53:25.410Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "解析阻断仅包含可确认的仓库映射问题",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "950d0386-ac27-4dfe-a89c-ca96b582d659, 6d5e1be0-30fc-4ca8-a18b-a9c8ca7d51a9",
"traceId": "99f605ff4563477da81996d4d7d9d24c",
"evidence": "blocking=0, unexpected=none, infrastructure=none",
"checkedAt": "2026-08-08T01:53:25.410Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "上传后回查价格版本列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "85ae312c-18e0-4a27-bcfc-d97e8e98f149, ac831c7e-d205-426b-a122-35073893ac7b",
"traceId": "810f528e025c44aaa7518cddc54faf07",
"evidence": "",
"checkedAt": "2026-08-08T01:53:25.434Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "上传只生成待确认版本且不替换当前生效版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "85ae312c-18e0-4a27-bcfc-d97e8e98f149, ac831c7e-d205-426b-a122-35073893ac7b",
"traceId": "810f528e025c44aaa7518cddc54faf07",
"evidence": "pending=PENDING_CONFIRMATION, activeBefore=2085661426268549121, activeAfterUpload=2085661426268549121",
"checkedAt": "2026-08-08T01:53:25.435Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "查询待确认版本渠道和价格预览",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books/2085907189707878402"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "deffed17-5367-4cd8-a3c1-7ab6ad14f78a, 047c53de-2cec-4dd7-80ea-a63b46805c1b",
"traceId": "987e92da473444b981bb8d49807b4fe2",
"evidence": "",
"checkedAt": "2026-08-08T01:53:25.541Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "详情与上传解析结果一致",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books/2085907189707878402"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "deffed17-5367-4cd8-a3c1-7ab6ad14f78a, 047c53de-2cec-4dd7-80ea-a63b46805c1b",
"traceId": "987e92da473444b981bb8d49807b4fe2",
"evidence": "versionId=2085907189707878402, channels=15, rates=2157, issues=2",
"checkedAt": "2026-08-08T01:53:25.541Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "下载原始 XLSX 且内容散列一致",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books/2085907189707878402/source"
},
"http": 200,
"code": null,
"message": "1343413 bytes (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)",
"requestId": "a87afbfa-e2a8-4716-ad3e-0b9f81bd7e6d, ee2fca00-55ec-406d-b39a-9cdc27cb34c8, 3c9cffc2-06eb-4beb-9213-74289ae5b87d",
"traceId": "8adf5a4db38f4a789d4bf6431d6f4c97, 490c5fe289b24a99a28af29a2afe4fb3",
"evidence": "bytes=1343413, contentType=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, zipMagic=true, sha256=3170fd3aa8795cd816ca9600f87a6a2a06b15d0cf6042d4592f33d4b30c7b8ab",
"checkedAt": "2026-08-08T01:53:25.679Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "查询启用的平台公共国内仓",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin?page=1&size=200&status=ACTIVE&warehouseType=DOMESTIC"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ef2feb3c-a719-4369-86e9-21a7c9ca5df2, 3f35d758-6b6b-468f-80d5-eda98207ca04",
"traceId": "a7864a481e09470cb23c5ac2f5d1364d",
"evidence": "",
"checkedAt": "2026-08-08T01:53:25.715Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "查询启用的平台公共海外仓",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin?page=1&size=200&status=ACTIVE&warehouseType=OVERSEAS"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e00f34ba-ace4-4586-a8dd-3a53030cbc21, 44446dc8-10c5-4cdb-afc3-8450010efc4f",
"traceId": "ce9fc0584ba8445ca468f9e5ca2f630b",
"evidence": "",
"checkedAt": "2026-08-08T01:53:25.764Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "国内起运仓和目的海外仓均精确映射",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/warehouse-admin?page=1&size=200&status=ACTIVE&warehouseType=OVERSEAS"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e00f34ba-ace4-4586-a8dd-3a53030cbc21, 44446dc8-10c5-4cdb-afc3-8450010efc4f",
"traceId": "ce9fc0584ba8445ca468f9e5ca2f630b",
"evidence": "origins=CN-DONGGUAN-01->193000000000200002,CN-YIWU-01->193000000000200001, overseas=纽约仓->193000000000200004,洛杉矶仓->193000000000200003, missing=none",
"checkedAt": "2026-08-08T01:53:25.765Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "确认仓库映射并立即生效",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books/2085907189707878402/publish"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "292ac8b9-c619-4b88-9c0a-d8a6a99acc78, d51d8c16-cca7-4a10-966c-93f30cb8da71",
"traceId": "19cb4f1200434041a6bb2dd12324bbec",
"evidence": "versionId=2085907189707878402, originMappings=2, overseasMappings=2",
"checkedAt": "2026-08-08T01:53:27.865Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "发布后价格版本为 ACTIVE 且无阻断",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books/2085907189707878402/publish"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "292ac8b9-c619-4b88-9c0a-d8a6a99acc78, d51d8c16-cca7-4a10-966c-93f30cb8da71",
"traceId": "19cb4f1200434041a6bb2dd12324bbec",
"evidence": "status=ACTIVE, blocking=0, publishedAt=2026-08-08T09:53:26.781, channels=15, rates=2157",
"checkedAt": "2026-08-08T01:53:27.865Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "发布后回查当前生效和历史版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "0e715e18-3b4e-413e-9f2c-299a7aa37350, a3d54336-5c33-4458-b234-082298026520",
"traceId": "da1deab503934499b576ebeedacd785b",
"evidence": "",
"checkedAt": "2026-08-08T01:53:27.900Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "始终只有一份当前生效版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "0e715e18-3b4e-413e-9f2c-299a7aa37350, a3d54336-5c33-4458-b234-082298026520",
"traceId": "da1deab503934499b576ebeedacd785b",
"evidence": "active=2085907189707878402",
"checkedAt": "2026-08-08T01:53:27.901Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "上一生效版本已保留为历史版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "0e715e18-3b4e-413e-9f2c-299a7aa37350, a3d54336-5c33-4458-b234-082298026520",
"traceId": "da1deab503934499b576ebeedacd785b",
"evidence": "previous=2085661426268549121, superseded=true",
"checkedAt": "2026-08-08T01:53:27.901Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "查询当前生效价格版本详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books/2085907189707878402"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "75c1590f-8d33-43f6-bd24-2dd29436c45f, e5944f65-f645-4694-8ced-aef3f3c8b36d",
"traceId": "becb11887ecd41d6b81c441c807cdc8d",
"evidence": "",
"checkedAt": "2026-08-08T01:53:27.990Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "当前生效版本预览数据完整",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books/2085907189707878402"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "75c1590f-8d33-43f6-bd24-2dd29436c45f, e5944f65-f645-4694-8ced-aef3f3c8b36d",
"traceId": "becb11887ecd41d6b81c441c807cdc8d",
"evidence": "status=ACTIVE, channels=15, rates=2157",
"checkedAt": "2026-08-08T01:53:27.990Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "已生效版本禁止重复发布",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books/2085907189707878402/publish"
},
"http": 200,
"code": "A0409",
"message": "只有待确认上传记录可以确认并生效",
"requestId": "af0d78af-caac-4095-9164-6de76bf07c33",
"traceId": "00bbbdf631374c56ad0d965c6fdae0a3",
"evidence": "HTTP=200, code=A0409",
"checkedAt": "2026-08-08T01:53:28.027Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "重复发布被拒绝后回查版本状态",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "162da644-9186-445c-980f-6bd59800e362, 4cbd2911-2dfd-45e5-848d-50a574bc457a",
"traceId": "a1a2ed53cd2345e5a3150c577dd38693",
"evidence": "",
"checkedAt": "2026-08-08T01:53:28.059Z"
},
{
"module": "pricing",
"scenario": "price-book-lifecycle",
"name": "重复发布未改变唯一生效版本",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/fba-pricing/carrier-rates/price-books"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "162da644-9186-445c-980f-6bd59800e362, 4cbd2911-2dfd-45e5-848d-50a574bc457a",
"traceId": "a1a2ed53cd2345e5a3150c577dd38693",
"evidence": "active=2085907189707878402",
"checkedAt": "2026-08-08T01:53:28.059Z"
}
]
}
@@ -0,0 +1,740 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808015325966",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:53:27.032Z",
"startedAt": "2026-08-08T01:53:25.966Z",
"success": false,
"fatal": null,
"summary": {
"total": 38,
"passed": 36,
"failed": 2
},
"modules": [
{
"name": "customers",
"startedAt": "2026-08-08T01:53:26.382Z",
"finishedAt": "2026-08-08T01:53:27.032Z",
"success": false,
"passed": 35,
"failed": 2,
"fatal": null
}
],
"scenarios": [
{
"name": "customer-dimension-rules",
"module": "customers",
"success": true,
"deploymentBlocked": false,
"passed": 13,
"failed": 0
}
],
"deploymentBlockers": [],
"createdResources": {
"customer": {
"tenantId": "2085907201314639874",
"marker": "ADMIN-API-20260808015325966",
"contactPhone": "16154006382"
}
},
"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": "db634c73-67af-4c33-b3a7-69c7198b7e5c, f3c2e482-059b-49e1-b260-67caf7c7bcd5",
"traceId": "ba2aee8dce5c4fb380d3fed7297f9b15",
"evidence": "",
"checkedAt": "2026-08-08T01:53:26.381Z"
},
{
"module": "customers",
"scenario": null,
"name": "检查测试联系电话可用性",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/contact-phone-availability?contactPhone=16154006382"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "24e85948-fd9a-4f46-9e9f-1fc93b9a874e, d962cc30-8abe-4f40-9444-67665eaba6ab",
"traceId": "fc06afaa3d564789a5588244d4c7898c",
"evidence": "attempt=1",
"checkedAt": "2026-08-08T01:53:26.431Z"
},
{
"module": "customers",
"scenario": null,
"name": "创建客户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d3cbed21-b931-46ad-a48e-172162068cdd, a7962dde-3790-4391-a910-2ea5f3651443",
"traceId": "b5fc9f51280f412d8d9423e2bee3fb07",
"evidence": "",
"checkedAt": "2026-08-08T01:53:26.486Z"
},
{
"module": "customers",
"scenario": null,
"name": "创建响应客户字段完整",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d3cbed21-b931-46ad-a48e-172162068cdd, a7962dde-3790-4391-a910-2ea5f3651443",
"traceId": "b5fc9f51280f412d8d9423e2bee3fb07",
"evidence": "missing=none",
"checkedAt": "2026-08-08T01:53:26.486Z"
},
{
"module": "customers",
"scenario": null,
"name": "新客户基础字段正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d3cbed21-b931-46ad-a48e-172162068cdd, a7962dde-3790-4391-a910-2ea5f3651443",
"traceId": "b5fc9f51280f412d8d9423e2bee3fb07",
"evidence": "tenantId=2085907201314639874, status=PROFILE_REQUIRED",
"checkedAt": "2026-08-08T01:53:26.486Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户草稿不提前生成管理员凭据",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d3cbed21-b931-46ad-a48e-172162068cdd, a7962dde-3790-4391-a910-2ea5f3651443",
"traceId": "b5fc9f51280f412d8d9423e2bee3fb07",
"evidence": "temporaryPassword=omitted, administratorUsername=omitted",
"checkedAt": "2026-08-08T01:53:26.486Z"
},
{
"module": "customers",
"scenario": null,
"name": "待完善客户的审核字段保持为空",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d3cbed21-b931-46ad-a48e-172162068cdd, a7962dde-3790-4391-a910-2ea5f3651443",
"traceId": "b5fc9f51280f412d8d9423e2bee3fb07",
"evidence": "creditCode=omitted, approvedAt=omitted",
"checkedAt": "2026-08-08T01:53:26.486Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "新客户尺寸规则可查询",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f0c1e457-9f56-4bb8-ae5c-8921241530f4, afbf4496-ce98-42f7-8cfa-41845c360584",
"traceId": "4b76edbbac0444959b26be3748f77618",
"evidence": "tenantId=2085907201314639874",
"checkedAt": "2026-08-08T01:53:26.518Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "固定四箱规顺序完整且无重复",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f0c1e457-9f56-4bb8-ae5c-8921241530f4, afbf4496-ce98-42f7-8cfa-41845c360584",
"traceId": "4b76edbbac0444959b26be3748f77618",
"evidence": "count=4, order=SMALL,MEDIUM,LARGE,EXTRA_LARGE",
"checkedAt": "2026-08-08T01:53:26.518Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "四箱规默认内径、加边和备注正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f0c1e457-9f56-4bb8-ae5c-8921241530f4, afbf4496-ce98-42f7-8cfa-41845c360584",
"traceId": "4b76edbbac0444959b26be3748f77618",
"evidence": "defaults=matched",
"checkedAt": "2026-08-08T01:53:26.518Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "客户 ID、规则 ID 为字符串且初始版本为0",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f0c1e457-9f56-4bb8-ae5c-8921241530f4, afbf4496-ce98-42f7-8cfa-41845c360584",
"traceId": "4b76edbbac0444959b26be3748f77618",
"evidence": "groupTenantIdType=string, rows=string/v0,string/v0,string/v0,string/v0",
"checkedAt": "2026-08-08T01:53:26.518Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "默认规则外径与 HALF_UP 三位 CBM 正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f0c1e457-9f56-4bb8-ae5c-8921241530f4, afbf4496-ce98-42f7-8cfa-41845c360584",
"traceId": "4b76edbbac0444959b26be3748f77618",
"evidence": "derivedRows=4",
"checkedAt": "2026-08-08T01:53:26.518Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "PUT 每行只发送约定7字段并整组保存",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f0c1e457-9f56-4bb8-ae5c-8921241530f4, afbf4496-ce98-42f7-8cfa-41845c360584",
"traceId": "4b76edbbac0444959b26be3748f77618",
"evidence": "fields=boxType,innerLengthCm,innerWidthCm,innerHeightCm,edgeAdditionCm,remark,version",
"checkedAt": "2026-08-08T01:53:26.519Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "一次 PUT 保存四条三位小数规则",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "0c34bdcc-dbea-40c0-9f45-5fca1655ab13, b6a24c2e-f7f1-4a8c-9c5b-66ad69d982ef",
"traceId": "60653a35cef34af5b7a136ff0d8456e3",
"evidence": "rows=4",
"checkedAt": "2026-08-08T01:53:26.552Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "修改后四行版本+1且派生外径/CBM正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "0c34bdcc-dbea-40c0-9f45-5fca1655ab13, b6a24c2e-f7f1-4a8c-9c5b-66ad69d982ef",
"traceId": "60653a35cef34af5b7a136ff0d8456e3",
"evidence": "versions=1,1,1,1",
"checkedAt": "2026-08-08T01:53:26.553Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "旧 version 重放 PUT 返回冲突",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "A0409",
"message": "箱规尺寸配置已被修改,请刷新后重试",
"requestId": "0b861ca5-a2a4-4e85-b6ea-5dbe4f944db0",
"traceId": "46a446fe831e4c969e8c1ce3c02ea313",
"evidence": "HTTP=200, code=A0409",
"checkedAt": "2026-08-08T01:53:26.585Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "冲突后重新查询四条规则",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6c5f6611-d7c7-4a3c-a969-f7b5275bfea3, 55881a79-9266-4727-bf33-efbf77fcc3fb",
"traceId": "0f17b1e9109f4838a4696e789fec7072",
"evidence": "",
"checkedAt": "2026-08-08T01:53:26.617Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "冲突请求未造成四行部分更新",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6c5f6611-d7c7-4a3c-a969-f7b5275bfea3, 55881a79-9266-4727-bf33-efbf77fcc3fb",
"traceId": "0f17b1e9109f4838a4696e789fec7072",
"evidence": "unchangedRows=4",
"checkedAt": "2026-08-08T01:53:26.617Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "恢复本轮修改前的四箱规配置",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "65b35a20-b9de-4b29-abf8-05f4e2bde0e3, 5d75f480-c97d-4cae-a6c3-a031570fa7f6",
"traceId": "7604a277ba2a4d3dbaf453b4491983bc",
"evidence": "restore=original-values",
"checkedAt": "2026-08-08T01:53:26.669Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "尺寸规则恢复后回显与初始值一致",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "65b35a20-b9de-4b29-abf8-05f4e2bde0e3, 5d75f480-c97d-4cae-a6c3-a031570fa7f6",
"traceId": "7604a277ba2a4d3dbaf453b4491983bc",
"evidence": "restoredRows=4",
"checkedAt": "2026-08-08T01:53:26.669Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants?page=1&size=20&keyword=%E5%90%8E%E5%8F%B0%E5%AE%A2%E6%88%B7%E9%AA%8C%E6%94%B6-808015325966"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d3d89c7c-60aa-466e-8427-df5023fc9be1, 9d2528ed-3b66-44b6-9312-fd825c0f6c7b",
"traceId": "0791dc3ecbd74e5ea570583924d43914",
"evidence": "",
"checkedAt": "2026-08-08T01:53:26.707Z"
},
{
"module": "customers",
"scenario": null,
"name": "列表包含新客户且字段完整",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants?page=1&size=20&keyword=%E5%90%8E%E5%8F%B0%E5%AE%A2%E6%88%B7%E9%AA%8C%E6%94%B6-808015325966"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d3d89c7c-60aa-466e-8427-df5023fc9be1, 9d2528ed-3b66-44b6-9312-fd825c0f6c7b",
"traceId": "0791dc3ecbd74e5ea570583924d43914",
"evidence": "found=true, missing=none",
"checkedAt": "2026-08-08T01:53:26.707Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c2b214e1-d5fd-4df1-85e5-dcb2604fca29, a5cc615b-ae7e-44eb-a394-d80cc215788f",
"traceId": "58f87b8dad654f34a253ca6169a63268",
"evidence": "",
"checkedAt": "2026-08-08T01:53:26.755Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户详情基本信息正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c2b214e1-d5fd-4df1-85e5-dcb2604fca29, a5cc615b-ae7e-44eb-a394-d80cc215788f",
"traceId": "58f87b8dad654f34a253ca6169a63268",
"evidence": "tenantId=2085907201314639874",
"checkedAt": "2026-08-08T01:53:26.756Z"
},
{
"module": "customers",
"scenario": null,
"name": "编辑客户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2c6d2069-c5ae-4227-a88b-825130a052e8, 60cfcb3c-a8c8-4fe0-8168-0ff601c94e42",
"traceId": "56cb0abf70d747eaa7ef0d77b2bcfa39",
"evidence": "",
"checkedAt": "2026-08-08T01:53:26.802Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2c6d2069-c5ae-4227-a88b-825130a052e8, 60cfcb3c-a8c8-4fe0-8168-0ff601c94e42",
"traceId": "56cb0abf70d747eaa7ef0d77b2bcfa39",
"evidence": "tenantId=2085907201314639874",
"checkedAt": "2026-08-08T01:53:26.802Z"
},
{
"module": "customers",
"scenario": null,
"name": "回查客户编辑结果",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "cc7f4674-b902-4f71-8f33-a5afdf1c1744, ca7a07c2-ba52-4996-a09c-6208704de74b",
"traceId": "af1b6e53c8aa43ac98028aa314a2f6e9",
"evidence": "",
"checkedAt": "2026-08-08T01:53:26.826Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户编辑已持久化",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "cc7f4674-b902-4f71-8f33-a5afdf1c1744, ca7a07c2-ba52-4996-a09c-6208704de74b",
"traceId": "af1b6e53c8aa43ac98028aa314a2f6e9",
"evidence": "tenantId=2085907201314639874",
"checkedAt": "2026-08-08T01:53:26.826Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询资质",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085907201314639874/qualifications"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "124f0e8b-c64d-42f1-a697-ab385d852aed, 195e34f3-1253-4d72-87b6-d38c23652de1",
"traceId": "7c7fa61561f84f0d9d5d415a72202849",
"evidence": "",
"checkedAt": "2026-08-08T01:53:26.849Z"
},
{
"module": "customers",
"scenario": null,
"name": "资质概况结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085907201314639874/qualifications"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "124f0e8b-c64d-42f1-a697-ab385d852aed, 195e34f3-1253-4d72-87b6-d38c23652de1",
"traceId": "7c7fa61561f84f0d9d5d415a72202849",
"evidence": "count=0",
"checkedAt": "2026-08-08T01:53:26.849Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询成员",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085907201314639874/members"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3748f7f8-766a-4746-a9d9-9340e7737bb2, 4b0269c1-29de-4a08-9f18-e0daacafbceb",
"traceId": "75a180aa950f4c96963dd0527ad5c073",
"evidence": "",
"checkedAt": "2026-08-08T01:53:26.893Z"
},
{
"module": "customers",
"scenario": null,
"name": "待完善客户尚未生成企业管理员",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085907201314639874/members"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3748f7f8-766a-4746-a9d9-9340e7737bb2, 4b0269c1-29de-4a08-9f18-e0daacafbceb",
"traceId": "75a180aa950f4c96963dd0527ad5c073",
"evidence": "members=0",
"checkedAt": "2026-08-08T01:53:26.894Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询运单数量",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/tenant-counts?tenantIds=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "57045150-9185-4fbe-bdfb-eb25f0b910c0, 8ac72add-0902-4975-a7af-bf98772f8117",
"traceId": "2aac73b5c3fa48d18a2a9bbaf46f2d40",
"evidence": "",
"checkedAt": "2026-08-08T01:53:26.933Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况返回当前客户运单数",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/tenant-counts?tenantIds=2085907201314639874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "57045150-9185-4fbe-bdfb-eb25f0b910c0, 8ac72add-0902-4975-a7af-bf98772f8117",
"traceId": "2aac73b5c3fa48d18a2a9bbaf46f2d40",
"evidence": "orderCount=0",
"checkedAt": "2026-08-08T01:53:26.933Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户运单记录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=2085907201314639874&page=1&size=10"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d48f448d-352d-43bd-89b0-51d8372ea9b6, ad3eb7be-0a5a-436d-95a1-d7795713eef6",
"traceId": "6056d078fa394c18a3e2c7fec4bfadca",
"evidence": "",
"checkedAt": "2026-08-08T01:53:26.973Z"
},
{
"module": "customers",
"scenario": null,
"name": "运单记录分页结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=2085907201314639874&page=1&size=10"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "d48f448d-352d-43bd-89b0-51d8372ea9b6, ad3eb7be-0a5a-436d-95a1-d7795713eef6",
"traceId": "6056d078fa394c18a3e2c7fec4bfadca",
"evidence": "records=0, total=0",
"checkedAt": "2026-08-08T01:53:26.973Z"
},
{
"module": "customers",
"scenario": null,
"name": "归档测试客户",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants/2085907201314639874/archive"
},
"http": 200,
"code": "S0500",
"message": "Internal server error",
"requestId": "e35a256c-67a2-4146-87b4-294e0ad6a872",
"traceId": "135e1ad7b73d4d669db31ac1738dd3a2",
"evidence": "",
"checkedAt": "2026-08-08T01:53:27.032Z"
},
{
"module": "customers",
"scenario": null,
"name": "测试客户归档后处于停用状态",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants/2085907201314639874/archive"
},
"http": 200,
"code": "S0500",
"message": "Internal server error",
"requestId": "e35a256c-67a2-4146-87b4-294e0ad6a872",
"traceId": "135e1ad7b73d4d669db31ac1738dd3a2",
"evidence": "status=missing",
"checkedAt": "2026-08-08T01:53:27.032Z"
}
]
}
@@ -0,0 +1,83 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808015327235",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:53:27.783Z",
"startedAt": "2026-08-08T01:53:27.235Z",
"success": false,
"fatal": null,
"summary": {
"total": 2,
"passed": 1,
"failed": 1
},
"modules": [
{
"name": "inbound-weight-ocr",
"startedAt": "2026-08-08T01:53:27.684Z",
"finishedAt": "2026-08-08T01:53:27.783Z",
"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": [],
"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": "b97739c1-555c-45a8-a9df-a11fac175b3d, 5d5a2199-d959-442e-8267-b277285b38b3",
"traceId": "0dc9c24b611a4a67b04e478be3171572",
"evidence": "",
"checkedAt": "2026-08-08T01:53:27.681Z"
},
{
"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-20260808015327235, 未找到本轮创建且状态为 WAIT_MEASURE/MEASURING/MEASURE_REJECTED 的入库单",
"checkedAt": "2026-08-08T01:53:27.783Z"
}
]
}
@@ -0,0 +1,932 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808015845602",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:58:48.416Z",
"startedAt": "2026-08-08T01:58:45.602Z",
"success": false,
"fatal": null,
"summary": {
"total": 48,
"passed": 46,
"failed": 2
},
"modules": [
{
"name": "orders",
"startedAt": "2026-08-08T01:58:45.972Z",
"finishedAt": "2026-08-08T01:58:48.416Z",
"success": false,
"passed": 45,
"failed": 2,
"fatal": null
}
],
"scenarios": [],
"deploymentBlockers": [],
"createdResources": {
"orders": {
"marker": "ADMIN-API-20260808015845602",
"tenantId": "10001",
"orderId": "2085908544302415874",
"productImageFileId": "2085908543895568386",
"boxTypes": [
"SMALL",
"MEDIUM",
"LARGE",
"EXTRA_LARGE"
],
"inboundId": "2085908549518012417",
"orderNo": "FL2085908544302415874",
"inboundCode": "RK-2085908544302415874",
"processStatus": "WAIT_MEASURE"
},
"ocrInbound": {
"tenantId": "10001",
"inboundId": "2085908549518012417",
"marker": "ADMIN-API-20260808015845602"
},
"testInbound": {
"tenantId": "10001",
"inboundId": "2085908549518012417",
"marker": "ADMIN-API-20260808015845602"
}
},
"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": "2df00281-3bc2-448b-a0e7-7b2d8a3686c0, 29ecd91d-f58f-4816-9571-5bc9316016f7",
"traceId": "e964f0bee2dc41a9b2819394c7a40869",
"evidence": "",
"checkedAt": "2026-08-08T01:58:45.971Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 测试账号登录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/auth/login"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9bafe061-2875-4edf-b88e-2b07a9f1dd5c, 88a6b4cc-15bb-42ca-84e2-155d3dd02a59",
"traceId": "32698d4d352b43b8b33385327d62459b",
"evidence": "username=merchant_admin",
"checkedAt": "2026-08-08T01:58:46.317Z"
},
{
"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": "f0319880-b18c-4c74-a8aa-d9b4ebfd3139, 2949ef4f-8049-4b02-bed6-9c85be09abdb",
"traceId": "0d4f1aef979043feb4fa7e12ed33d8e0",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.344Z"
},
{
"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": "f0319880-b18c-4c74-a8aa-d9b4ebfd3139, 2949ef4f-8049-4b02-bed6-9c85be09abdb",
"traceId": "0d4f1aef979043feb4fa7e12ed33d8e0",
"evidence": "tenantId=10001, status=ACTIVE",
"checkedAt": "2026-08-08T01:58:46.344Z"
},
{
"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": "f5b956ab-635f-4d5a-b5f4-c6e06a99ea11, f3855650-244a-42a6-b738-cd302bb9ce65",
"traceId": "c918eedc616c4d9ebd1e1f8f3ece807f",
"evidence": "tenantId=10001",
"checkedAt": "2026-08-08T01:58:46.378Z"
},
{
"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": "f5b956ab-635f-4d5a-b5f4-c6e06a99ea11, f3855650-244a-42a6-b738-cd302bb9ce65",
"traceId": "c918eedc616c4d9ebd1e1f8f3ece807f",
"evidence": "types=SMALL,MEDIUM,LARGE,EXTRA_LARGE",
"checkedAt": "2026-08-08T01:58:46.378Z"
},
{
"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": "2b7b22b6-705b-4e6d-8ffb-e3ae3bea9e86, d0fb6ae1-e618-4432-b9d4-a58a09da32c7",
"traceId": "56f1f1300a144c7b9c90c8824eff5899",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.412Z"
},
{
"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": "2b7b22b6-705b-4e6d-8ffb-e3ae3bea9e86, d0fb6ae1-e618-4432-b9d4-a58a09da32c7",
"traceId": "56f1f1300a144c7b9c90c8824eff5899",
"evidence": "count=2",
"checkedAt": "2026-08-08T01:58:46.412Z"
},
{
"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": "45045704-2cd0-4403-be53-293bc5f26e72, 5bfc6053-ac43-4c9c-83ad-b326b083ebd2",
"traceId": "b5775339f5cf44549606038c716326bb",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.464Z"
},
{
"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": "45045704-2cd0-4403-be53-293bc5f26e72, 5bfc6053-ac43-4c9c-83ad-b326b083ebd2",
"traceId": "b5775339f5cf44549606038c716326bb",
"evidence": "count=200",
"checkedAt": "2026-08-08T01:58:46.464Z"
},
{
"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": "79f6d7f0-f634-4282-b9c0-a0167dba8d8d, e9a9e0a1-a9e3-4fe7-8dda-321b25706f74",
"traceId": "4d91cee3c9a546489ca856683c1e3866",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.500Z"
},
{
"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": "79f6d7f0-f634-4282-b9c0-a0167dba8d8d, e9a9e0a1-a9e3-4fe7-8dda-321b25706f74",
"traceId": "4d91cee3c9a546489ca856683c1e3866",
"evidence": "count=15",
"checkedAt": "2026-08-08T01:58:46.501Z"
},
{
"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": "151cabba-06d0-47c1-ae7a-e9c04a53bb99, df6dc290-eb32-4e1a-871e-24b997e3be8e",
"traceId": "3d56dc4845224ed6a960d0941afa2437",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.532Z"
},
{
"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": "84e5df0a-1165-4616-9fea-e126b773b012, e5625c74-fae1-4156-843c-b302789abd8d",
"traceId": "f4dc865591b74b50b2fb216a3a6eb7e8",
"evidence": "contentType=image/png",
"checkedAt": "2026-08-08T01:58:46.563Z"
},
{
"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": "84e5df0a-1165-4616-9fea-e126b773b012, e5625c74-fae1-4156-843c-b302789abd8d",
"traceId": "f4dc865591b74b50b2fb216a3a6eb7e8",
"evidence": "fileIdType=string, contentType=image/png",
"checkedAt": "2026-08-08T01:58:46.563Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "Client 可下载本轮产品图片",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/files/product-images/2085908543895568386"
},
"http": 200,
"code": null,
"message": null,
"requestId": "6fb2eb7d-e6c0-465e-85ba-80ceee864afe, 9064fb39-dd42-4a98-acd6-17f395903625",
"traceId": "647a5fda1fcd4efba4b597718e8b6577",
"evidence": "bytes=68, contentType=image/png",
"checkedAt": "2026-08-08T01:58:46.595Z"
},
{
"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": "45a345d8-d836-47ec-af79-03da6448ddb3, ae795305-805a-4361-b15f-769f02211433",
"traceId": "6a6671c65031457885d3140d9ddea7b0",
"evidence": "marker=ADMIN-API-20260808015845602",
"checkedAt": "2026-08-08T01:58:46.665Z"
},
{
"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": "1cbf94cf-898c-450c-bda1-aff27188791a, fdf7c425-ac45-495c-afc2-f3c0af3c2e64",
"traceId": "e4fc664072c449f5bcdd3091b9e0826c",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.711Z"
},
{
"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": "1cbf94cf-898c-450c-bda1-aff27188791a, fdf7c425-ac45-495c-afc2-f3c0af3c2e64",
"traceId": "e4fc664072c449f5bcdd3091b9e0826c",
"evidence": "orderId=2085908544302415874, replayedId=2085908544302415874",
"checkedAt": "2026-08-08T01:58:46.711Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 查询隔离订单草稿",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/orders/first-leg/2085908544302415874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9a744a35-40ac-4f27-b0b6-e88a05148da7, 307ebeb3-91ea-4d0a-aced-6d1a8c54f2e5",
"traceId": "5ca1918bae824baf8a5e9f4c941b1187",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.750Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 草稿身份、marker 和状态正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/orders/first-leg/2085908544302415874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9a744a35-40ac-4f27-b0b6-e88a05148da7, 307ebeb3-91ea-4d0a-aced-6d1a8c54f2e5",
"traceId": "5ca1918bae824baf8a5e9f4c941b1187",
"evidence": "orderNo=FL2085908544302415874, status=DRAFT",
"checkedAt": "2026-08-08T01:58:46.751Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "四箱型产品字段及图片契约完整回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/orders/first-leg/2085908544302415874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9a744a35-40ac-4f27-b0b6-e88a05148da7, 307ebeb3-91ea-4d0a-aced-6d1a8c54f2e5",
"traceId": "5ca1918bae824baf8a5e9f4c941b1187",
"evidence": "cargoes=4, imageFileId=2085908543895568386",
"checkedAt": "2026-08-08T01:58:46.751Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 提交隔离订单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/merchant/v1/orders/first-leg/2085908544302415874/submit"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2ddeae60-09f4-46f4-ba95-0da918cae5a1, 1c99eaa3-59c8-44c4-be1f-d8032cd7a5c8",
"traceId": "77a5f60280ca457f8342b0bcd69f9b3c",
"evidence": "orderId=2085908544302415874",
"checkedAt": "2026-08-08T01:58:46.815Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 回查已提交订单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/orders/first-leg/2085908544302415874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2a7d9c8a-e5e3-4e0f-9ad1-38d8448f7ba7, f719ea5c-96ee-43ae-a7e0-b7bbec868a91",
"traceId": "3ad7ca4193654a048d4b7176bb940316",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.844Z"
},
{
"module": "orders",
"scenario": null,
"name": "Client 订单已进入待审核状态",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/merchant/v1/orders/first-leg/2085908544302415874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "2a7d9c8a-e5e3-4e0f-9ad1-38d8448f7ba7, f719ea5c-96ee-43ae-a7e0-b7bbec868a91",
"traceId": "3ad7ca4193654a048d4b7176bb940316",
"evidence": "status=SUBMITTED",
"checkedAt": "2026-08-08T01:58:46.844Z"
},
{
"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=FL2085908544302415874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "90b72878-5370-4bda-93a7-fad6b786f9a1, 94f8b715-4da1-481f-9e33-0e68b0164d4e",
"traceId": "142e51a32c114f25a5bb5bc2f27e20e4",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.884Z"
},
{
"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=FL2085908544302415874"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "90b72878-5370-4bda-93a7-fad6b786f9a1, 94f8b715-4da1-481f-9e33-0e68b0164d4e",
"traceId": "142e51a32c114f25a5bb5bc2f27e20e4",
"evidence": "orderId=2085908544302415874, found=true",
"checkedAt": "2026-08-08T01:58:46.884Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 查询统一订单详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/2085908544302415874?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ab453fcc-d6dc-4692-aac7-71aa232700ed, 16fe95a7-1ea1-4505-b3a5-1dfd5a5ffe07",
"traceId": "9dcddfefa35d4f7f91cd6d6f274a8e3f",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.927Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 统一订单详情身份与待审核状态正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/2085908544302415874?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "ab453fcc-d6dc-4692-aac7-71aa232700ed, 16fe95a7-1ea1-4505-b3a5-1dfd5a5ffe07",
"traceId": "9dcddfefa35d4f7f91cd6d6f274a8e3f",
"evidence": "mainStatus=PENDING_REVIEW, timeline=2",
"checkedAt": "2026-08-08T01:58:46.927Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 查询头程订单列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=10001&keyword=FL2085908544302415874&page=1&size=100"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9fe027f9-daf9-4524-b630-15c8e33f8f5a, 1650d83d-e4b4-4b5a-8712-3f896b0a40bc",
"traceId": "b6832bb6eae644ad976303bd062b778f",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.957Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 头程订单列表只核对本轮订单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=10001&keyword=FL2085908544302415874&page=1&size=100"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9fe027f9-daf9-4524-b630-15c8e33f8f5a, 1650d83d-e4b4-4b5a-8712-3f896b0a40bc",
"traceId": "b6832bb6eae644ad976303bd062b778f",
"evidence": "orderId=2085908544302415874, found=true",
"checkedAt": "2026-08-08T01:58:46.957Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 查询头程订单详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/2085908544302415874?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "fc22ec0a-1b44-4407-abd4-02b0998ce3c4, bcb367cf-09ee-4856-ae76-e6c8a04ad0a5",
"traceId": "9a683d10e9b0495f8229336e44874e38",
"evidence": "",
"checkedAt": "2026-08-08T01:58:46.985Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 头程订单详情聚合结构完整",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/2085908544302415874?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "fc22ec0a-1b44-4407-abd4-02b0998ce3c4, bcb367cf-09ee-4856-ae76-e6c8a04ad0a5",
"traceId": "9a683d10e9b0495f8229336e44874e38",
"evidence": "cargoes=4, history=2",
"checkedAt": "2026-08-08T01:58:46.985Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "四箱型产品字段及图片契约完整回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/2085908544302415874?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "fc22ec0a-1b44-4407-abd4-02b0998ce3c4, bcb367cf-09ee-4856-ae76-e6c8a04ad0a5",
"traceId": "9a683d10e9b0495f8229336e44874e38",
"evidence": "cargoes=4, imageFileId=2085908543895568386",
"checkedAt": "2026-08-08T01:58:46.985Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 为本轮订单添加内部备注",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/orders/first-leg/2085908544302415874/internal-notes?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c93d3ee3-bf0f-411b-846f-667cccd1a99a, cc8111a2-41fb-487d-bd6d-1ab0293f45f1",
"traceId": "2761a0f12b804b97b422df9ce2296e25",
"evidence": "",
"checkedAt": "2026-08-08T01:58:47.021Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 审核通过本轮订单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/orders/first-leg/2085908544302415874/approve?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3786268b-5995-4700-a25e-5d9ec2d76028, d6ff05db-ba34-4479-b74a-3938b5c560ac",
"traceId": "8f34aa7fc14045eaa94d672d1a77addd",
"evidence": "orderId=2085908544302415874",
"checkedAt": "2026-08-08T01:58:47.068Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "审核后生成隔离入库单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/2085908544302415874?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "23f20a88-9f4c-4580-9522-346a525f61a0, 3bb76bd9-c6f1-4319-b2e3-006f3e7b6933",
"traceId": "2020d9c73ccd4e8ea1c22dc1a66d5af4",
"evidence": "inboundId=2085908549518012417",
"checkedAt": "2026-08-08T01:58:48.176Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "Admin 查询本轮入库单详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/inbounds/2085908549518012417?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3bf50e55-ab10-4da5-84f1-ed4c6a8218f3, 1bb3971c-1ca4-43fd-bf4f-df29c69daf29",
"traceId": "8df76fb788f44bcaa521ca3623caf22c",
"evidence": "inboundId=2085908549518012417",
"checkedAt": "2026-08-08T01:58:48.212Z"
},
{
"module": "orders",
"scenario": "isolated-order-chain",
"name": "隔离入库单 marker 来源与可测量状态正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/inbounds/2085908549518012417?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3bf50e55-ab10-4da5-84f1-ed4c6a8218f3, 1bb3971c-1ca4-43fd-bf4f-df29c69daf29",
"traceId": "8df76fb788f44bcaa521ca3623caf22c",
"evidence": "markerFound=true, status=WAIT_MEASURE",
"checkedAt": "2026-08-08T01:58:48.212Z"
},
{
"module": "orders",
"scenario": null,
"name": "入库单详情包含托盘库位、文件、备注和状态历史",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/inbounds/2085908549518012417?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3bf50e55-ab10-4da5-84f1-ed4c6a8218f3, 1bb3971c-1ca4-43fd-bf4f-df29c69daf29",
"traceId": "8df76fb788f44bcaa521ca3623caf22c",
"evidence": "inboundId=2085908549518012417, history=1",
"checkedAt": "2026-08-08T01:58:48.212Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 查询入库单列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/inbounds?tenantId=10001&inboundCode=RK-2085908544302415874&page=1&size=100"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "77756604-d418-4061-8bb0-4c3e9f1ff147, 05844fd5-68b6-41bc-b63d-f238c7a6fa50",
"traceId": "c356efe5a4b349fa8624a956bb4a545f",
"evidence": "",
"checkedAt": "2026-08-08T01:58:48.250Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 入库单列表只核对本轮入库单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/inbounds?tenantId=10001&inboundCode=RK-2085908544302415874&page=1&size=100"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "77756604-d418-4061-8bb0-4c3e9f1ff147, 05844fd5-68b6-41bc-b63d-f238c7a6fa50",
"traceId": "c356efe5a4b349fa8624a956bb4a545f",
"evidence": "inboundId=2085908549518012417, total=1",
"checkedAt": "2026-08-08T01:58:48.251Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 查询本轮订单履约任务",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/tasks/by-order/2085908544302415874?tenantId=10001"
},
"http": 200,
"code": "A0404",
"message": "头程履约任务不存在",
"requestId": "dcede4ed-e6e3-4037-a5b3-8b5a2b41b447",
"traceId": "689fe50d1c4140debc77cb9c358dff84",
"evidence": "",
"checkedAt": "2026-08-08T01:58:48.287Z"
},
{
"module": "orders",
"scenario": null,
"name": "履约任务与本轮订单和客户一致",
"passed": false,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/tasks/by-order/2085908544302415874?tenantId=10001"
},
"http": 200,
"code": "A0404",
"message": "头程履约任务不存在",
"requestId": "dcede4ed-e6e3-4037-a5b3-8b5a2b41b447",
"traceId": "689fe50d1c4140debc77cb9c358dff84",
"evidence": "taskId=missing, status=missing",
"checkedAt": "2026-08-08T01:58:48.287Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 受理本轮订单",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/orders/first-leg/2085908544302415874/accept?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "fcaf6403-0b07-45ab-aea0-0060bef0d8a1, 56cb5483-3a9b-4cc2-b36f-941caf68892c",
"traceId": "c807bc35c9d548de905c0fb365d43f8a",
"evidence": "",
"checkedAt": "2026-08-08T01:58:48.328Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 启动本轮订单履约",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/orders/first-leg/2085908544302415874/start?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "8fc7773f-c7fa-45b1-94e9-214d456cd01b, 50b6afb8-5586-4d33-993c-f5d4914c591b",
"traceId": "3e4a441b3683484e80228080f3b3aa53",
"evidence": "",
"checkedAt": "2026-08-08T01:58:48.374Z"
},
{
"module": "orders",
"scenario": null,
"name": "Admin 回查本轮订单履约状态",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/2085908544302415874?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "be32f8d8-9186-4350-a025-e7acd0ba707b, 3fc8db0b-dc64-40fe-a67c-e82b5103dd28",
"traceId": "7cdaeb99beb64aec8290800701610733",
"evidence": "",
"checkedAt": "2026-08-08T01:58:48.416Z"
},
{
"module": "orders",
"scenario": null,
"name": "本轮订单已进入处理中且保留状态历史",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/2085908544302415874?tenantId=10001"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "be32f8d8-9186-4350-a025-e7acd0ba707b, 3fc8db0b-dc64-40fe-a67c-e82b5103dd28",
"traceId": "7cdaeb99beb64aec8290800701610733",
"evidence": "status=PROCESSING, history=5",
"checkedAt": "2026-08-08T01:58:48.416Z"
}
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,777 @@
{
"reportName": "后台管理测试环境 HTTP API 验收",
"marker": "ADMIN-API-20260808020600398",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T02:06:01.284Z",
"startedAt": "2026-08-08T02:06:00.398Z",
"success": false,
"fatal": null,
"summary": {
"total": 38,
"passed": 34,
"failed": 4
},
"modules": [
{
"name": "customers",
"startedAt": "2026-08-08T02:06:00.764Z",
"finishedAt": "2026-08-08T02:06:01.284Z",
"success": false,
"passed": 33,
"failed": 4,
"fatal": null
}
],
"scenarios": [
{
"name": "customer-dimension-rules",
"module": "customers",
"success": true,
"deploymentBlocked": false,
"passed": 13,
"failed": 0
}
],
"deploymentBlockers": [
{
"module": "customers",
"scenario": "customer-owner-provisioning",
"name": "创建客户生成初始密码",
"evidence": "generated=false, length=0",
"code": "200",
"requestId": "1f863aad-18a9-4bd0-903d-54b6137a1e97, 5cde4d63-7324-42c5-8302-73f092b285e8",
"traceId": "4675d04359f7429b99de4d6ff4582045"
},
{
"module": "customers",
"scenario": "customer-owner-provisioning",
"name": "管理员用户名默认使用联系电话",
"evidence": "matches=false",
"code": "200",
"requestId": "1f863aad-18a9-4bd0-903d-54b6137a1e97, 5cde4d63-7324-42c5-8302-73f092b285e8",
"traceId": "4675d04359f7429b99de4d6ff4582045"
},
{
"module": "customers",
"scenario": "customer-owner-provisioning",
"name": "联系电话账号是企业管理员",
"evidence": "ownerFound=false, matches=false",
"code": "200",
"requestId": "4e38b00f-9877-448f-9c54-d7a4a604bf79, 6261cac3-bf0d-4e9c-81f5-19edb4a89bec",
"traceId": "261ac37d19d94fa0a6108d65333c84c6"
},
{
"module": "customers",
"scenario": "customer-draft-archive",
"name": "归档测试客户",
"evidence": "tenantId=2085910365380136962",
"code": "S0500",
"requestId": "d7382957-3ac8-44a5-bf1d-fb10394f2181",
"traceId": "33ede0d091f644a283c541e91aa0ab65"
}
],
"createdResources": {
"customer": {
"tenantId": "2085910365380136962",
"marker": "ADMIN-API-20260808020600398",
"contactPhone": "16154760765"
}
},
"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": "4c559599-241d-4e9d-a8cd-717f333037b8, 6c7f9a77-889f-484f-b39a-b6755a68779f",
"traceId": "3765d7a68a1d4d85bab38362c14f15c2",
"evidence": "",
"checkedAt": "2026-08-08T02:06:00.764Z"
},
{
"module": "customers",
"scenario": null,
"name": "检查测试联系电话可用性",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/contact-phone-availability?contactPhone=16154760765"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "32c4883f-a91f-4212-a0cd-269c521726a0, ab347d17-f86f-43b3-b904-6c45cb78dcf9",
"traceId": "de191475b4d84f88998330c796c56d1a",
"evidence": "attempt=1",
"checkedAt": "2026-08-08T02:06:00.806Z"
},
{
"module": "customers",
"scenario": null,
"name": "创建客户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1f863aad-18a9-4bd0-903d-54b6137a1e97, 5cde4d63-7324-42c5-8302-73f092b285e8",
"traceId": "4675d04359f7429b99de4d6ff4582045",
"evidence": "",
"checkedAt": "2026-08-08T02:06:00.850Z"
},
{
"module": "customers",
"scenario": null,
"name": "创建响应客户字段完整",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1f863aad-18a9-4bd0-903d-54b6137a1e97, 5cde4d63-7324-42c5-8302-73f092b285e8",
"traceId": "4675d04359f7429b99de4d6ff4582045",
"evidence": "missing=none",
"checkedAt": "2026-08-08T02:06:00.850Z"
},
{
"module": "customers",
"scenario": null,
"name": "新客户基础字段正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1f863aad-18a9-4bd0-903d-54b6137a1e97, 5cde4d63-7324-42c5-8302-73f092b285e8",
"traceId": "4675d04359f7429b99de4d6ff4582045",
"evidence": "tenantId=2085910365380136962, status=PROFILE_REQUIRED",
"checkedAt": "2026-08-08T02:06:00.850Z"
},
{
"module": "customers",
"scenario": "customer-owner-provisioning",
"name": "创建客户生成初始密码",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1f863aad-18a9-4bd0-903d-54b6137a1e97, 5cde4d63-7324-42c5-8302-73f092b285e8",
"traceId": "4675d04359f7429b99de4d6ff4582045",
"evidence": "generated=false, length=0",
"checkedAt": "2026-08-08T02:06:00.850Z"
},
{
"module": "customers",
"scenario": "customer-owner-provisioning",
"name": "管理员用户名默认使用联系电话",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1f863aad-18a9-4bd0-903d-54b6137a1e97, 5cde4d63-7324-42c5-8302-73f092b285e8",
"traceId": "4675d04359f7429b99de4d6ff4582045",
"evidence": "matches=false",
"checkedAt": "2026-08-08T02:06:00.850Z"
},
{
"module": "customers",
"scenario": null,
"name": "待完善客户的审核字段保持为空",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "1f863aad-18a9-4bd0-903d-54b6137a1e97, 5cde4d63-7324-42c5-8302-73f092b285e8",
"traceId": "4675d04359f7429b99de4d6ff4582045",
"evidence": "creditCode=omitted, approvedAt=omitted",
"checkedAt": "2026-08-08T02:06:00.850Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "新客户尺寸规则可查询",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c2b20bbb-3ca8-43c2-8eb1-3dbb47fc71de, 87759826-5c05-4ff9-8567-c708ee171a80",
"traceId": "a1535addd49848878e84957c42d65e7c",
"evidence": "tenantId=2085910365380136962",
"checkedAt": "2026-08-08T02:06:00.882Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "固定四箱规顺序完整且无重复",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c2b20bbb-3ca8-43c2-8eb1-3dbb47fc71de, 87759826-5c05-4ff9-8567-c708ee171a80",
"traceId": "a1535addd49848878e84957c42d65e7c",
"evidence": "count=4, order=SMALL,MEDIUM,LARGE,EXTRA_LARGE",
"checkedAt": "2026-08-08T02:06:00.882Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "四箱规默认内径、加边和备注正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c2b20bbb-3ca8-43c2-8eb1-3dbb47fc71de, 87759826-5c05-4ff9-8567-c708ee171a80",
"traceId": "a1535addd49848878e84957c42d65e7c",
"evidence": "defaults=matched",
"checkedAt": "2026-08-08T02:06:00.882Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "客户 ID、规则 ID 为字符串且初始版本为0",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c2b20bbb-3ca8-43c2-8eb1-3dbb47fc71de, 87759826-5c05-4ff9-8567-c708ee171a80",
"traceId": "a1535addd49848878e84957c42d65e7c",
"evidence": "groupTenantIdType=string, rows=string/v0,string/v0,string/v0,string/v0",
"checkedAt": "2026-08-08T02:06:00.882Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "默认规则外径与 HALF_UP 三位 CBM 正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c2b20bbb-3ca8-43c2-8eb1-3dbb47fc71de, 87759826-5c05-4ff9-8567-c708ee171a80",
"traceId": "a1535addd49848878e84957c42d65e7c",
"evidence": "derivedRows=4",
"checkedAt": "2026-08-08T02:06:00.882Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "PUT 每行只发送约定7字段并整组保存",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "c2b20bbb-3ca8-43c2-8eb1-3dbb47fc71de, 87759826-5c05-4ff9-8567-c708ee171a80",
"traceId": "a1535addd49848878e84957c42d65e7c",
"evidence": "fields=boxType,innerLengthCm,innerWidthCm,innerHeightCm,edgeAdditionCm,remark,version",
"checkedAt": "2026-08-08T02:06:00.882Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "一次 PUT 保存四条三位小数规则",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9448a113-33b2-474b-b2f5-6d4a9ab65acc, f36a1a2b-c50d-4035-9abc-7820481390f5",
"traceId": "ad86761022ae47e98793789acda99f56",
"evidence": "rows=4",
"checkedAt": "2026-08-08T02:06:00.919Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "修改后四行版本+1且派生外径/CBM正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9448a113-33b2-474b-b2f5-6d4a9ab65acc, f36a1a2b-c50d-4035-9abc-7820481390f5",
"traceId": "ad86761022ae47e98793789acda99f56",
"evidence": "versions=1,1,1,1",
"checkedAt": "2026-08-08T02:06:00.919Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "旧 version 重放 PUT 返回冲突",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "A0409",
"message": "箱规尺寸配置已被修改,请刷新后重试",
"requestId": "afa9f40a-9b2b-4f70-a788-454d762dc5da",
"traceId": "56c946c77a5b4befb42727cfe28e5710",
"evidence": "HTTP=200, code=A0409",
"checkedAt": "2026-08-08T02:06:00.943Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "冲突后重新查询四条规则",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f18c251b-bbda-4cb2-b6f6-fd826709a1eb, 70013868-202c-4147-b8a3-68426a4e9d63",
"traceId": "29a534c8a04d462e87f979f04fbccbe5",
"evidence": "",
"checkedAt": "2026-08-08T02:06:00.975Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "冲突请求未造成四行部分更新",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "f18c251b-bbda-4cb2-b6f6-fd826709a1eb, 70013868-202c-4147-b8a3-68426a4e9d63",
"traceId": "29a534c8a04d462e87f979f04fbccbe5",
"evidence": "unchangedRows=4",
"checkedAt": "2026-08-08T02:06:00.975Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "恢复本轮修改前的四箱规配置",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "31537a8d-cd3e-403d-9ff3-52e722046fa2, add8c74d-b88a-42d9-9d2c-75dd4dc09064",
"traceId": "9e8f45ceebff4cefb032b1f754542797",
"evidence": "restore=original-values",
"checkedAt": "2026-08-08T02:06:01.011Z"
},
{
"module": "customers",
"scenario": "customer-dimension-rules",
"name": "尺寸规则恢复后回显与初始值一致",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/first-leg/customer-dimension-config?tenantId=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "31537a8d-cd3e-403d-9ff3-52e722046fa2, add8c74d-b88a-42d9-9d2c-75dd4dc09064",
"traceId": "9e8f45ceebff4cefb032b1f754542797",
"evidence": "restoredRows=4",
"checkedAt": "2026-08-08T02:06:01.011Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户列表",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants?page=1&size=20&keyword=%E5%90%8E%E5%8F%B0%E5%AE%A2%E6%88%B7%E9%AA%8C%E6%94%B6-808020600398"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3ee91990-b379-4968-b0e3-ab08d13fe8ba, ced818cf-b7d1-472d-b0e6-f8f10e7b32e6",
"traceId": "34abafb02a9a46938c58fd5f34c55318",
"evidence": "",
"checkedAt": "2026-08-08T02:06:01.038Z"
},
{
"module": "customers",
"scenario": null,
"name": "列表包含新客户且字段完整",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants?page=1&size=20&keyword=%E5%90%8E%E5%8F%B0%E5%AE%A2%E6%88%B7%E9%AA%8C%E6%94%B6-808020600398"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "3ee91990-b379-4968-b0e3-ab08d13fe8ba, ced818cf-b7d1-472d-b0e6-f8f10e7b32e6",
"traceId": "34abafb02a9a46938c58fd5f34c55318",
"evidence": "found=true, missing=none",
"checkedAt": "2026-08-08T02:06:01.038Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户详情",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6d694ce1-49a2-4206-ac59-9345219cd35b, de69b6a5-4b88-4a77-8df4-6d6361f23fa1",
"traceId": "21a96c4ca1f7428ca74a8050916999a7",
"evidence": "",
"checkedAt": "2026-08-08T02:06:01.068Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户详情基本信息正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "6d694ce1-49a2-4206-ac59-9345219cd35b, de69b6a5-4b88-4a77-8df4-6d6361f23fa1",
"traceId": "21a96c4ca1f7428ca74a8050916999a7",
"evidence": "tenantId=2085910365380136962",
"checkedAt": "2026-08-08T02:06:01.068Z"
},
{
"module": "customers",
"scenario": null,
"name": "编辑客户",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e650c62c-ccf0-46c4-859a-6857c73c0aa4, f07c58e3-72cb-4615-9733-30b838835ebc",
"traceId": "87a1412e0b984ae2b627f1262010504d",
"evidence": "",
"checkedAt": "2026-08-08T02:06:01.100Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户编辑正确回显",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "PUT",
"path": "/api/platform/v1/iam/tenants/2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "e650c62c-ccf0-46c4-859a-6857c73c0aa4, f07c58e3-72cb-4615-9733-30b838835ebc",
"traceId": "87a1412e0b984ae2b627f1262010504d",
"evidence": "tenantId=2085910365380136962",
"checkedAt": "2026-08-08T02:06:01.100Z"
},
{
"module": "customers",
"scenario": null,
"name": "回查客户编辑结果",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "eac35a1c-d37f-4ddb-b88a-c0e28f3e2a8e, 5972f9bb-2da2-4b89-b081-a630190123b1",
"traceId": "0570893840f145fd815a48af13bbb159",
"evidence": "",
"checkedAt": "2026-08-08T02:06:01.127Z"
},
{
"module": "customers",
"scenario": null,
"name": "客户编辑已持久化",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "eac35a1c-d37f-4ddb-b88a-c0e28f3e2a8e, 5972f9bb-2da2-4b89-b081-a630190123b1",
"traceId": "0570893840f145fd815a48af13bbb159",
"evidence": "tenantId=2085910365380136962",
"checkedAt": "2026-08-08T02:06:01.127Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询资质",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085910365380136962/qualifications"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b66ce26f-5511-4416-950c-0d8ff2ceff2e, 20900824-11f2-4bd9-9ee5-78db4267de35",
"traceId": "4f08664e2e8c4ed18f9c87292e25085e",
"evidence": "",
"checkedAt": "2026-08-08T02:06:01.159Z"
},
{
"module": "customers",
"scenario": null,
"name": "资质概况结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085910365380136962/qualifications"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "b66ce26f-5511-4416-950c-0d8ff2ceff2e, 20900824-11f2-4bd9-9ee5-78db4267de35",
"traceId": "4f08664e2e8c4ed18f9c87292e25085e",
"evidence": "count=0",
"checkedAt": "2026-08-08T02:06:01.159Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询成员",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085910365380136962/members"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "4e38b00f-9877-448f-9c54-d7a4a604bf79, 6261cac3-bf0d-4e9c-81f5-19edb4a89bec",
"traceId": "261ac37d19d94fa0a6108d65333c84c6",
"evidence": "",
"checkedAt": "2026-08-08T02:06:01.189Z"
},
{
"module": "customers",
"scenario": "customer-owner-provisioning",
"name": "联系电话账号是企业管理员",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "GET",
"path": "/api/platform/v1/iam/tenants/2085910365380136962/members"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "4e38b00f-9877-448f-9c54-d7a4a604bf79, 6261cac3-bf0d-4e9c-81f5-19edb4a89bec",
"traceId": "261ac37d19d94fa0a6108d65333c84c6",
"evidence": "ownerFound=false, matches=false",
"checkedAt": "2026-08-08T02:06:01.189Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况查询运单数量",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/tenant-counts?tenantIds=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "82d5ea41-1dbd-4d11-a353-fed6e3f69b58, 84dc0125-532d-4c5a-a591-563cd57eb2f0",
"traceId": "a5c195d8f66e4ebd9f778724c84fef5e",
"evidence": "",
"checkedAt": "2026-08-08T02:06:01.221Z"
},
{
"module": "customers",
"scenario": null,
"name": "业务概况返回当前客户运单数",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg/tenant-counts?tenantIds=2085910365380136962"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "82d5ea41-1dbd-4d11-a353-fed6e3f69b58, 84dc0125-532d-4c5a-a591-563cd57eb2f0",
"traceId": "a5c195d8f66e4ebd9f778724c84fef5e",
"evidence": "orderCount=0",
"checkedAt": "2026-08-08T02:06:01.221Z"
},
{
"module": "customers",
"scenario": null,
"name": "查询客户运单记录",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=2085910365380136962&page=1&size=10"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9c31f043-204a-426e-bd31-b12b2f858269, dd5e3362-ec45-4f90-a7f2-3ebed8f18794",
"traceId": "4aa7a5a101ab45c8ad97c173ff98f13c",
"evidence": "",
"checkedAt": "2026-08-08T02:06:01.253Z"
},
{
"module": "customers",
"scenario": null,
"name": "运单记录分页结构正确",
"passed": true,
"deploymentBlocker": false,
"request": {
"method": "GET",
"path": "/api/platform/v1/orders/first-leg?tenantId=2085910365380136962&page=1&size=10"
},
"http": 200,
"code": "200",
"message": "success",
"requestId": "9c31f043-204a-426e-bd31-b12b2f858269, dd5e3362-ec45-4f90-a7f2-3ebed8f18794",
"traceId": "4aa7a5a101ab45c8ad97c173ff98f13c",
"evidence": "records=0, total=0",
"checkedAt": "2026-08-08T02:06:01.253Z"
},
{
"module": "customers",
"scenario": "customer-draft-archive",
"name": "归档测试客户",
"passed": false,
"deploymentBlocker": true,
"request": {
"method": "POST",
"path": "/api/platform/v1/iam/tenants/2085910365380136962/archive"
},
"http": 200,
"code": "S0500",
"message": "Internal server error",
"requestId": "d7382957-3ac8-44a5-bf1d-fb10394f2181",
"traceId": "33ede0d091f644a283c541e91aa0ab65",
"evidence": "tenantId=2085910365380136962",
"checkedAt": "2026-08-08T02:06:01.284Z"
}
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,771 @@
{
"marker": "20260808014530-5a43cd64",
"gateway": "http://8.163.30.128:9000",
"generatedAt": "2026-08-08T01:45:30.640Z",
"completedAt": "2026-08-08T01:45:33.768Z",
"success": false,
"fatal": {
"name": "Error",
"message": "system role is required"
},
"testAccount": {
"username": "client_20260808014530_c35db3",
"companyName": "客户端全链路测试企业-20260808014530-5a43cd64",
"legalPhone": "13953530641"
},
"resources": {
"tenantId": "2085905207086665730",
"membershipId": "2085905207086665732",
"reviewId": "2085905211088031745",
"productImageFileId": "2085905209935593474",
"receivingWarehouseId": "193000000000200001",
"destinationWarehouseId": "193000000000000001",
"channelCode": "EXX_RAIL_NY",
"roleId": null,
"testMembershipId": null,
"orderIds": [
"2085905210350829570"
]
},
"steps": [
{
"module": "registration",
"name": "enterprise self-registration",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:30.641Z",
"durationMs": 385,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-c7794425-ab36-4269-a9b5-f5b4f86a3137, client-e2e-c7794425-ab36-4269-a9b5-f5b4f86a3137",
"traceId": "1d05138199574e85959dc74fd1a1b591",
"evidence": {
"tenantId": "2085905207086665730",
"membershipId": "2085905207086665732",
"tenantStatus": "PROFILE_REQUIRED",
"nextStep": "LOGIN_AND_COMPLETE_QUALIFICATIONS"
}
},
{
"module": "registration",
"name": "duplicate username is rejected",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.026Z",
"durationMs": 34,
"http": 200,
"code": "A0409",
"message": "Username already exists",
"requestId": "client-e2e-fb418bb4-bc3b-46c9-9142-d1ee80c3e2c4, client-e2e-fb418bb4-bc3b-46c9-9142-d1ee80c3e2c4",
"traceId": "3872c33ca42b4df2aec314d50ffbd617",
"evidence": null
},
{
"module": "registration",
"name": "login before approval",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.061Z",
"durationMs": 342,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-3b1567e4-778b-40f8-9d2c-c1a9305f5a15, client-e2e-3b1567e4-778b-40f8-9d2c-c1a9305f5a15",
"traceId": "77778d9ae75d4796ac6091eff4419277",
"evidence": {
"username": "client_20260808014530_c35db3"
}
},
{
"module": "registration",
"name": "profile is incomplete before qualifications",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.403Z",
"durationMs": 28,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-fdea7af8-745a-4164-8ff1-f151a514dbb8, client-e2e-fdea7af8-745a-4164-8ff1-f151a514dbb8",
"traceId": "6bcc058353464d868420c6200d381ddb",
"evidence": {
"id": "2085905207086665730",
"status": "PROFILE_REQUIRED",
"tenantName": "客户端全链路测试企业-20260808014530-5a43cd64"
}
},
{
"module": "pre-approval-master-data",
"name": "load active domestic receiving warehouses",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.431Z",
"durationMs": 32,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-5da4d470-0c10-4c49-8e73-8e252fd63dc6, client-e2e-5da4d470-0c10-4c49-8e73-8e252fd63dc6",
"traceId": "ad9e049be6934a08aad12c88a2fdb252",
"evidence": {
"total": "2",
"warehouseCount": 2
}
},
{
"module": "pre-approval-master-data",
"name": "load active FBA destination warehouses",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.463Z",
"durationMs": 73,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-67d8254c-9b79-42d5-8df5-48df4b81d931, client-e2e-67d8254c-9b79-42d5-8df5-48df4b81d931",
"traceId": "e24f5f82f419469e877a29b4f696a18f",
"evidence": {
"total": "253",
"warehouseCount": 200
}
},
{
"module": "pre-approval-master-data",
"name": "load receiving warehouse detail",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.536Z",
"durationMs": 30,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-b8d9aacc-fef5-40ee-8e77-7784ef3db66f, client-e2e-b8d9aacc-fef5-40ee-8e77-7784ef3db66f",
"traceId": "9d954f424b504b7bb978f01684cdf8ee",
"evidence": {
"id": "193000000000200001",
"name": "义乌仓"
}
},
{
"module": "pre-approval-master-data",
"name": "load active pricing channels",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.567Z",
"durationMs": 42,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-41874113-4ffb-4523-aa2e-dd1aac001f30, client-e2e-41874113-4ffb-4523-aa2e-dd1aac001f30",
"traceId": "325f2af177f64eefac20083ee9840d03",
"evidence": {
"channels": [
{
"channelCode": "EXX_RAIL_NY",
"priceModel": "FBA_WAREHOUSE_RATE",
"pricingTaxModes": [
"TAX_INCLUDED",
"TAX_EXCLUDED"
]
},
{
"channelCode": "EXX_TRUCK",
"priceModel": "FBA_WAREHOUSE_RATE",
"pricingTaxModes": [
"TAX_INCLUDED",
"TAX_EXCLUDED"
]
},
{
"channelCode": "HEDE_PARCEL",
"priceModel": "REGION_WEIGHT_TIER",
"pricingTaxModes": [
"TAX_INCLUDED"
]
},
{
"channelCode": "HEDE_TRUCK",
"priceModel": "FBA_WAREHOUSE_RATE",
"pricingTaxModes": [
"TAX_INCLUDED",
"TAX_EXCLUDED"
]
},
{
"channelCode": "MATSON_CLX_PARCEL",
"priceModel": "REGION_WEIGHT_TIER",
"pricingTaxModes": [
"TAX_INCLUDED"
]
},
{
"channelCode": "MATSON_CLX_TRUCK",
"priceModel": "FBA_WAREHOUSE_RATE",
"pricingTaxModes": [
"TAX_INCLUDED",
"TAX_EXCLUDED"
]
},
{
"channelCode": "MATSON_MAX_PARCEL",
"priceModel": "REGION_WEIGHT_TIER",
"pricingTaxModes": [
"TAX_INCLUDED"
]
},
{
"channelCode": "MATSON_MAX_TRUCK",
"priceModel": "FBA_WAREHOUSE_RATE",
"pricingTaxModes": [
"TAX_INCLUDED",
"TAX_EXCLUDED"
]
},
{
"channelCode": "OA_CHICAGO_RAIL",
"priceModel": "FBA_WAREHOUSE_RATE",
"pricingTaxModes": [
"TAX_INCLUDED",
"TAX_EXCLUDED"
]
},
{
"channelCode": "OA_HOUSTON_TRUCK",
"priceModel": "FBA_WAREHOUSE_RATE",
"pricingTaxModes": [
"TAX_INCLUDED",
"TAX_EXCLUDED"
]
},
{
"channelCode": "OA_LA_FULL_TRUCK",
"priceModel": "FBA_WAREHOUSE_RATE",
"pricingTaxModes": [
"TAX_INCLUDED",
"TAX_EXCLUDED"
]
},
{
"channelCode": "OA_LA_TRUCK",
"priceModel": "FBA_WAREHOUSE_RATE",
"pricingTaxModes": [
"TAX_INCLUDED",
"TAX_EXCLUDED"
]
},
{
"channelCode": "OA_NEW_YORK_TRUCK",
"priceModel": "FBA_WAREHOUSE_RATE",
"pricingTaxModes": [
"TAX_INCLUDED",
"TAX_EXCLUDED"
]
},
{
"channelCode": "OA_PARCEL",
"priceModel": "REGION_WEIGHT_TIER",
"pricingTaxModes": [
"TAX_INCLUDED"
]
},
{
"channelCode": "OA_SAVANNAH_TRUCK",
"priceModel": "FBA_WAREHOUSE_RATE",
"pricingTaxModes": [
"TAX_INCLUDED",
"TAX_EXCLUDED"
]
}
]
}
},
{
"module": "pre-approval-master-data",
"name": "load public shipper candidates",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.609Z",
"durationMs": 36,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-0fc5f34b-4873-4bbf-971d-f2eb852f9979, client-e2e-0fc5f34b-4873-4bbf-971d-f2eb852f9979",
"traceId": "dc27cbd326cc477fae138ca02c1ba745",
"evidence": {
"candidateCount": 3
}
},
{
"module": "pre-approval-product-image",
"name": "upload product image",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.645Z",
"durationMs": 36,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-38af106a-fc7e-4c06-bdf3-67506fe7556b, client-e2e-38af106a-fc7e-4c06-bdf3-67506fe7556b",
"traceId": "bee77f8cfa2a4e5fa2f57bbc48000808",
"evidence": {
"fileId": "2085905209935593474",
"fileName": "product-20260808014530-5a43cd64.png",
"contentType": "image/png",
"sizeBytes": "68"
}
},
{
"module": "order-validation",
"name": "missing pricingTaxMode is rejected",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.682Z",
"durationMs": 29,
"http": 200,
"code": "A0422",
"message": "pricingTaxMode: must not be blank",
"requestId": "client-e2e-e3a22c2a-5b4d-43bc-ba4f-a6fed0cdc1ad, client-e2e-e3a22c2a-5b4d-43bc-ba4f-a6fed0cdc1ad",
"traceId": "13991b6b039144ddbb87428938824b2f",
"evidence": null
},
{
"module": "order-before-approval",
"name": "create draft before approval",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.712Z",
"durationMs": 75,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-3cf5d757-9ff8-4478-8f45-d6df37f04f2b, client-e2e-3cf5d757-9ff8-4478-8f45-d6df37f04f2b",
"traceId": "7e68c91e2f5147688862d71ad961870e",
"evidence": {
"orderId": "2085905210350829570",
"status": "DRAFT"
}
},
{
"module": "order-before-approval",
"name": "create draft idempotent replay",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.787Z",
"durationMs": 29,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-634721a6-f68a-44f7-a220-2067585f9ef9, client-e2e-634721a6-f68a-44f7-a220-2067585f9ef9",
"traceId": "6df3ed2d9b13433a810ef9d3be6420f4",
"evidence": {
"orderId": "2085905210350829570",
"replayed": true
}
},
{
"module": "order-before-approval",
"name": "submit is blocked before approval",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.816Z",
"durationMs": 29,
"http": 200,
"code": "A0403",
"message": "Enterprise information must be approved before ordering",
"requestId": "client-e2e-5b65d3ca-0edf-4be8-96d1-5299af7a5a72, client-e2e-5b65d3ca-0edf-4be8-96d1-5299af7a5a72",
"traceId": "50b158e39c564395aa2c36af7f5e08a8",
"evidence": {
"orderId": "2085905210350829570",
"rejectionCode": "A0403"
}
},
{
"module": "qualification-review",
"name": "upload required qualification package",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.845Z",
"durationMs": 49,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-85dddf86-f87e-4ca6-af55-70ff44d67078, client-e2e-85dddf86-f87e-4ca6-af55-70ff44d67078",
"traceId": "a1d8108021514d4cabe14a4c206b1ab0",
"evidence": {
"businessLicenseQualificationId": "2085905210794430466",
"legalPersonIdQualificationId": "2085905210798624769",
"tenantStatus": "PROFILE_REQUIRED"
}
},
{
"module": "qualification-review",
"name": "required qualifications are pending",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.894Z",
"durationMs": 26,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-60e34c44-4eee-4365-a039-3653840f7806, client-e2e-60e34c44-4eee-4365-a039-3653840f7806",
"traceId": "c7da811803f04c118926f42e284b7680",
"evidence": {
"qualifications": [
{
"qualificationId": "2085905210794430466",
"qualificationType": "BUSINESS_LICENSE",
"status": "PENDING"
},
{
"qualificationId": "2085905210798624769",
"qualificationType": "LEGAL_PERSON_ID",
"status": "PENDING"
}
]
}
},
{
"module": "qualification-review",
"name": "submit enterprise review",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.920Z",
"durationMs": 38,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-36cbbcb1-6413-4da3-88ee-b260bcd8393f, client-e2e-36cbbcb1-6413-4da3-88ee-b260bcd8393f",
"traceId": "bccae7d6b31f4223afca1d2c024b884d",
"evidence": {
"tenantId": "2085905207086665730",
"status": "PENDING"
}
},
{
"module": "qualification-review",
"name": "platform reviewer login",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:31.958Z",
"durationMs": 570,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-3ffc0660-9bc8-4094-ad82-7c5d4ca74f15, client-e2e-3ffc0660-9bc8-4094-ad82-7c5d4ca74f15",
"traceId": "d6f120c86bc1408394ebe84803cdf112",
"evidence": {
"username": "platform_admin"
}
},
{
"module": "qualification-review",
"name": "platform finds pending review",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:32.528Z",
"durationMs": 21,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-11539108-592a-440b-8849-31b751d3b0a5, client-e2e-11539108-592a-440b-8849-31b751d3b0a5",
"traceId": "590c16ed44c8495c88d0d29a52b68f44",
"evidence": {
"reviewId": "2085905211088031745",
"tenantId": "2085905207086665730",
"status": "PENDING"
}
},
{
"module": "qualification-review",
"name": "platform sees required qualifications",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:32.549Z",
"durationMs": 37,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-c99aba15-c0d9-429c-b417-2dbfb1ec456f, client-e2e-c99aba15-c0d9-429c-b417-2dbfb1ec456f",
"traceId": "4e805a99bf134ff5be1aa9c1d091e5cc",
"evidence": {
"qualifications": [
{
"qualificationId": "2085905210794430466",
"qualificationType": "BUSINESS_LICENSE",
"status": "PENDING"
},
{
"qualificationId": "2085905210798624769",
"qualificationType": "LEGAL_PERSON_ID",
"status": "PENDING"
}
]
}
},
{
"module": "qualification-review",
"name": "platform approves enterprise",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:32.586Z",
"durationMs": 36,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-1c9e31f9-16cd-4a1f-a96b-6cc1c204c195, client-e2e-1c9e31f9-16cd-4a1f-a96b-6cc1c204c195",
"traceId": "b6e371ef58704f3a827dcf3919d416c9",
"evidence": {
"tenantId": "2085905207086665730",
"administratorCreated": false,
"generatedCredentialsReturned": false
}
},
{
"module": "qualification-review",
"name": "registered account remains enterprise owner",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:32.622Z",
"durationMs": 32,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-2cc4f5e3-fa0d-410b-8c97-a9be991fc68f, client-e2e-2cc4f5e3-fa0d-410b-8c97-a9be991fc68f",
"traceId": "77418746198a44b282efcd740ae67fe6",
"evidence": {
"membershipId": "2085905207086665732",
"username": "client_20260808014530_c35db3",
"memberType": "OWNER",
"status": "ACTIVE"
}
},
{
"module": "merchant-session",
"name": "client re-login after approval",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:32.654Z",
"durationMs": 330,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-4575fcf9-7c1d-42b8-af71-0a8320480c3e, client-e2e-4575fcf9-7c1d-42b8-af71-0a8320480c3e",
"traceId": "01426a07828a4cfa833fa0a55f45f1a2",
"evidence": {
"username": "client_20260808014530_c35db3"
}
},
{
"module": "merchant-session",
"name": "profile is active after approval",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:32.984Z",
"durationMs": 35,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-45c80e5b-518e-4c79-a002-8895b07e934a, client-e2e-45c80e5b-518e-4c79-a002-8895b07e934a",
"traceId": "33835089a75346259faf3e4be7fc4cab",
"evidence": {
"id": "2085905207086665730",
"status": "ACTIVE",
"tenantName": "客户端全链路测试企业-20260808014530-5a43cd64"
}
},
{
"module": "merchant-session",
"name": "required qualifications are approved",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:33.019Z",
"durationMs": 32,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-b5ca7293-3bc8-4fe9-acec-ba780b883598, client-e2e-b5ca7293-3bc8-4fe9-acec-ba780b883598",
"traceId": "e9dde9eb1c304d59a84a119073150341",
"evidence": {
"qualifications": [
{
"qualificationId": "2085905210794430466",
"qualificationType": "BUSINESS_LICENSE",
"status": "APPROVED"
},
{
"qualificationId": "2085905210798624769",
"qualificationType": "LEGAL_PERSON_ID",
"status": "APPROVED"
}
]
}
},
{
"module": "profile-management",
"name": "load enterprise profile for editing",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:33.052Z",
"durationMs": 30,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-0a73393c-f9ae-4b14-88ab-55935757a54a, client-e2e-0a73393c-f9ae-4b14-88ab-55935757a54a",
"traceId": "452e015390f940238d2568ad4916b89b",
"evidence": {
"id": "2085905207086665730",
"tenantName": "客户端全链路测试企业-20260808014530-5a43cd64",
"status": "ACTIVE"
}
},
{
"module": "profile-management",
"name": "edit enterprise profile",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:33.082Z",
"durationMs": 43,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-80ee9d05-b29b-4a40-afbd-555bcccd5556, client-e2e-80ee9d05-b29b-4a40-afbd-555bcccd5556",
"traceId": "5ff4bf3c6368427cbdc31b5834f5271d",
"evidence": {
"id": "2085905207086665730",
"tenantName": "客户端全链路测试企业-20260808014530-5a43cd64-已编辑",
"contactName": "企业联系人-20260808014530-5a43cd64",
"status": "ACTIVE"
}
},
{
"module": "profile-management",
"name": "enterprise profile edit is persisted",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:33.125Z",
"durationMs": 21,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-ec89642c-289d-4126-a0dd-150240edada4, client-e2e-ec89642c-289d-4126-a0dd-150240edada4",
"traceId": "da8dda311d904eb59942aff9069de6e3",
"evidence": {
"id": "2085905207086665730",
"tenantName": "客户端全链路测试企业-20260808014530-5a43cd64-已编辑",
"contactEmail": "company_202608080145305a43cd64@example.com"
}
},
{
"module": "profile-management",
"name": "load personal profile",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:33.146Z",
"durationMs": 27,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-76865186-3db6-4172-8ebc-3df342f78665, client-e2e-76865186-3db6-4172-8ebc-3df342f78665",
"traceId": "3771e32c220e439fb3f2ebff0a7e752d",
"evidence": {
"userId": "2085905207086665731",
"username": "client_20260808014530_c35db3",
"displayName": "测试管理员-20260808014530-5a43cd64"
}
},
{
"module": "profile-management",
"name": "edit personal profile",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:33.173Z",
"durationMs": 42,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-ced218a9-a8d8-4991-9122-bb10e574006a, client-e2e-ced218a9-a8d8-4991-9122-bb10e574006a",
"traceId": "bfdc316613204a13b2f1be04c4ac8095",
"evidence": {
"userId": "2085905207086665731",
"username": "client_20260808014530_c35db3",
"displayName": "测试企业管理员-20260808014530-5a43cd64"
}
},
{
"module": "profile-management",
"name": "re-login after personal profile token revocation",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:33.215Z",
"durationMs": 346,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-40c1f77b-720f-414a-b4e9-9f6832fc6860, client-e2e-40c1f77b-720f-414a-b4e9-9f6832fc6860",
"traceId": "952b1d17ba8742898b658b6358d16867",
"evidence": {
"username": "client_20260808014530_c35db3"
}
},
{
"module": "profile-management",
"name": "personal profile edit is persisted",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:33.561Z",
"durationMs": 37,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-43d40bc7-8036-4bab-ad8e-13cf29b4671e, client-e2e-43d40bc7-8036-4bab-ad8e-13cf29b4671e",
"traceId": "c76ed4050f5e49269489ff80cc76436d",
"evidence": {
"userId": "2085905207086665731",
"username": "client_20260808014530_c35db3",
"displayName": "测试企业管理员-20260808014530-5a43cd64"
}
},
{
"module": "rbac-member",
"name": "load merchant permission resources",
"passed": true,
"skipped": false,
"startedAt": "2026-08-08T01:45:33.599Z",
"durationMs": 138,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-d341aa52-7250-4f4b-b8e0-a70303366d06, client-e2e-d341aa52-7250-4f4b-b8e0-a70303366d06",
"traceId": "f582262ec52c4239b2d025c6e85f977f",
"evidence": {
"permissionCount": 27
}
},
{
"module": "rbac-member",
"name": "load initial merchant roles",
"passed": false,
"skipped": false,
"startedAt": "2026-08-08T01:45:33.737Z",
"durationMs": 31,
"http": 200,
"code": "200",
"message": "success",
"requestId": "client-e2e-235e0999-bffc-4926-93af-9a61f7fa2223, client-e2e-235e0999-bffc-4926-93af-9a61f7fa2223",
"traceId": "ccc2bed8eaaf48029a9cb40705a9da90",
"evidence": {
"roleCount": 0
},
"error": {
"name": "Error",
"message": "system role is required"
}
}
],
"futureModules": [
"measurement",
"ocr",
"mojia",
"billing"
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+448
View File
@@ -0,0 +1,448 @@
#!/usr/bin/env node
// Test environment administrator / 测试环境管理员
// Username: platform_admin
// Password: ChangeMe123!
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import process from 'node:process'
const DEFAULT_GATEWAY = 'http://8.163.30.128:9000'
const DEFAULT_USERNAME = 'platform_admin'
const DEFAULT_PASSWORD = 'ChangeMe123!'
const DEFAULT_MODULES = ['customers', 'system', 'master-data', 'orders', 'pricing', 'downloads', 'inbound-weight-ocr', 'inbound-measurement']
const WEIGHT_IMAGE_PATH = 'C:/Users/BY-OY/Desktop/上传图片包/测量图片/测量图片/orig_1cf27695-b2c3-44b3-98e3-c0be8b80f79a.JPG'
const CUSTOMER_FIELDS = [
'id', 'tenantCode', 'tenantName', 'status', 'legalName', 'legalRepresentative',
'contactName', 'contactPhone', 'contactEmail', 'contactWechat',
'servicePackage', 'salesOwnerName', 'paymentTermDays', 'enterpriseType', 'establishedDate',
'registeredCapital', 'registeredCapitalCurrency', 'registeredAddress', 'internalRemark',
'createdAt',
]
const BOX_DEFAULTS = [
{ boxType: 'SMALL', dimensions: ['30.000', '20.000', '20.000'], remark: '标准小箱' },
{ boxType: 'MEDIUM', dimensions: ['40.000', '30.000', '30.000'], remark: '标准中箱' },
{ boxType: 'LARGE', dimensions: ['50.000', '40.000', '40.000'], remark: '标准大箱' },
{ boxType: 'EXTRA_LARGE', dimensions: ['60.000', '50.000', '50.000'], remark: '特大箱' },
]
function usage() {
console.log(`后台管理测试环境 HTTP API 验收
测试管理员: ${DEFAULT_USERNAME} / ${DEFAULT_PASSWORD}
用法:
node scripts/admin-test-env-acceptance.mjs [options]
选项:
--gateway <url> Gateway 地址,默认 ${DEFAULT_GATEWAY}
--username <name> 管理员账号,默认 ${DEFAULT_USERNAME}
--password <value> 管理员密码,默认使用上方测试密码
--modules <names> 逗号分隔,默认 ${DEFAULT_MODULES.join(',')}
--output <path> JSON 报告路径,默认 reports/
--help 显示帮助
环境变量覆盖: ADMIN_TEST_GATEWAY, ADMIN_TEST_USERNAME, ADMIN_TEST_PASSWORD`)
}
function parseArguments(argv) {
if (argv.includes('--help')) { usage(); process.exit(0) }
const values = new Map()
for (let index = 0; index < argv.length; index += 1) {
const argument = argv[index]
if (!argument.startsWith('--')) throw new Error(`无法识别的参数: ${argument}`)
const separator = argument.indexOf('=')
const name = separator >= 0 ? argument.slice(2, separator) : argument.slice(2)
const inline = separator >= 0 ? argument.slice(separator + 1) : undefined
if (inline !== undefined) values.set(name, inline)
else {
const next = argv[index + 1]
if (!next || next.startsWith('--')) throw new Error(`参数 --${name} 缺少值`)
values.set(name, next); index += 1
}
}
return {
gateway: (values.get('gateway') || process.env.ADMIN_TEST_GATEWAY || DEFAULT_GATEWAY).replace(/\/$/, ''),
username: values.get('username') || process.env.ADMIN_TEST_USERNAME || DEFAULT_USERNAME,
password: values.get('password') || process.env.ADMIN_TEST_PASSWORD || DEFAULT_PASSWORD,
modules: (values.get('modules') || DEFAULT_MODULES.join(',')).split(',').map((v) => v.trim()).filter(Boolean),
output: values.get('output') || '',
}
}
function own(value, field) { return value !== null && typeof value === 'object' && Object.hasOwn(value, field) }
function hasFields(value, fields) { return fields.every((field) => own(value, field)) }
function numberEquals(actual, expected) { return Math.abs(Number(actual) - Number(expected)) < 0.0005 }
function halfUp3(value) { return Math.round((Number(value) + Number.EPSILON) * 1000) / 1000 }
function derived(length, width, height, edge) {
const outerLength = Number(length) + Number(edge) * 2
const outerWidth = Number(width) + Number(edge) * 2
const outerHeight = Number(height) + Number(edge) * 2
return { outerLength, outerWidth, outerHeight, volume: halfUp3(outerLength * outerWidth * outerHeight / 1_000_000) }
}
class AcceptanceRun {
constructor(options) {
this.options = options
this.marker = `ADMIN-API-${new Date().toISOString().replace(/\D/g, '').slice(0, 17)}`
this.startedAt = new Date().toISOString()
this.steps = []
this.modules = []
this.scenarios = []
this.createdResources = {}
this.deploymentBlockers = []
this.context = {}
this.token = ''
this.fatal = null
}
async request(method, path, body = undefined, token = this.token) {
const headers = { Accept: 'application/json' }
if (token) headers.Authorization = `Bearer ${token}`
if (body !== undefined) headers['Content-Type'] = 'application/json; charset=utf-8'
try {
const response = await fetch(`${this.options.gateway}${path}`, {
method, headers, body: body === undefined ? undefined : JSON.stringify(body),
signal: AbortSignal.timeout(30_000),
})
const content = await response.text()
let payload = null
try { payload = content ? JSON.parse(content) : null } catch { /* Raw response is recorded below. */ }
return {
method, path, http: response.status, body: payload,
raw: payload === null ? content.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: error instanceof Error ? error.message : String(error), requestId: null, traceId: null }
}
}
async requestBinary(method, path) {
const headers = { Accept: '*/*', Authorization: `Bearer ${this.token}` }
try {
const response = await fetch(`${this.options.gateway}${path}`, { method, headers, signal: AbortSignal.timeout(120_000) })
const bytes = new Uint8Array(await response.arrayBuffer())
const contentType = response.headers.get('content-type') || ''
let payload = null
if (contentType.toLowerCase().includes('json')) {
try { payload = JSON.parse(new TextDecoder().decode(bytes)) } catch { /* Invalid envelope remains binary evidence. */ }
}
return {
method, path, http: response.status, body: payload, bytes, contentType,
raw: payload ? null : `${bytes.length} bytes (${contentType || 'unknown'})`,
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, bytes: new Uint8Array(), contentType: '', raw: String(error), requestId: null, traceId: null }
}
}
addStep(module, name, response, passed, evidence = '', options = {}) {
const step = {
module, scenario: options.scenario || null, name, passed: Boolean(passed),
deploymentBlocker: Boolean(options.deploymentBlocker && !passed),
request: response ? { method: response.method, path: response.path } : null,
http: response?.http ?? null,
code: response?.body?.code === undefined ? null : String(response.body.code),
message: response?.body?.message || response?.raw || null,
requestId: response?.requestId || null, traceId: response?.traceId || null,
evidence, checkedAt: new Date().toISOString(),
}
this.steps.push(step)
if (step.deploymentBlocker) this.deploymentBlockers.push({ module, scenario: step.scenario, name, evidence, code: step.code, requestId: step.requestId, traceId: step.traceId })
console.log(`[${step.passed ? 'PASS' : 'FAIL'}] [${module}${step.scenario ? `/${step.scenario}` : ''}] ${name}${evidence ? ` - ${evidence}` : ''}`)
return step.passed
}
apiSuccess(module, name, response, evidence = '', options = {}) {
const passed = response.http === 200 && String(response.body?.code) === '200'
this.addStep(module, name, response, passed, evidence, options)
return passed ? response.body.data : null
}
assert(module, name, response, condition, evidence, options = {}) {
return this.addStep(module, name, response, Boolean(condition), evidence, options)
}
expectConflict(module, name, response, scenario) {
const passed = response.http === 200 && ['A0409', '409'].includes(String(response.body?.code))
this.addStep(module, name, response, passed, `HTTP=${response.http}, code=${response.body?.code ?? 'missing'}`, { scenario, deploymentBlocker: true })
return passed
}
async executeModule(name, handler) {
const startedAt = new Date().toISOString(); const start = this.steps.length; let fatal = null
try { await handler() } catch (error) {
fatal = error instanceof Error ? error.message : String(error)
this.addStep(name, '模块执行', null, false, fatal)
}
const steps = this.steps.slice(start)
this.modules.push({ name, startedAt, finishedAt: new Date().toISOString(), success: fatal === null && steps.every((s) => s.passed), passed: steps.filter((s) => s.passed).length, failed: steps.filter((s) => !s.passed).length, fatal })
}
async findAvailablePhone(module = 'customers') {
const seed = BigInt(Date.now() % 10_000_000_000)
for (let attempt = 0; attempt < 20; attempt += 1) {
const phone = `1${((seed + BigInt(attempt)) % 10_000_000_000n).toString().padStart(10, '0')}`
const response = await this.request('GET', `/api/platform/v1/iam/tenants/contact-phone-availability?contactPhone=${phone}`)
if (this.apiSuccess(module, '检查测试联系电话可用性', response, `attempt=${attempt + 1}`) === true) return phone
}
throw new Error('连续 20 个测试联系电话均不可用')
}
async runCustomerDimensionRules(tenantId) {
const module = 'customers'; const scenario = 'customer-dimension-rules'; const start = this.steps.length
const options = { scenario, deploymentBlocker: true }
const path = `/api/platform/v1/first-leg/customer-dimension-config?tenantId=${tenantId}`
const initialResponse = await this.request('GET', path)
const group = this.apiSuccess(module, '新客户尺寸规则可查询', initialResponse, `tenantId=${tenantId}`, options)
const rules = Array.isArray(group?.rules) ? group.rules : []
const orderedTypes = rules.map((rule) => rule.boxType)
this.assert(module, '固定四箱规顺序完整且无重复', initialResponse,
rules.length === 4 && orderedTypes.join(',') === BOX_DEFAULTS.map((item) => item.boxType).join(',') && new Set(orderedTypes).size === 4,
`count=${rules.length}, order=${orderedTypes.join(',')}`, options)
const defaultsCorrect = BOX_DEFAULTS.every((expected, index) => {
const rule = rules[index]
return rule?.boxType === expected.boxType
&& numberEquals(rule.innerLengthCm, expected.dimensions[0])
&& numberEquals(rule.innerWidthCm, expected.dimensions[1])
&& numberEquals(rule.innerHeightCm, expected.dimensions[2])
&& numberEquals(rule.edgeAdditionCm, '2.000') && rule.remark === expected.remark
})
this.assert(module, '四箱规默认内径、加边和备注正确', initialResponse, defaultsCorrect,
defaultsCorrect ? 'defaults=matched' : '默认尺寸、edge 或备注不匹配', options)
const identityCorrect = typeof group?.tenantId === 'string' && group.tenantId === tenantId
&& rules.length === 4 && rules.every((rule) => typeof rule.id === 'string' && Number(rule.version) === 0)
this.assert(module, '客户 ID、规则 ID 为字符串且初始版本为0', initialResponse, identityCorrect,
`groupTenantIdType=${typeof group?.tenantId}, rows=${rules.map((r) => `${typeof r.id}/v${r.version}`).join(',')}`, options)
const initialDerivedCorrect = rules.length === 4 && rules.every((rule) => {
const value = derived(rule.innerLengthCm, rule.innerWidthCm, rule.innerHeightCm, rule.edgeAdditionCm)
return numberEquals(rule.outerLengthCm, value.outerLength) && numberEquals(rule.outerWidthCm, value.outerWidth)
&& numberEquals(rule.outerHeightCm, value.outerHeight) && numberEquals(rule.volumeCbm, value.volume)
})
this.assert(module, '默认规则外径与 HALF_UP 三位 CBM 正确', initialResponse, initialDerivedCorrect,
`derivedRows=${rules.length}`, options)
if (rules.length === 4) {
const updatePayload = { rules: rules.map((rule, index) => ({
boxType: rule.boxType,
innerLengthCm: Number((Number(rule.innerLengthCm) + index + 0.125).toFixed(3)),
innerWidthCm: Number((Number(rule.innerWidthCm) + index + 0.250).toFixed(3)),
innerHeightCm: Number((Number(rule.innerHeightCm) + index + 0.375).toFixed(3)),
edgeAdditionCm: Number((2.125 + index * 0.125).toFixed(3)),
remark: `${this.marker}-${rule.boxType}`,
version: Number(rule.version),
})) }
const allowed = ['boxType', 'innerLengthCm', 'innerWidthCm', 'innerHeightCm', 'edgeAdditionCm', 'remark', 'version']
this.assert(module, 'PUT 每行只发送约定7字段并整组保存', initialResponse,
updatePayload.rules.length === 4 && updatePayload.rules.every((row) => Object.keys(row).join(',') === allowed.join(',')),
`fields=${Object.keys(updatePayload.rules[0]).join(',')}`, options)
const updateResponse = await this.request('PUT', path, updatePayload)
const updatedGroup = this.apiSuccess(module, '一次 PUT 保存四条三位小数规则', updateResponse, 'rows=4', options)
const updatedRules = Array.isArray(updatedGroup?.rules) ? updatedGroup.rules : []
const updatedCorrect = updatedRules.length === 4 && updatePayload.rules.every((expected, index) => {
const actual = updatedRules[index]; const value = derived(expected.innerLengthCm, expected.innerWidthCm, expected.innerHeightCm, expected.edgeAdditionCm)
return actual?.boxType === expected.boxType && Number(actual.version) === expected.version + 1
&& numberEquals(actual.innerLengthCm, expected.innerLengthCm) && numberEquals(actual.innerWidthCm, expected.innerWidthCm)
&& numberEquals(actual.innerHeightCm, expected.innerHeightCm) && numberEquals(actual.edgeAdditionCm, expected.edgeAdditionCm)
&& numberEquals(actual.outerLengthCm, value.outerLength) && numberEquals(actual.outerWidthCm, value.outerWidth)
&& numberEquals(actual.outerHeightCm, value.outerHeight) && numberEquals(actual.volumeCbm, value.volume)
&& actual.remark === expected.remark
})
this.assert(module, '修改后四行版本+1且派生外径/CBM正确', updateResponse, updatedCorrect,
`versions=${updatedRules.map((r) => r.version).join(',')}`, options)
const stalePayload = { rules: updatePayload.rules.map((rule) => ({
...rule,
innerLengthCm: Number((Number(rule.innerLengthCm) + 9).toFixed(3)),
})) }
const staleResponse = await this.request('PUT', path, stalePayload)
this.expectConflict(module, '旧 version 重放 PUT 返回冲突', staleResponse, scenario)
const afterConflictResponse = await this.request('GET', path)
const afterConflict = this.apiSuccess(module, '冲突后重新查询四条规则', afterConflictResponse, '', options)
const afterRules = Array.isArray(afterConflict?.rules) ? afterConflict.rules : []
const stableSnapshot = (rule) => JSON.stringify({
id: String(rule.id), boxType: rule.boxType,
innerLengthCm: Number(rule.innerLengthCm), innerWidthCm: Number(rule.innerWidthCm),
innerHeightCm: Number(rule.innerHeightCm), edgeAdditionCm: Number(rule.edgeAdditionCm),
outerLengthCm: Number(rule.outerLengthCm), outerWidthCm: Number(rule.outerWidthCm),
outerHeightCm: Number(rule.outerHeightCm), volumeCbm: Number(rule.volumeCbm),
remark: rule.remark ?? null, version: Number(rule.version), updatedAt: rule.updatedAt ?? null,
})
const atomic = afterRules.length === 4 && updatedRules.length === 4
&& afterRules.every((rule, index) => stableSnapshot(rule) === stableSnapshot(updatedRules[index]))
this.assert(module, '冲突请求未造成四行部分更新', afterConflictResponse, atomic,
`unchangedRows=${atomic ? 4 : 0}`, options)
const currentByType = new Map(afterRules.map((rule) => [rule.boxType, rule]))
const restorePayload = { rules: rules.map((original) => ({
boxType: original.boxType,
innerLengthCm: Number(original.innerLengthCm),
innerWidthCm: Number(original.innerWidthCm),
innerHeightCm: Number(original.innerHeightCm),
edgeAdditionCm: Number(original.edgeAdditionCm),
remark: original.remark ?? null,
version: Number(currentByType.get(original.boxType)?.version),
})) }
const restorable = restorePayload.rules.length === 4
&& restorePayload.rules.every((rule) => Number.isInteger(rule.version) && rule.version >= 0)
if (restorable) {
const restoreResponse = await this.request('PUT', path, restorePayload)
const restoredGroup = this.apiSuccess(module, '恢复本轮修改前的四箱规配置', restoreResponse,
'restore=original-values', options)
const restoredRules = Array.isArray(restoredGroup?.rules) ? restoredGroup.rules : []
const restored = restoredRules.length === 4 && rules.every((original, index) => {
const actual = restoredRules[index]
return actual?.boxType === original.boxType
&& numberEquals(actual.innerLengthCm, original.innerLengthCm)
&& numberEquals(actual.innerWidthCm, original.innerWidthCm)
&& numberEquals(actual.innerHeightCm, original.innerHeightCm)
&& numberEquals(actual.edgeAdditionCm, original.edgeAdditionCm)
&& actual.remark === original.remark
&& Number(actual.version) === restorePayload.rules[index].version + 1
})
this.assert(module, '尺寸规则恢复后回显与初始值一致', restoreResponse, restored,
`restoredRows=${restored ? 4 : 0}`, options)
} else {
this.assert(module, '恢复本轮修改前的四箱规配置', afterConflictResponse, false,
'冲突后未返回完整四行及有效版本,无法安全恢复', options)
}
} else {
this.assert(module, '尺寸规则编辑与并发冲突场景', initialResponse, false, '默认规则不是完整四条,禁止降级跳过', options)
}
const steps = this.steps.slice(start)
this.scenarios.push({ name: scenario, module, success: steps.every((step) => step.passed), deploymentBlocked: steps.some((step) => step.deploymentBlocker), passed: steps.filter((step) => step.passed).length, failed: steps.filter((step) => !step.passed).length })
}
async runCustomers() {
const module = 'customers'; const phone = await this.findAvailablePhone(module); const suffix = this.marker.slice(-12)
const payload = {
tenantName: `后台客户验收-${suffix}`, legalName: `后台客户验收有限公司-${suffix}`,
legalRepresentative: '接口验收法人', contactName: '接口验收联系人', contactPhone: phone,
contactEmail: `admin-api-${suffix.toLowerCase()}@example.com`, servicePackage: 'API验收套餐',
contactWechat: `api_${suffix.toLowerCase()}`, salesOwnerName: '接口验收销售', paymentTermDays: 30,
enterpriseType: 'LIMITED_COMPANY', establishedDate: '2020-01-02', registeredCapital: 5000000,
registeredCapitalCurrency: 'CNY', registeredAddress: '深圳市南山区接口验收地址', internalRemark: this.marker,
}
const createResponse = await this.request('POST', '/api/platform/v1/iam/tenants', payload)
const createData = this.apiSuccess(module, '创建客户', createResponse)
if (!createData) throw new Error('创建客户失败,后续客户模块无法继续')
const customer = createData.tenant || createData.customer || createData
const tenantId = String(customer.id || createData.tenantId || '')
if (!tenantId) throw new Error('创建响应未返回客户 ID')
this.context.customerId = tenantId
this.createdResources.customer = { tenantId, marker: this.marker, contactPhone: phone }
this.assert(module, '创建响应客户字段完整', createResponse, hasFields(customer, CUSTOMER_FIELDS), `missing=${CUSTOMER_FIELDS.filter((f) => !own(customer, f)).join(',') || 'none'}`)
this.assert(module, '新客户基础字段正确', createResponse, customer.tenantName === payload.tenantName && customer.contactPhone === phone && customer.status === 'PROFILE_REQUIRED', `tenantId=${tenantId}, status=${customer.status}`)
const ownerProvisioningOptions = { scenario: 'customer-owner-provisioning', deploymentBlocker: true }
this.assert(module, '创建客户生成初始密码', createResponse,
typeof createData.temporaryPassword === 'string' && createData.temporaryPassword.length >= 8
&& /[A-Za-z]/.test(createData.temporaryPassword) && /\d/.test(createData.temporaryPassword),
`generated=${typeof createData.temporaryPassword === 'string'}, length=${createData.temporaryPassword?.length || 0}`,
ownerProvisioningOptions)
this.assert(module, '管理员用户名默认使用联系电话', createResponse,
createData.administratorUsername === phone,
`matches=${createData.administratorUsername === phone}`,
ownerProvisioningOptions)
this.assert(module, '待完善客户的审核字段保持为空', createResponse,
customer.unifiedSocialCreditCode == null && customer.approvedAt == null,
`creditCode=${customer.unifiedSocialCreditCode ?? 'omitted'}, approvedAt=${customer.approvedAt ?? 'omitted'}`)
await this.runCustomerDimensionRules(tenantId)
const listResponse = await this.request('GET', `/api/platform/v1/iam/tenants?page=1&size=20&keyword=${encodeURIComponent(payload.tenantName)}`)
const page = this.apiSuccess(module, '查询客户列表', listResponse); const listed = page?.records?.find((item) => String(item.id) === tenantId)
this.assert(module, '列表包含新客户且字段完整', listResponse, listed && hasFields(listed, CUSTOMER_FIELDS), `found=${Boolean(listed)}, missing=${listed ? CUSTOMER_FIELDS.filter((f) => !own(listed, f)).join(',') || 'none' : 'all'}`)
const detailPath = `/api/platform/v1/iam/tenants/${tenantId}`
const detailResponse = await this.request('GET', detailPath); const detail = this.apiSuccess(module, '查询客户详情', detailResponse)
this.assert(module, '客户详情基本信息正确', detailResponse, detail && detail.tenantName === payload.tenantName && detail.contactPhone === phone && detail.internalRemark === payload.internalRemark, `tenantId=${tenantId}`)
const editedPayload = { ...payload, tenantName: `${payload.tenantName}-已编辑`, contactName: '接口验收联系人-已编辑', contactEmail: `edited-${payload.contactEmail}`, paymentTermDays: 45, internalRemark: `${this.marker}-EDITED` }
const editResponse = await this.request('PUT', detailPath, editedPayload); const edited = this.apiSuccess(module, '编辑客户', editResponse)
this.assert(module, '客户编辑正确回显', editResponse, edited?.tenantName === editedPayload.tenantName && edited?.contactEmail === editedPayload.contactEmail && Number(edited?.paymentTermDays) === 45, `tenantId=${tenantId}`)
const persistedResponse = await this.request('GET', detailPath); const persisted = this.apiSuccess(module, '回查客户编辑结果', persistedResponse)
this.assert(module, '客户编辑已持久化', persistedResponse, persisted?.tenantName === editedPayload.tenantName && persisted?.internalRemark === editedPayload.internalRemark, `tenantId=${tenantId}`)
const qualificationResponse = await this.request('GET', `${detailPath}/qualifications`); const qualifications = this.apiSuccess(module, '业务概况查询资质', qualificationResponse)
this.assert(module, '资质概况结构正确', qualificationResponse, Array.isArray(qualifications), `count=${Array.isArray(qualifications) ? qualifications.length : 'n/a'}`)
const memberResponse = await this.request('GET', `${detailPath}/members`); const members = this.apiSuccess(module, '业务概况查询成员', memberResponse)
const owner = members?.find?.((item) => item.memberType === 'OWNER')
this.assert(module, '联系电话账号是企业管理员', memberResponse,
owner?.username === phone,
`ownerFound=${Boolean(owner)}, matches=${owner?.username === phone}`,
ownerProvisioningOptions)
const countResponse = await this.request('GET', `/api/platform/v1/orders/first-leg/tenant-counts?tenantIds=${tenantId}`); const counts = this.apiSuccess(module, '业务概况查询运单数量', countResponse); const count = counts?.find?.((item) => String(item.tenantId) === tenantId)
this.assert(module, '业务概况返回当前客户运单数', countResponse, count && Number.isFinite(Number(count.orderCount)), `orderCount=${count?.orderCount ?? 'missing'}`)
const ordersResponse = await this.request('GET', `/api/platform/v1/orders/first-leg?tenantId=${tenantId}&page=1&size=10`); const orders = this.apiSuccess(module, '查询客户运单记录', ordersResponse)
this.assert(module, '运单记录分页结构正确', ordersResponse, Array.isArray(orders?.records) && Number.isFinite(Number(orders?.total)), `records=${orders?.records?.length ?? 'n/a'}, total=${orders?.total ?? 'n/a'}`)
const archiveOptions = { scenario: 'customer-draft-archive', deploymentBlocker: true }
const archiveResponse = await this.request('POST', `${detailPath}/archive`)
const archived = this.apiSuccess(module, '归档测试客户', archiveResponse, `tenantId=${tenantId}`, archiveOptions)
if (archived) {
this.assert(module, '测试客户归档后处于停用状态', archiveResponse,
archived.status === 'DISABLED', `status=${archived.status ?? 'missing'}`, archiveOptions)
}
}
async run() {
const loginResponse = await this.request('POST', '/api/auth/login', { username: this.options.username, password: this.options.password }, '')
const login = this.apiSuccess('authentication', '平台管理员登录', loginResponse)
if (!login?.accessToken) this.fatal = '平台管理员登录失败,未执行业务模块'
else {
this.token = login.accessToken
const handlers = { customers: () => this.runCustomers() }
for (const module of this.options.modules) {
let handler = handlers[module]
if (!handler) {
try {
const extension = await import(new URL(`./admin-test-env-modules/${module}.mjs`, import.meta.url))
if (typeof extension.run !== 'function') throw new Error('模块必须导出 async function run(context)')
handler = () => extension.run(this)
} catch (error) {
const reason = error instanceof Error ? error.message : String(error)
this.addStep(module, '模块注册检查', null, false, `无法加载模块: ${reason}`)
this.modules.push({ name: module, success: false, passed: 0, failed: 1, fatal: reason })
continue
}
}
await this.executeModule(module, handler)
}
}
const failed = this.steps.filter((step) => !step.passed).length
return {
reportName: '后台管理测试环境 HTTP API 验收', marker: this.marker,
gateway: this.options.gateway, generatedAt: new Date().toISOString(), startedAt: this.startedAt,
success: this.fatal === null && failed === 0, fatal: this.fatal,
summary: { total: this.steps.length, passed: this.steps.length - failed, failed },
modules: this.modules, scenarios: this.scenarios, deploymentBlockers: this.deploymentBlockers,
createdResources: this.createdResources,
excluded: [
{ scope: 'dashboard', reason: '首页为静态演示数据,不调用后端 API' },
{ scope: 'hidden-prototypes', reason: '隐藏原型页面尚无已确认后端契约' },
],
securityNote: '报告不保存登录密码、Access Token、Refresh Token、图片二进制或 OCR 原文全文。',
steps: this.steps,
}
}
}
async function main() {
const options = parseArguments(process.argv.slice(2))
const timestamp = new Date().toISOString().replace(/\D/g, '').slice(0, 14)
const outputPath = resolve(options.output || `reports/admin-test-env-acceptance-${timestamp}.json`)
const report = await new AcceptanceRun(options).run()
await mkdir(dirname(outputPath), { recursive: true })
await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8')
console.log(`REPORT=${outputPath}`)
console.log(`RESULT=${report.success ? 'PASS' : 'FAIL'} total=${report.summary.total} passed=${report.summary.passed} failed=${report.summary.failed}`)
process.exitCode = report.success ? 0 : 1
}
main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1 })
@@ -0,0 +1,21 @@
# Admin HTTP 验收模块协作表
所有对话共用入口 `../admin-test-env-acceptance.mjs`,原则上只修改自己负责的模块文件。
| 对话 ID | 主题 | 负责文件 / 场景 |
|---|---|---|
| 当前总控对话 | Admin 全量脚本 | 总入口、`system.mjs`、`master-data.mjs`、`downloads.mjs`、最终集成与全量报告 |
| `019fdbd3-f24f-7903-9124-de79c3ba37d0` | 客户尺寸规则 | 总入口内 `customer-dimension-rules` 子场景;不得改其他 customers 流程 |
| `019fdb7d-8e29-7c40-bda6-5a29dcb37971` | OCR 扫描 | `inbound-weight-ocr.mjs` |
| `019fdac1-ae04-7032-bac0-968497ea54ad` | 墨家测量 | `inbound-measurement.mjs` |
| `019fd5af-2e85-74b0-94e5-98c1b9ea4d3e` | 船期价格表 | `pricing.mjs` |
| `019fdbb1-ca56-7263-8914-a29d96cc5177` | 客户端创建订单 | `orders.mjs`,负责创建隔离测试订单并补 Admin 状态流程 |
## 统一交付要求
1. 模块导出 `async function run(context)`,接口见 `README.md`。
2. 测试数据必须带 `context.marker`,不得修改无关既有订单或主数据。
3. 模块完成后运行 `node --check scripts/admin-test-env-modules/<module>.mjs`。
4. 再运行 `node scripts/admin-test-env-acceptance.mjs --modules <module>`,报告写入 `reports/`。
5. 报告不得包含密码、Token、图片二进制或 OCR 原文全文。
6. 未部署的新契约必须记录 `deploymentBlocker`,不得跳过或模拟成功。
+37
View File
@@ -0,0 +1,37 @@
# Admin 测试环境 HTTP 模块
总入口是 `../admin-test-env-acceptance.mjs`。每个业务模块使用独立的同名 `.mjs` 文件,避免多个对话同时修改总入口。
例如 `--modules system,inbound-weight-ocr` 会依次加载:
- `system.mjs`
- `inbound-weight-ocr.mjs`
模块只需导出:
```js
export async function run(context) {
const response = await context.request('GET', '/api/platform/v1/example')
const data = context.apiSuccess('example', '查询示例', response)
context.assert('example', '示例结构正确', response, Array.isArray(data), `count=${data?.length ?? 'n/a'}`)
}
```
## 可用上下文
- `request(method, path, body?)`:JSON HTTP 请求,自动使用平台 Token。
- `requestBinary(method, path)`:文件下载,不把二进制写入报告。
- `apiSuccess(module, name, response, evidence?, options?)`:断言 HTTP 200 + body code `200`,成功时返回 data。
- `assert(module, name, response, condition, evidence, options?)`:业务断言。
- `expectConflict(module, name, response, scenario)`:断言并发冲突。
- `addStep(...)`:写入统一步骤报告。
- `marker`:本轮唯一测试标记。
- `createdResources`:登记测试资源 ID。
- `context`:模块间共享少量 ID;模块应尽量独立。
## 约束
- 不记录密码、Token、图片二进制或 OCR 原文全文。
- 测试数据必须带 `context.marker`;可删除的清理,不能删除的最终停用。
- 缺少已确认契约时记录失败或 `deploymentBlocker`,不得模拟成功。
- 一个模块只修改自己的 `.mjs`,不要修改其他模块文件。
@@ -0,0 +1,7 @@
export async function run(context) {
const module = 'replace-with-module-name'
const response = await context.request('GET', '/api/platform/v1/replace-with-real-api')
const data = context.apiSuccess(module, '查询模块列表', response)
context.assert(module, '列表结构正确', response, Array.isArray(data?.records),
`records=${data?.records?.length ?? 'n/a'}`)
}
@@ -0,0 +1,43 @@
function wait(milliseconds) { return new Promise((resolve) => setTimeout(resolve, milliseconds)) }
function isXlsx(response) {
return response.http === 200 && response.bytes?.length > 4 && response.bytes[0] === 0x50 && response.bytes[1] === 0x4b
}
export async function run(context) {
const module = 'downloads'
const createResponse = await context.request('POST', '/api/platform/v1/files/export-tasks', {
exportType: 'CUSTOMER_LIST', format: 'XLSX', taskName: `Admin全量验收-${context.marker}`,
parameters: { keyword: context.marker },
})
const task = context.apiSuccess(module, '创建客户列表导出任务', createResponse)
const taskId = task?.id == null ? '' : String(task.id)
context.assert(module, '导出任务创建字段正确', createResponse,
taskId && task?.exportType === 'CUSTOMER_LIST' && task?.format === 'XLSX',
`taskId=${taskId || 'missing'}, status=${task?.status ?? 'missing'}`)
if (!taskId) throw new Error('导出任务创建失败')
context.createdResources.exportTask = { id: taskId }
const listResponse = await context.request('GET', '/api/platform/v1/files/export-tasks?page=1&size=20&exportType=CUSTOMER_LIST')
const page = context.apiSuccess(module, '查询下载中心任务列表', listResponse)
context.assert(module, '下载中心列表包含新任务', listResponse,
page?.records?.some((item) => String(item.id) === taskId), `records=${page?.records?.length ?? 'n/a'}`)
let latestResponse = createResponse
let latest = task
for (let attempt = 0; attempt < 30 && !['SUCCEEDED', 'FAILED', 'EXPIRED'].includes(latest?.status); attempt += 1) {
await wait(2000)
latestResponse = await context.request('GET', `/api/platform/v1/files/export-tasks/${encodeURIComponent(taskId)}`)
if (latestResponse.http === 200 && String(latestResponse.body?.code) === '200') latest = latestResponse.body.data
}
context.addStep(module, '轮询导出任务直到终态', latestResponse,
latest?.status === 'SUCCEEDED', `status=${latest?.status ?? 'missing'}, processedRows=${latest?.processedRows ?? 'n/a'}`)
if (latest?.status !== 'SUCCEEDED') return
const detailResponse = await context.request('GET', `/api/platform/v1/files/export-tasks/${encodeURIComponent(taskId)}`)
const detail = context.apiSuccess(module, '查询导出任务详情', detailResponse)
context.assert(module, '导出详情行数和文件元数据正确', detailResponse,
String(detail?.id) === taskId && Number.isFinite(Number(detail?.processedRows)) && Number(detail?.sizeBytes) > 0,
`processedRows=${detail?.processedRows ?? 'n/a'}, sizeBytes=${detail?.sizeBytes ?? 'n/a'}`)
const downloadResponse = await context.requestBinary('GET', `/api/platform/v1/files/export-tasks/${encodeURIComponent(taskId)}/download`)
context.addStep(module, '下载并校验客户列表 XLSX', downloadResponse, isXlsx(downloadResponse),
`bytes=${downloadResponse.bytes?.length ?? 0}, contentType=${downloadResponse.contentType || 'missing'}`)
}
@@ -0,0 +1,344 @@
import { readFile } from 'node:fs/promises'
const MODULE = 'inbound-measurement'
const SCENARIO = 'mojia-carton-measurement'
const OPTIONS = { scenario: SCENARIO, deploymentBlocker: true }
function asId(value) { return value == null ? '' : String(value) }
function asNumber(value) { return Number(value) }
function closeTo(actual, expected, tolerance = 0.000001) {
return Number.isFinite(asNumber(actual)) && Math.abs(asNumber(actual) - expected) <= tolerance
}
function ceiling(value, scale) {
const factor = 10 ** scale
return Math.ceil((value - Number.EPSILON) * factor) / factor
}
function query(tenantId) { return `tenantId=${encodeURIComponent(tenantId)}` }
function validPdf(response) {
return response.http === 200 && response.contentType.toLowerCase().includes('pdf')
&& response.bytes?.length > 4 && new TextDecoder().decode(response.bytes.slice(0, 4)) === '%PDF'
}
function findIsolatedInbound(context) {
const roots = [context.createdResources, context.context]
const visited = new Set()
const candidates = []
function visit(value, inheritedMarker = '') {
if (!value || typeof value !== 'object' || visited.has(value)) return
visited.add(value)
const marker = String(value.marker || value.testMarker || inheritedMarker || '')
const inboundId = asId(value.inboundId || value.inbound?.id || value.inbound?.inboundId)
const tenantId = asId(value.tenantId || value.customerId || value.inbound?.tenantId)
if (marker === context.marker && inboundId && tenantId) {
candidates.push({ inboundId, tenantId, marker, source: value })
}
for (const [key, child] of Object.entries(value)) {
if (key === 'temporaryPassword' || key === 'token') continue
if (child && typeof child === 'object') visit(child, marker)
}
}
roots.forEach((root) => visit(root))
return candidates[0] || null
}
async function multipartRequest(context, path, data) {
const form = new FormData()
form.append('data', new Blob([JSON.stringify(data)], { type: 'application/json' }))
const image = await readFile(new URL('../../../apps/pda-uniapp/static/c7.png', import.meta.url))
form.append('image', new Blob([image], { type: 'image/png' }), 'carton-evidence.png')
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 content = await response.text()
let body = null
try { body = content ? JSON.parse(content) : null } catch { /* Preserve only a bounded error string. */ }
return {
method: 'POST', path, http: response.status, body,
raw: body === null ? content.slice(0, 500) : null,
requestId: body?.requestId || response.headers.get('x-request-id'),
traceId: body?.traceId || response.headers.get('x-trace-id'),
}
} catch (error) {
return { method: 'POST', path, http: 0, body: null, raw: error instanceof Error ? error.message : String(error), requestId: null, traceId: null }
}
}
async function requestWithHeaders(context, method, path, body, extraHeaders) {
try {
const response = await fetch(`${context.options.gateway}${path}`, {
method,
headers: {
Accept: 'application/json', Authorization: `Bearer ${context.token}`,
'Content-Type': 'application/json; charset=utf-8', ...extraHeaders,
},
body: JSON.stringify(body), signal: AbortSignal.timeout(30_000),
})
const content = await response.text()
let payload = null
try { payload = content ? JSON.parse(content) : null } catch { /* Preserve only a bounded error string. */ }
return {
method, path, http: response.status, body: payload,
raw: payload === null ? content.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: error instanceof Error ? error.message : String(error), requestId: null, traceId: null }
}
}
function finishScenario(context, start) {
const steps = context.steps.slice(start)
context.scenarios.push({
name: SCENARIO, module: MODULE, success: steps.every((step) => step.passed),
deploymentBlocked: steps.some((step) => step.deploymentBlocker),
passed: steps.filter((step) => step.passed).length,
failed: steps.filter((step) => !step.passed).length,
})
}
export async function run(context) {
const start = context.steps.length
const isolated = findIsolatedInbound(context)
if (!isolated) {
context.addStep(MODULE, '取得隔离的待测量入库单', null, false,
`orders 模块未提供 marker=${context.marker} 的 inboundId/tenantId;为避免修改随机历史订单,本模块停止写入`, OPTIONS)
finishScenario(context, start)
return
}
const { inboundId, tenantId } = isolated
const detailPath = `/api/platform/inbounds/${inboundId}?${query(tenantId)}`
const inboundResponse = await context.request('GET', detailPath)
const inboundDetail = context.apiSuccess(MODULE, '查询隔离入库单详情', inboundResponse,
`inboundId=${inboundId}, tenantId=${tenantId}`, OPTIONS)
const summary = inboundDetail?.summary
const provenanceOk = asId(summary?.id) === inboundId && asId(summary?.tenantId) === tenantId
&& ['WAIT_MEASURE', 'MEASURING'].includes(summary?.processStatus)
context.assert(MODULE, '隔离入库单身份与可测量状态正确', inboundResponse, provenanceOk,
`identity=${asId(summary?.id) === inboundId && asId(summary?.tenantId) === tenantId}, status=${summary?.processStatus ?? 'missing'}`, OPTIONS)
if (!provenanceOk) {
finishScenario(context, start)
return
}
const inboundCode = String(summary.inboundCode || '')
if (!inboundCode) {
context.assert(MODULE, '箱码生成前置数据完整', inboundResponse, false,
'inboundCode=missing', OPTIONS)
finishScenario(context, start)
return
}
const labelPath = `/api/platform/inbounds/${inboundId}/carton-label-pdf?${query(tenantId)}`
const labelResponse = await context.requestBinary('POST', labelPath)
const pdfOk = validPdf(labelResponse)
context.assert(MODULE, '生成箱唛 PDF 并冻结稳定箱码', labelResponse, pdfOk,
`contentType=${labelResponse.contentType || 'missing'}, bytes=${labelResponse.bytes?.length ?? 0}`, OPTIONS)
if (!pdfOk) {
finishScenario(context, start)
return
}
const frozenInboundResponse = await context.request('GET', detailPath)
const frozenInbound = context.apiSuccess(MODULE, '首次打印后回查冻结箱数', frozenInboundResponse, '', OPTIONS)
const frozenSummary = frozenInbound?.summary
const originalPackageCount = Number(frozenSummary?.totalPackageCount)
context.assert(MODULE, '首次打印按订单货物冻结原始总箱数', frozenInboundResponse,
frozenSummary?.inboundCode === inboundCode && Number.isInteger(originalPackageCount) && originalPackageCount >= 1,
`inboundCodeStable=${frozenSummary?.inboundCode === inboundCode}, totalPackageCount=${frozenSummary?.totalPackageCount ?? 'missing'}`, OPTIONS)
if (!Number.isInteger(originalPackageCount) || originalPackageCount < 1) {
finishScenario(context, start)
return
}
const boxCode = `${inboundCode}#${originalPackageCount}-1`
const reprintResponse = await context.requestBinary('POST', labelPath)
context.assert(MODULE, '重复整单打印复用已冻结箱码映射', reprintResponse, validPdf(reprintResponse),
`boxCode=${boxCode}, contentType=${reprintResponse.contentType || 'missing'}, bytes=${reprintResponse.bytes?.length ?? 0}`, OPTIONS)
const partialPrintPath = `${labelPath}&cartonIndex=1`
const partialPrintResponse = await context.requestBinary('POST', partialPrintPath)
context.assert(MODULE, '指定第1箱补打复用已冻结箱码映射', partialPrintResponse, validPdf(partialPrintResponse),
`boxCode=${boxCode}, cartonIndex=1, contentType=${partialPrintResponse.contentType || 'missing'}, bytes=${partialPrintResponse.bytes?.length ?? 0}`, OPTIONS)
if (!validPdf(reprintResponse) || !validPdf(partialPrintResponse)) {
finishScenario(context, start)
return
}
const scanPath = `/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/carton-scans?${query(tenantId)}`
const firstDimensions = { length: 60.1, width: 40.2, height: 30.3, weight: 12.5 }
const firstScanResponse = await multipartRequest(context, scanPath, {
boxCode, ...firstDimensions, machineNo: 'MJ-ADMIN-E2E-01', measuredAt: new Date().toISOString().slice(0, 19),
})
const firstScan = context.apiSuccess(MODULE, '模拟墨家首次扫描单箱', firstScanResponse,
`boxCode=${boxCode}, machineNo=MJ-ADMIN-E2E-01`, OPTIONS)
context.assert(MODULE, '首次扫描新增一条且默认代表1箱', firstScanResponse,
firstScan?.created === true && asId(firstScan?.boxCode) === boxCode
&& Number(firstScan?.confirmedPackageCount) === 1 && asId(firstScan?.itemId),
`created=${firstScan?.created}, confirmedPackageCount=${firstScan?.confirmedPackageCount ?? 'missing'}`, OPTIONS)
context.assert(MODULE, '重复打印及指定箱补打后原箱码仍可解析', firstScanResponse,
firstScan?.created === true && firstScan?.boxCode === boxCode,
`boxCodeStable=${firstScan?.boxCode === boxCode}, resolvedInboundId=${inboundId}`, OPTIONS)
if (!firstScan?.itemId || !firstScan?.measurementId) {
finishScenario(context, start)
return
}
const measurementPath = `/api/platform/v1/first-leg/inbounds/${inboundId}/measurement?${query(tenantId)}`
const firstQueryResponse = await context.request('GET', measurementPath)
const firstQuery = context.apiSuccess(MODULE, '查询箱级测量详情', firstQueryResponse,
`measurementId=${asId(firstScan.measurementId)}`, OPTIONS)
const firstItem = firstQuery?.items?.find((item) => asId(item.id) === asId(firstScan.itemId))
context.assert(MODULE, '箱级查询回显箱码、尺寸、图片与设备信息', firstQueryResponse,
firstItem?.boxCode === boxCode && asId(firstItem?.intelligentImageFileId)
&& firstItem?.machineNo === 'MJ-ADMIN-E2E-01'
&& closeTo(firstItem?.lengthCm, firstDimensions.length)
&& closeTo(firstItem?.widthCm, firstDimensions.width)
&& closeTo(firstItem?.heightCm, firstDimensions.height),
`itemFound=${Boolean(firstItem)}, imageFileId=${asId(firstItem?.intelligentImageFileId) || 'missing'}`, OPTIONS)
const countResponse = await context.request('PUT',
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/items/${firstScan.itemId}/confirmed-package-count?${query(tenantId)}`,
{ measurementVersion: Number(firstScan.measurementVersion), confirmedPackageCount: originalPackageCount })
const countResult = context.apiSuccess(MODULE, '人工确认当前测量代表箱数', countResponse,
`confirmedPackageCount=${originalPackageCount}`, OPTIONS)
context.assert(MODULE, '人工箱数更新且测量版本递增', countResponse,
Number(countResult?.confirmedPackageCount) === originalPackageCount
&& Number(countResult?.measurementVersion) === Number(firstScan.measurementVersion) + 1,
`version=${countResult?.measurementVersion ?? 'missing'}`, OPTIONS)
const replacement = { length: 61.1, width: 41.2, height: 31.3, weight: 13.6 }
const rescanResponse = await multipartRequest(context, scanPath, {
boxCode, ...replacement, machineNo: 'MJ-ADMIN-E2E-02', measuredAt: new Date().toISOString().slice(0, 19),
})
const rescan = context.apiSuccess(MODULE, '同箱号重新扫描覆盖当前记录', rescanResponse,
`boxCode=${boxCode}, machineNo=MJ-ADMIN-E2E-02`, OPTIONS)
context.assert(MODULE, '覆盖保留同一条记录和人工箱数', rescanResponse,
rescan?.created === false && asId(rescan?.itemId) === asId(firstScan.itemId)
&& Number(rescan?.confirmedPackageCount) === originalPackageCount
&& closeTo(rescan?.lengthCm, replacement.length),
`sameItem=${asId(rescan?.itemId) === asId(firstScan.itemId)}, confirmedPackageCount=${rescan?.confirmedPackageCount ?? 'missing'}`, OPTIONS)
const measuredResponse = await context.request('GET', measurementPath)
const measured = context.apiSuccess(MODULE, '回查覆盖后的尺寸与体积结果', measuredResponse, '', OPTIONS)
const measuredItem = measured?.items?.find((item) => asId(item.id) === asId(firstScan.itemId))
const unitVolume = ceiling(replacement.length * replacement.width * replacement.height / 1_000_000, 4)
const totalVolume = ceiling(unitVolume * originalPackageCount, 4)
const volumeWeight = ceiling(totalVolume * 167, 3)
const girth = ceiling(replacement.length + 2 * (replacement.width + replacement.height), 3)
context.assert(MODULE, '覆盖尺寸、箱数和服务端体积公式正确', measuredResponse,
measuredItem && closeTo(measuredItem.lengthCm, replacement.length)
&& closeTo(measuredItem.widthCm, replacement.width) && closeTo(measuredItem.heightCm, replacement.height)
&& Number(measuredItem.confirmedPackageCount) === originalPackageCount
&& closeTo(measuredItem.unitVolumeCbm, unitVolume) && closeTo(measuredItem.totalVolumeCbm, totalVolume)
&& closeTo(measuredItem.volumeWeightKg, volumeWeight) && closeTo(measuredItem.girthCm, girth)
&& closeTo(measured.actualVolumeCbm, totalVolume) && closeTo(measured.volumeWeightKg, volumeWeight),
`unitVolume=${unitVolume}, totalVolume=${totalVolume}, volumeWeight=${volumeWeight}, girth=${girth}`, OPTIONS)
let inboundDetailId = measuredItem?.inboundDetailId
if (!inboundDetailId) {
const orderId = asId(frozenSummary?.orderId || context.createdResources?.orders?.orderId)
const orderDetailResponse = await context.request('GET',
`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}?${query(tenantId)}`)
const orderDetail = context.apiSuccess(MODULE, '测量详情缺少货物ID时回查隔离订单', orderDetailResponse,
`orderId=${orderId}`, OPTIONS)
const cargoes = Array.isArray(orderDetail?.cargoes) ? [...orderDetail.cargoes] : []
cargoes.sort((left, right) => Number(left?.itemNo ?? Number.MAX_SAFE_INTEGER)
- Number(right?.itemNo ?? Number.MAX_SAFE_INTEGER) || asId(left?.id).localeCompare(asId(right?.id)))
const firstCargo = cargoes[0]
const cargoBelongsToRun = asId(orderDetail?.order?.id) === orderId
&& String(firstCargo?.cargoRemark || '').includes(context.marker)
inboundDetailId = cargoBelongsToRun ? firstCargo?.id : null
context.assert(MODULE, '草稿绑定本轮箱码对应的隔离货物', orderDetailResponse,
cargoBelongsToRun && asId(inboundDetailId),
`cargoCount=${cargoes.length}, markerMatched=${cargoBelongsToRun}, inboundDetailId=${asId(inboundDetailId) || 'missing'}`, OPTIONS)
}
if (!inboundDetailId || !measuredItem?.intelligentImageFileId) {
context.assert(MODULE, '草稿保存前置字段完整', measuredResponse, false,
`inboundDetailId=${asId(inboundDetailId) || 'missing'}, imageFileId=${asId(measuredItem?.intelligentImageFileId) || 'missing'}`, OPTIONS)
finishScenario(context, start)
return
}
const draftPayload = {
measurementId: measured.measurementId, version: Number(measured.version),
originalDocumentNo: context.marker, originalGrossWeightKg: replacement.weight * originalPackageCount,
originalVolumeCbm: totalVolume, actualGoodsName: `墨家测量验收-${context.marker.slice(-12)}`,
items: [{
inboundDetailId, itemNo: measuredItem.itemNo,
boxSpecification: measuredItem.boxSpecification, packageCount: originalPackageCount,
lengthCm: replacement.length, widthCm: replacement.width, heightCm: replacement.height,
unitWeightKg: replacement.weight, intelligentImageFileId: measuredItem.intelligentImageFileId,
overLimitWarning: measuredItem.overLimitWarning ?? null,
outerLengthCm: measuredItem.outerLengthCm ?? null, outerWidthCm: measuredItem.outerWidthCm ?? null,
outerHeightCm: measuredItem.outerHeightCm ?? null, remark: context.marker,
}],
}
const draftResponse = await context.request('PUT',
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/draft?${query(tenantId)}`, draftPayload)
const draft = context.apiSuccess(MODULE, '保存同一测量批次草稿', draftResponse,
`measurementId=${asId(measured.measurementId)}, items=1`, OPTIONS)
context.assert(MODULE, '草稿沿用批次并递增版本', draftResponse,
asId(draft?.measurementId) === asId(measured.measurementId)
&& Number(draft?.version) === Number(measured.version) + 1 && draft?.status === 'DRAFT',
`status=${draft?.status ?? 'missing'}, version=${draft?.version ?? 'missing'}`, OPTIONS)
if (!draft?.measurementId) {
finishScenario(context, start)
return
}
const submitResponse = await context.request('POST',
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/submit?${query(tenantId)}`,
{ measurementId: draft.measurementId, version: Number(draft.version) })
const submitted = context.apiSuccess(MODULE, '提交测量审核', submitResponse,
`measurementId=${asId(draft.measurementId)}`, OPTIONS)
context.assert(MODULE, '提交后进入待测量审核状态', submitResponse,
submitted?.processStatus === 'MEASURE_PENDING' && submitted?.measureReviewStatus === 'PENDING',
`processStatus=${submitted?.processStatus ?? 'missing'}, reviewStatus=${submitted?.measureReviewStatus ?? 'missing'}`, OPTIONS)
const rejectedScanResponse = await multipartRequest(context, scanPath, {
boxCode, ...replacement, machineNo: 'MJ-ADMIN-E2E-03', measuredAt: new Date().toISOString().slice(0, 19),
})
const rejected = rejectedScanResponse.http === 200 && String(rejectedScanResponse.body?.code) === 'A0409'
&& String(rejectedScanResponse.body?.message || '').includes('测量已提交')
context.assert(MODULE, '测量提交后拒绝设备覆盖并返回中文提示', rejectedScanResponse, rejected,
`HTTP=${rejectedScanResponse.http}, code=${rejectedScanResponse.body?.code ?? 'missing'}, chineseMessage=${String(rejectedScanResponse.body?.message || '').includes('测量已提交')}`, OPTIONS)
const pendingInboundResponse = await context.request('GET', detailPath)
const pendingInbound = context.apiSuccess(MODULE, '审核前读取最新入库单版本', pendingInboundResponse, '', OPTIONS)
const pendingSummary = pendingInbound?.summary
context.assert(MODULE, '审核前状态和版本有效', pendingInboundResponse,
pendingSummary?.processStatus === 'MEASURE_PENDING' && pendingSummary?.measureReviewStatus === 'PENDING'
&& Number.isInteger(Number(pendingSummary?.version)),
`status=${pendingSummary?.processStatus ?? 'missing'}, version=${pendingSummary?.version ?? 'missing'}`, OPTIONS)
const approvalPath = `/api/platform/v1/inbounds/${inboundId}/measurements/approve?${query(tenantId)}`
const approvalResponse = await requestWithHeaders(context, 'POST', approvalPath, {
measurementId: draft.measurementId, expectedVersion: Number(pendingSummary?.version), reason: null,
}, { 'Idempotency-Key': `${context.marker}-mojia-approve`.slice(0, 128) })
context.apiSuccess(MODULE, 'Admin 审核通过墨家测量', approvalResponse,
`measurementId=${asId(draft.measurementId)}`, OPTIONS)
const finalResponse = await context.request('GET', measurementPath)
const finalMeasurement = context.apiSuccess(MODULE, '查询审核后的最终测量状态', finalResponse, '', OPTIONS)
context.assert(MODULE, '审核后入库与测量状态闭环且结果未变化', finalResponse,
finalMeasurement?.processStatus === 'INBOUNDED' && finalMeasurement?.measureReviewStatus === 'APPROVED'
&& finalMeasurement?.measurementStatus === 'APPROVED'
&& Number(finalMeasurement?.actualPackageCount) === originalPackageCount
&& closeTo(finalMeasurement?.actualVolumeCbm, totalVolume)
&& closeTo(finalMeasurement?.volumeWeightKg, volumeWeight),
`processStatus=${finalMeasurement?.processStatus ?? 'missing'}, measurementStatus=${finalMeasurement?.measurementStatus ?? 'missing'}, packages=${finalMeasurement?.actualPackageCount ?? 'missing'}`, OPTIONS)
context.createdResources.inboundMeasurement = {
marker: context.marker, tenantId, inboundId, measurementId: asId(draft.measurementId),
boxCode, finalStatus: finalMeasurement?.measurementStatus || null,
}
finishScenario(context, start)
}
@@ -0,0 +1,452 @@
import { readFile } from 'node:fs/promises'
import { basename } from 'node:path'
import process from 'node:process'
const MODULE = 'inbound-weight-ocr'
const SCENARIO = 'weight-image-recognition-and-confirmation'
const DEFAULT_IMAGE = 'C:/Users/BY-OY/Desktop/上传图片包/测量图片/测量图片/orig_1cf27695-b2c3-44b3-98e3-c0be8b80f79a.JPG'
const EDITABLE_STATUSES = new Set(['WAIT_MEASURE', 'MEASURING', 'MEASURE_REJECTED'])
const OCR_STATUSES = new Set(['RECOGNIZED', 'FAILED'])
const NEW_CONTRACT_OPTIONS = { scenario: SCENARIO, deploymentBlocker: true }
const SCENARIO_OPTIONS = { scenario: SCENARIO }
function id(value) {
return value == null ? '' : String(value)
}
function apiOk(response) {
return response?.http === 200 && String(response?.body?.code) === '200'
}
function closeNumber(actual, expected, tolerance = 0.0005) {
return Number.isFinite(Number(actual)) && Math.abs(Number(actual) - Number(expected)) < tolerance
}
function expectedConflict(response) {
return response?.http === 200 && ['A0409', '409'].includes(String(response?.body?.code))
}
function expectedRejection(response) {
return response?.http === 200 && response?.body && String(response.body.code) !== '200'
}
function markerVisible(detail, marker) {
const values = [
detail?.publicRemark,
detail?.summary?.inboundCode,
detail?.summary?.orderNo,
detail?.summary?.customerName,
detail?.summary?.companyName,
...(Array.isArray(detail?.internalNotes) ? detail.internalNotes.map((note) => note?.content) : []),
]
return values.some((value) => typeof value === 'string' && value.includes(marker))
}
function candidateValues(context) {
const shared = context.context || {}
return [
shared.inboundWeightOcr,
shared.ocrInbound,
shared.testInbound,
shared.createdInbound,
shared.inboundId && shared.tenantId
? { inboundId: shared.inboundId, tenantId: shared.tenantId, marker: shared.inboundMarker }
: null,
].filter(Boolean)
}
async function resolveMarkedInbound(context) {
for (const candidate of candidateValues(context)) {
const tenantId = id(candidate.tenantId || candidate.customerId)
const inboundId = id(candidate.inboundId || candidate.id)
if (!tenantId || !inboundId) continue
const response = await context.request(
'GET', `/api/platform/inbounds/${encodeURIComponent(inboundId)}?tenantId=${encodeURIComponent(tenantId)}`,
)
const detail = apiOk(response) ? response.body.data : null
const marked = markerVisible(detail, context.marker)
const editable = EDITABLE_STATUSES.has(detail?.summary?.processStatus)
if (detail && marked && editable) return { tenantId, inboundId, detail, response }
}
const filters = [
`inboundCode=${encodeURIComponent(context.marker)}`,
`orderNo=${encodeURIComponent(context.marker)}`,
]
for (const filter of filters) {
const pageResponse = await context.request('GET', `/api/platform/inbounds?page=1&size=20&${filter}`)
if (!apiOk(pageResponse)) continue
for (const row of pageResponse.body.data?.records || []) {
const tenantId = id(row.tenantId)
const inboundId = id(row.id)
if (!tenantId || !inboundId || !EDITABLE_STATUSES.has(row.processStatus)) continue
const detailResponse = await context.request(
'GET', `/api/platform/inbounds/${encodeURIComponent(inboundId)}?tenantId=${encodeURIComponent(tenantId)}`,
)
const detail = apiOk(detailResponse) ? detailResponse.body.data : null
if (detail && markerVisible(detail, context.marker)) {
return { tenantId, inboundId, detail, response: detailResponse }
}
}
}
return null
}
async function multipart(context, path, file, token = context.token) {
const form = new FormData()
form.append('file', new Blob([file.bytes], { type: file.contentType }), file.name)
const headers = { Accept: 'application/json' }
if (token) headers.Authorization = `Bearer ${token}`
try {
const response = await fetch(`${context.options.gateway}${path}`, {
method: 'POST', headers, body: form, signal: AbortSignal.timeout(120_000),
})
const content = await response.text()
let payload = null
try { payload = content ? JSON.parse(content) : null } catch { /* Keep bounded diagnostic text only. */ }
return {
method: 'POST', path, http: response.status, body: payload,
raw: payload === null ? content.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: error instanceof Error ? error.message : String(error),
requestId: null, traceId: null,
}
}
}
function jpegDimensions(bytes) {
if (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8) return null
const standalone = new Set([0x01, 0xd8, 0xd9, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7])
const startsOfFrame = new Set([0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf])
let offset = 2
while (offset + 3 < bytes.length) {
while (offset < bytes.length && bytes[offset] !== 0xff) offset += 1
while (offset < bytes.length && bytes[offset] === 0xff) offset += 1
if (offset >= bytes.length) break
const marker = bytes[offset]
offset += 1
if (standalone.has(marker)) continue
if (offset + 1 >= bytes.length) break
const length = bytes[offset] * 256 + bytes[offset + 1]
if (length < 2 || offset + length > bytes.length) break
if (startsOfFrame.has(marker) && length >= 7) {
return {
height: bytes[offset + 3] * 256 + bytes[offset + 4],
width: bytes[offset + 5] * 256 + bytes[offset + 6],
}
}
offset += length
}
return null
}
function workspacePath(tenantId, inboundId) {
return `/api/platform/v1/first-leg/inbounds/${encodeURIComponent(inboundId)}/measurement?tenantId=${encodeURIComponent(tenantId)}`
}
function resultPath(inboundId, resultId) {
return `/api/platform/v1/first-leg/inbounds/${encodeURIComponent(inboundId)}/weight-results/${encodeURIComponent(resultId)}`
}
function confirmPath(tenantId, inboundId, resultId) {
return `${resultPath(inboundId, resultId)}/confirm?tenantId=${encodeURIComponent(tenantId)}`
}
function deletePath(tenantId, inboundId, resultId, version) {
return `${resultPath(inboundId, resultId)}?tenantId=${encodeURIComponent(tenantId)}&version=${version}`
}
async function createUploadedResult(context, target, image, overrides = {}) {
const imagePath = `/api/platform/v1/files/measurements/${encodeURIComponent(target.tenantId)}/${encodeURIComponent(target.inboundId)}`
const uploadResponse = await multipart(context, imagePath, image)
const upload = context.apiSuccess(MODULE, '上传称重图片并执行 OCR', uploadResponse,
`file=${image.name}, size=${image.bytes.length}`, SCENARIO_OPTIONS)
const fileId = id(upload?.fileId)
context.assert(MODULE, '上传响应保持文件 ID 字符串契约', uploadResponse,
Boolean(fileId) && typeof upload?.fileId === 'string',
`fileIdType=${typeof upload?.fileId}, status=${upload?.recognitionStatus ?? 'missing'}`, SCENARIO_OPTIONS)
context.assert(MODULE, 'OCR 上传响应字段完整且置信度受限', uploadResponse,
upload && OCR_STATUSES.has(upload.recognitionStatus)
&& Object.hasOwn(upload, 'weightValue') && Object.hasOwn(upload, 'weightUnit')
&& Object.hasOwn(upload, 'confidence')
&& (upload.confidence == null || (Number(upload.confidence) >= 0 && Number(upload.confidence) <= 1)),
`status=${upload?.recognitionStatus ?? 'missing'}, hasWeight=${upload?.weightValue != null}, confidence=${upload?.confidence ?? 'null'}`,
SCENARIO_OPTIONS)
if (!fileId) throw new Error('称重图片上传未返回 fileId,无法登记和清理测试资源')
let recognized = upload
if (overrides.retry) {
const retryPath = `${imagePath}/${encodeURIComponent(fileId)}/recognize`
const retryResponse = await context.request('POST', retryPath)
const retry = context.apiSuccess(MODULE, '重试当前称重图片 OCR', retryResponse,
`fileId=${fileId}`, SCENARIO_OPTIONS)
context.assert(MODULE, 'OCR 重试保持文件身份并返回识别契约', retryResponse,
id(retry?.fileId) === fileId && OCR_STATUSES.has(retry?.recognitionStatus)
&& Object.hasOwn(retry || {}, 'weightValue') && Object.hasOwn(retry || {}, 'confidence'),
`sameFile=${id(retry?.fileId) === fileId}, status=${retry?.recognitionStatus ?? 'missing'}`,
SCENARIO_OPTIONS)
if (retry) recognized = retry
}
const payload = {
fileId,
ocrProvider: `ADMIN_ACCEPTANCE_${context.marker.replace(/[^A-Za-z0-9]/g, '').slice(-24)}`,
ocrRawText: overrides.manual ? null : (recognized.ocrRawText || null),
ocrWeightValue: overrides.manual ? null : recognized.weightValue,
ocrWeightUnit: overrides.manual ? null : recognized.weightUnit,
confidence: overrides.manual ? null : recognized.confidence,
}
const createPath = `/api/platform/v1/first-leg/inbounds/${encodeURIComponent(target.inboundId)}/weight-results?tenantId=${encodeURIComponent(target.tenantId)}`
let createResponse = await context.request('POST', createPath, payload)
let result = context.apiSuccess(MODULE, overrides.manual ? '登记人工录入重量结果' : '登记 OCR 原值',
createResponse, `fileId=${fileId}`, SCENARIO_OPTIONS)
if (!result && !overrides.manual) {
createResponse = await context.request('POST', createPath, {
...payload, ocrRawText: null, ocrWeightValue: null, ocrWeightUnit: null, confidence: null,
})
result = context.apiSuccess(MODULE, 'OCR 原值登记失败后建立可清理人工结果', createResponse,
`fileId=${fileId}`, NEW_CONTRACT_OPTIONS)
}
const resultId = id(result?.id)
context.assert(MODULE, '重量结果 ID、文件 ID 和初始版本契约正确', createResponse,
Boolean(resultId) && typeof result?.id === 'string' && typeof result?.fileId === 'string'
&& id(result?.fileId) === fileId && Number(result?.version) === 0,
`resultIdType=${typeof result?.id}, fileIdType=${typeof result?.fileId}, version=${result?.version ?? 'missing'}`,
SCENARIO_OPTIONS)
if (!resultId) throw new Error(`文件 ${fileId} 已上传但重量结果登记失败,需要人工清理该测试文件`)
return { upload: recognized, fileId, result, resultId, version: Number(result.version), deleted: false }
}
async function verifyImages(context, target, item, image) {
const base = `/api/platform/v1/files/measurements/${encodeURIComponent(target.tenantId)}/${encodeURIComponent(target.inboundId)}/${encodeURIComponent(item.fileId)}`
const original = await context.requestBinary('GET', base)
context.assert(MODULE, '鉴权读取原始称重图片', original,
original.http === 200 && original.bytes.length === image.bytes.length
&& /^image\/(jpeg|png|bmp)/i.test(original.contentType),
`contentType=${original.contentType || 'missing'}, bytes=${original.bytes.length}`, SCENARIO_OPTIONS)
const thumbnail = await context.requestBinary('GET', `${base}/thumbnail`)
const dimensions = jpegDimensions(thumbnail.bytes)
context.assert(MODULE, '缩略图返回受限尺寸 JPEG', thumbnail,
thumbnail.http === 200 && /^image\/jpeg/i.test(thumbnail.contentType)
&& thumbnail.bytes.length > 0 && dimensions && dimensions.width <= 480 && dimensions.height <= 320,
`contentType=${thumbnail.contentType || 'missing'}, bytes=${thumbnail.bytes.length}, dimensions=${dimensions ? `${dimensions.width}x${dimensions.height}` : 'unreadable'}`,
NEW_CONTRACT_OPTIONS)
}
async function loadWorkspace(context, target, name) {
const response = await context.request('GET', workspacePath(target.tenantId, target.inboundId))
const workspace = context.apiSuccess(MODULE, name, response, `inboundId=${target.inboundId}`, SCENARIO_OPTIONS)
return { response, workspace }
}
async function rejectWithoutMutation(context, target, item, name, payload) {
const path = confirmPath(target.tenantId, target.inboundId, item.resultId)
const response = await context.request('POST', path, payload)
context.assert(MODULE, name, response, expectedRejection(response),
`HTTP=${response.http}, code=${response.body?.code ?? 'missing'}`, SCENARIO_OPTIONS)
const check = await loadWorkspace(context, target, `${name}后回查工作区`)
const persisted = check.workspace?.weightResults?.find((row) => id(row.id) === item.resultId)
context.assert(MODULE, `${name}未改变重量结果`, check.response,
persisted && Number(persisted.version) === item.version && persisted.recognitionStatus !== 'CONFIRMED',
`version=${persisted?.version ?? 'missing'}, status=${persisted?.recognitionStatus ?? 'missing'}`, SCENARIO_OPTIONS)
}
async function deleteResult(context, target, item, name, options = NEW_CONTRACT_OPTIONS) {
const path = deletePath(target.tenantId, target.inboundId, item.resultId, item.version)
const response = await context.request('DELETE', path)
const passed = apiOk(response)
context.addStep(MODULE, name, response, passed,
`resultId=${item.resultId}, version=${item.version}`, options)
if (passed) item.deleted = true
return { response, passed }
}
async function verifyDeleted(context, target, item, baselineWeight) {
const check = await loadWorkspace(context, target, '删除后回查重量工作区')
const missing = !check.workspace?.weightResults?.some((row) => id(row.id) === item.resultId)
context.assert(MODULE, '删除后结果消失且总毛重恢复基线', check.response,
missing && (baselineWeight == null
? check.workspace?.actualGrossWeightKg == null
: closeNumber(check.workspace?.actualGrossWeightKg, baselineWeight)),
`resultMissing=${missing}, baseline=${baselineWeight ?? 'null'}, actual=${check.workspace?.actualGrossWeightKg ?? 'null'}`,
NEW_CONTRACT_OPTIONS)
const imagePath = `/api/platform/v1/files/measurements/${encodeURIComponent(target.tenantId)}/${encodeURIComponent(target.inboundId)}/${encodeURIComponent(item.fileId)}`
const image = await context.requestBinary('GET', imagePath)
context.assert(MODULE, '删除结果后原始图片不可再读取', image,
image.http === 200 && image.body && String(image.body.code) !== '200',
`HTTP=${image.http}, code=${image.body?.code ?? 'missing'}`, NEW_CONTRACT_OPTIONS)
}
export async function run(context) {
const scenarioStart = context.steps.length
const created = []
const target = await resolveMarkedInbound(context)
context.assert(MODULE, '仅选择带本轮测试标记的可编辑入库单', target?.response || null,
Boolean(target), target
? `tenantId=${target.tenantId}, inboundId=${target.inboundId}, status=${target.detail.summary.processStatus}`
: `marker=${context.marker}, 未找到本轮创建且状态为 ${[...EDITABLE_STATUSES].join('/')} 的入库单`,
SCENARIO_OPTIONS)
if (!target) {
context.scenarios.push({
name: SCENARIO, module: MODULE, success: false, deploymentBlocked: false,
passed: 0, failed: 1,
})
return
}
context.context.inboundWeightOcr = {
tenantId: target.tenantId, inboundId: target.inboundId, marker: context.marker,
}
context.createdResources.inboundWeightOcr = {
tenantId: target.tenantId, inboundId: target.inboundId, resultIds: [], cleaned: false,
}
const imagePath = process.env.ADMIN_TEST_OCR_IMAGE || DEFAULT_IMAGE
const imageBytes = await readFile(imagePath)
const image = { bytes: imageBytes, name: basename(imagePath), contentType: 'image/jpeg' }
const initial = await loadWorkspace(context, target, '读取 OCR 测试前重量工作区')
if (!initial.workspace) throw new Error('无法读取测试入库单重量工作区')
const baselineWeight = initial.workspace.actualGrossWeightKg
const baselineIds = new Set((initial.workspace.weightResults || []).map((row) => id(row.id)))
context.assert(MODULE, '测试入库单身份与基线可追踪', initial.response,
id(initial.workspace.inboundId) === target.inboundId
&& initial.workspace.processStatus === target.detail.summary.processStatus,
`existingResults=${baselineIds.size}, baselineWeight=${baselineWeight ?? 'null'}`, SCENARIO_OPTIONS)
const uploadPath = `/api/platform/v1/files/measurements/${encodeURIComponent(target.tenantId)}/${encodeURIComponent(target.inboundId)}`
const noAuth = await multipart(context, uploadPath, image, '')
context.assert(MODULE, '未认证请求不能上传称重图片', noAuth,
noAuth.http === 401 || noAuth.http === 403,
`HTTP=${noAuth.http}, code=${noAuth.body?.code ?? 'none'}`, SCENARIO_OPTIONS)
const wrongType = await multipart(context, uploadPath, {
bytes: new TextEncoder().encode(context.marker), name: `${context.marker}.txt`, contentType: 'text/plain',
})
context.assert(MODULE, '非图片类型被拒绝且不模拟成功', wrongType,
expectedRejection(wrongType), `HTTP=${wrongType.http}, code=${wrongType.body?.code ?? 'missing'}`, SCENARIO_OPTIONS)
const oversized = await multipart(context, uploadPath, {
bytes: new Uint8Array(5 * 1024 * 1024 + 1), name: `${context.marker}.jpg`, contentType: 'image/jpeg',
})
context.assert(MODULE, '超过5MB图片被拒绝', oversized,
expectedRejection(oversized), `HTTP=${oversized.http}, code=${oversized.body?.code ?? 'missing'}`, SCENARIO_OPTIONS)
try {
const original = await createUploadedResult(context, target, image, { retry: true })
created.push(original)
context.createdResources.inboundWeightOcr.resultIds.push(original.resultId)
await verifyImages(context, target, original, image)
const registered = await loadWorkspace(context, target, '登记后回查 OCR 重量结果')
const registeredRow = registered.workspace?.weightResults?.find((row) => id(row.id) === original.resultId)
context.assert(MODULE, '工作区保留本轮 OCR 原值且未覆盖历史结果', registered.response,
registeredRow && !baselineIds.has(original.resultId)
&& closeNumber(registeredRow.ocrWeightValue, original.result.ocrWeightValue)
&& registeredRow.ocrWeightUnit === original.result.ocrWeightUnit,
`resultFound=${Boolean(registeredRow)}, baselineCount=${baselineIds.size}, currentCount=${registered.workspace?.weightResults?.length ?? 'n/a'}`,
SCENARIO_OPTIONS)
const staleConfirm = await context.request('POST',
confirmPath(target.tenantId, target.inboundId, original.resultId),
{ version: original.version + 1 })
context.assert(MODULE, '旧版本确认返回并发冲突', staleConfirm, expectedConflict(staleConfirm),
`HTTP=${staleConfirm.http}, code=${staleConfirm.body?.code ?? 'missing'}`, NEW_CONTRACT_OPTIONS)
const recognizedValue = Number(original.result.ocrWeightValue)
const recognizedUnit = original.result.ocrWeightUnit
const canConfirmOriginal = Number.isFinite(recognizedValue) && recognizedValue > 0
&& ['KG', 'KGS', 'LB', 'LBS'].includes(String(recognizedUnit || '').trim().toUpperCase())
const confirmPayload = canConfirmOriginal
? { version: original.version }
: {
version: original.version, adjustedWeightValue: 1.234, adjustedWeightUnit: 'KG',
adjustmentReason: `${context.marker} OCR未返回有效重量,人工验收`,
}
const confirmResponse = await context.request('POST',
confirmPath(target.tenantId, target.inboundId, original.resultId), confirmPayload)
const confirmed = context.apiSuccess(MODULE, '确认 OCR 重量并计入实际总毛重', confirmResponse,
`usedOriginal=${canConfirmOriginal}`, NEW_CONTRACT_OPTIONS)
context.assert(MODULE, '确认结果状态、版本和最终KG正确', confirmResponse,
confirmed?.recognitionStatus === 'CONFIRMED' && Number(confirmed?.version) === original.version + 1
&& Number(confirmed?.finalWeightKg) > 0,
`status=${confirmed?.recognitionStatus ?? 'missing'}, version=${confirmed?.version ?? 'missing'}, finalKg=${confirmed?.finalWeightKg ?? 'missing'}`,
NEW_CONTRACT_OPTIONS)
original.version = Number(confirmed?.version ?? original.version)
const afterConfirm = await loadWorkspace(context, target, '确认后回查实际总毛重')
const expectedTotal = Number(baselineWeight || 0) + Number(confirmed?.finalWeightKg || 0)
context.assert(MODULE, '确认后实际总毛重按服务端结果重算', afterConfirm.response,
closeNumber(afterConfirm.workspace?.actualGrossWeightKg, expectedTotal),
`baseline=${baselineWeight ?? 'null'}, added=${confirmed?.finalWeightKg ?? 'missing'}, actual=${afterConfirm.workspace?.actualGrossWeightKg ?? 'missing'}`,
NEW_CONTRACT_OPTIONS)
const staleDelete = await context.request('DELETE',
deletePath(target.tenantId, target.inboundId, original.resultId, Math.max(0, original.version - 1)))
context.assert(MODULE, '旧版本删除返回并发冲突', staleDelete, expectedConflict(staleDelete),
`HTTP=${staleDelete.http}, code=${staleDelete.body?.code ?? 'missing'}`, NEW_CONTRACT_OPTIONS)
const deletedOriginal = await deleteResult(context, target, original, '删除本轮已确认图片及重量结果')
if (deletedOriginal.passed) await verifyDeleted(context, target, original, baselineWeight)
const manual = await createUploadedResult(context, target, image, { manual: true })
created.push(manual)
context.createdResources.inboundWeightOcr.resultIds.push(manual.resultId)
await rejectWithoutMutation(context, target, manual, '零重量确认被拒绝', {
version: manual.version, adjustedWeightValue: 0, adjustedWeightUnit: 'KG', adjustmentReason: context.marker,
})
await rejectWithoutMutation(context, target, manual, '负重量确认被拒绝', {
version: manual.version, adjustedWeightValue: -1, adjustedWeightUnit: 'KG', adjustmentReason: context.marker,
})
await rejectWithoutMutation(context, target, manual, 'LB 0.001换算为零后被拒绝', {
version: manual.version, adjustedWeightValue: 0.001, adjustedWeightUnit: 'LB', adjustmentReason: context.marker,
})
await rejectWithoutMutation(context, target, manual, '超出15位整数重量被拒绝', {
version: manual.version, adjustedWeightValue: 1_000_000_000_000_000,
adjustedWeightUnit: 'KG', adjustmentReason: context.marker,
})
const adjustedValue = 10.125
const adjustedConfirmResponse = await context.request('POST',
confirmPath(target.tenantId, target.inboundId, manual.resultId), {
version: manual.version, adjustedWeightValue: adjustedValue, adjustedWeightUnit: 'LB',
adjustmentReason: `${context.marker} 人工确认 LB`,
})
const adjusted = context.apiSuccess(MODULE, '人工改重与确认原子完成', adjustedConfirmResponse,
`adjusted=${adjustedValue} LB`, NEW_CONTRACT_OPTIONS)
const expectedKg = Math.round(adjustedValue * 0.45359237 * 1000) / 1000
context.assert(MODULE, 'LB 人工改重保留输入并正确换算最终KG', adjustedConfirmResponse,
adjusted?.recognitionStatus === 'CONFIRMED' && Number(adjusted?.version) === manual.version + 2
&& closeNumber(adjusted?.adjustedWeightValue, adjustedValue)
&& adjusted?.adjustedWeightUnit === 'LB' && closeNumber(adjusted?.finalWeightKg, expectedKg),
`adjusted=${adjusted?.adjustedWeightValue ?? 'missing'} ${adjusted?.adjustedWeightUnit ?? ''}, finalKg=${adjusted?.finalWeightKg ?? 'missing'}, expectedKg=${expectedKg}`,
NEW_CONTRACT_OPTIONS)
manual.version = Number(adjusted?.version ?? manual.version)
const deletedManual = await deleteResult(context, target, manual, '删除本轮人工改重图片及结果')
if (deletedManual.passed) await verifyDeleted(context, target, manual, baselineWeight)
} finally {
for (const item of created.filter((value) => !value.deleted)) {
await deleteResult(context, target, item, '异常收尾清理本轮重量结果', NEW_CONTRACT_OPTIONS)
}
const cleanup = await loadWorkspace(context, target, '最终清理后回查重量工作区')
const createdIds = new Set(created.map((item) => item.resultId))
const leftovers = (cleanup.workspace?.weightResults || []).filter((row) => createdIds.has(id(row.id)))
const restored = leftovers.length === 0 && (baselineWeight == null
? cleanup.workspace?.actualGrossWeightKg == null
: closeNumber(cleanup.workspace?.actualGrossWeightKg, baselineWeight))
context.assert(MODULE, '仅清理本轮结果且订单总毛重恢复运行前基线', cleanup.response,
restored, `leftovers=${leftovers.length}, baseline=${baselineWeight ?? 'null'}, actual=${cleanup.workspace?.actualGrossWeightKg ?? 'null'}`,
NEW_CONTRACT_OPTIONS)
context.createdResources.inboundWeightOcr.cleaned = restored
}
const steps = context.steps.slice(scenarioStart)
context.scenarios.push({
name: SCENARIO, module: MODULE, success: steps.every((step) => step.passed),
deploymentBlocked: steps.some((step) => step.deploymentBlocker),
passed: steps.filter((step) => step.passed).length,
failed: steps.filter((step) => !step.passed).length,
})
}
@@ -0,0 +1,157 @@
function id(value) { return value == null ? '' : String(value) }
function pageOk(value) { return Array.isArray(value?.records) && Number.isFinite(Number(value?.total)) }
function xlsxOk(response) {
return response.http === 200 && response.bytes?.length > 4
&& response.bytes[0] === 0x50 && response.bytes[1] === 0x4b
}
export async function run(context) {
const module = 'master-data'
const suffix = context.marker.replace(/\D/g, '').slice(-12)
const warehouseListResponse = await context.request('GET', '/api/platform/v1/warehouse-admin?page=1&size=20')
const warehousePage = context.apiSuccess(module, '查询平台仓库列表', warehouseListResponse)
context.assert(module, '仓库列表分页结构正确', warehouseListResponse, pageOk(warehousePage), `total=${warehousePage?.total ?? 'n/a'}`)
const warehousePayload = {
tenantId: 0, warehouseCode: `AW${suffix}`, warehouseName: `Admin验收仓-${suffix}`,
warehouseType: 'DOMESTIC', countryCode: 'CN', timezone: 'Asia/Shanghai',
contactName: '仓库验收员', contactPhone: '13800000001', contactEmail: `warehouse-${suffix}@example.com`,
status: 'ACTIVE', remark: context.marker,
address: {
addressType: 'WAREHOUSE', countryCode: 'CN', stateProvince: '广东省', city: '深圳市',
district: '南山区', postalCode: '518000', addressLine1: 'Admin API 验收仓地址',
addressLine2: null, latitude: null, longitude: null,
},
capabilities: [{ capabilityCode: 'RECEIVING', capabilityName: '收货', enabled: true, cutoffTime: '16:00:00', description: context.marker }],
}
const createWarehouseResponse = await context.request('POST', '/api/platform/v1/warehouse-admin', warehousePayload)
const warehouseId = id(context.apiSuccess(module, '创建平台公共仓库', createWarehouseResponse))
context.assert(module, '仓库创建返回字符串兼容 ID', createWarehouseResponse, Boolean(warehouseId), `warehouseId=${warehouseId || 'missing'}`)
if (!warehouseId) throw new Error('平台仓库创建失败,库位和托盘场景无法继续')
context.context.warehouseId = warehouseId
context.createdResources.warehouse = { id: warehouseId, code: warehousePayload.warehouseCode, finalStatus: 'INACTIVE' }
const warehousePath = `/api/platform/v1/warehouse-admin/${warehouseId}?tenantId=0`
const warehouseDetailResponse = await context.request('GET', warehousePath)
const warehouseDetail = context.apiSuccess(module, '查询平台仓库详情', warehouseDetailResponse)
context.assert(module, '仓库详情、地址和能力正确', warehouseDetailResponse,
id(warehouseDetail?.warehouse?.id) === warehouseId && warehouseDetail?.addresses?.length === 1 && warehouseDetail?.capabilities?.length === 1,
`addresses=${warehouseDetail?.addresses?.length ?? 'n/a'}, capabilities=${warehouseDetail?.capabilities?.length ?? 'n/a'}`)
const editedWarehouse = { ...warehousePayload, warehouseName: `${warehousePayload.warehouseName}-已编辑`, remark: `${context.marker}-EDITED` }
const updateWarehouseResponse = await context.request('PUT', warehousePath, editedWarehouse)
context.apiSuccess(module, '编辑平台仓库', updateWarehouseResponse)
const warehouseAfterEditResponse = await context.request('GET', warehousePath)
const warehouseAfterEdit = context.apiSuccess(module, '回查编辑后的平台仓库', warehouseAfterEditResponse)
context.assert(module, '仓库编辑已持久化', warehouseAfterEditResponse,
warehouseAfterEdit?.warehouse?.warehouseName === editedWarehouse.warehouseName && warehouseAfterEdit?.warehouse?.remark === editedWarehouse.remark,
`warehouseId=${warehouseId}`)
const locationPayload = {
warehouseId, zoneCode: `Z${suffix.slice(-5)}`, zoneName: 'Admin验收区',
locationCode: `L${suffix}`, locationName: 'Admin验收库位', locationType: 'SHELF',
maxWeightKg: 1000, status: 'AVAILABLE', remark: context.marker,
}
const createLocationResponse = await context.request('POST', '/api/platform/v1/locations?tenantId=0', locationPayload)
const locationId = id(context.apiSuccess(module, '创建仓库库位', createLocationResponse))
if (!locationId) throw new Error('库位创建失败')
context.createdResources.location = { id: locationId, warehouseId, deleted: false }
const locationPath = `/api/platform/v1/locations/${locationId}?tenantId=0&warehouseId=${warehouseId}`
const locationDetailResponse = await context.request('GET', locationPath)
const locationDetail = context.apiSuccess(module, '查询库位详情', locationDetailResponse)
context.assert(module, '库位详情关联正确仓库', locationDetailResponse,
id(locationDetail?.location?.id) === locationId && id(locationDetail?.location?.warehouseId) === warehouseId,
`locationId=${locationId}`)
const locationVersion = Number(locationDetail?.location?.version)
const updateLocationResponse = await context.request('PUT', locationPath, { ...locationPayload, locationName: 'Admin验收库位-已编辑', maxWeightKg: 1200, version: locationVersion })
context.apiSuccess(module, '编辑库位', updateLocationResponse)
const locationAfterEditResponse = await context.request('GET', locationPath)
const locationAfterEdit = context.apiSuccess(module, '回查编辑后的库位', locationAfterEditResponse)
context.assert(module, '库位编辑与版本递增正确', locationAfterEditResponse,
locationAfterEdit?.location?.locationName === 'Admin验收库位-已编辑' && Number(locationAfterEdit?.location?.version) === locationVersion + 1,
`version=${locationAfterEdit?.location?.version ?? 'missing'}`)
const disabledLocationVersion = Number(locationAfterEdit?.location?.version)
const disableLocationResponse = await context.request('PATCH', `/api/platform/v1/locations/${locationId}/status?tenantId=0&warehouseId=${warehouseId}`, { status: 'DISABLED', version: disabledLocationVersion })
context.apiSuccess(module, '停用库位', disableLocationResponse)
const deleteLocationResponse = await context.request('DELETE', `/api/platform/v1/locations/${locationId}?tenantId=0&warehouseId=${warehouseId}&version=${disabledLocationVersion + 1}`)
const deletedLocation = context.apiSuccess(module, '删除未占用测试库位', deleteLocationResponse)
if (deletedLocation === null && String(deleteLocationResponse.body?.code) === '200') context.createdResources.location.deleted = true
const palletPayload = { warehouseId, palletType: 'STANDARD', capacityCbm: 1.5, weightKg: 25, color: 'BLUE', remark: context.marker }
const createPalletResponse = await context.request('POST', '/api/platform/v1/pallets?tenantId=0', palletPayload)
const palletId = id(context.apiSuccess(module, '创建仓库托盘', createPalletResponse))
if (!palletId) throw new Error('托盘创建失败')
context.createdResources.pallet = { id: palletId, warehouseId, deleted: false }
const palletPath = `/api/platform/v1/pallets/${palletId}?tenantId=0&warehouseId=${warehouseId}`
const palletDetailResponse = await context.request('GET', palletPath)
const pallet = context.apiSuccess(module, '查询托盘详情', palletDetailResponse)
context.assert(module, '托盘详情关联正确仓库', palletDetailResponse,
id(pallet?.id) === palletId && id(pallet?.warehouseId) === warehouseId, `palletId=${palletId}`)
const updatePalletResponse = await context.request('PUT', palletPath, { ...palletPayload, capacityCbm: 1.75, weightKg: 26, remark: `${context.marker}-EDITED` })
context.apiSuccess(module, '编辑托盘', updatePalletResponse)
const palletAfterEditResponse = await context.request('GET', palletPath)
const palletAfterEdit = context.apiSuccess(module, '回查编辑后的托盘', palletAfterEditResponse)
context.assert(module, '托盘编辑已持久化', palletAfterEditResponse,
Number(palletAfterEdit?.capacityCbm) === 1.75 && palletAfterEdit?.remark === `${context.marker}-EDITED`, `palletId=${palletId}`)
const deletePalletResponse = await context.request('DELETE', palletPath)
context.apiSuccess(module, '删除未使用测试托盘', deletePalletResponse)
if (String(deletePalletResponse.body?.code) === '200') context.createdResources.pallet.deleted = true
const expressPayload = {
companyCode: `EX${suffix}`, companyName: `Admin验收快递-${suffix}`, servicePhone: '4000000000',
website: 'https://example.com', trackingUrlTemplate: 'https://example.com/{trackingNo}', status: 'ACTIVE', remark: context.marker,
}
const createExpressResponse = await context.request('POST', '/api/platform/v1/express-companies?tenantId=0', expressPayload)
const expressId = id(context.apiSuccess(module, '创建快递公司', createExpressResponse))
if (!expressId) throw new Error('快递公司创建失败')
context.createdResources.expressCompany = { id: expressId, code: expressPayload.companyCode, finalStatus: 'INACTIVE' }
const expressPath = `/api/platform/v1/express-companies/${expressId}?tenantId=0`
const expressDetailResponse = await context.request('GET', expressPath)
const express = context.apiSuccess(module, '查询快递公司详情', expressDetailResponse)
context.assert(module, '快递公司详情正确', expressDetailResponse, id(express?.id) === expressId && express?.companyCode === expressPayload.companyCode, `expressId=${expressId}`)
const updateExpressResponse = await context.request('PUT', expressPath, { ...expressPayload, companyName: `${expressPayload.companyName}-已编辑`, remark: `${context.marker}-EDITED` })
context.apiSuccess(module, '编辑快递公司', updateExpressResponse)
const disableExpressResponse = await context.request('PATCH', `/api/platform/v1/express-companies/${expressId}/status?tenantId=0`, { status: 'INACTIVE' })
context.apiSuccess(module, '停用快递公司', disableExpressResponse)
const expressListResponse = await context.request('GET', `/api/platform/v1/express-companies?page=1&size=20&keyword=${expressPayload.companyCode}`)
const expressPage = context.apiSuccess(module, '筛选快递公司列表', expressListResponse)
context.assert(module, '快递公司列表回显停用状态', expressListResponse,
expressPage?.records?.some((item) => id(item.id) === expressId && item.status === 'INACTIVE'), `records=${expressPage?.records?.length ?? 'n/a'}`)
const partnerPayload = {
partnerCode: `PT${suffix}`, partnerName: `Admin验收往来单位-${suffix}`, partnerType: 'SUPPLIER', types: ['SUPPLIER'],
contactName: '往来单位验收员', contactPhone: '13800000002', contactEmail: `partner-${suffix}@example.com`,
countryCode: 'CN', province: '广东省', city: '深圳市', district: '南山区', addressLine: 'Admin API 往来单位地址',
postalCode: '518000', status: 'ACTIVE', remark: context.marker,
}
const createPartnerResponse = await context.request('POST', '/api/platform/v1/partners?tenantId=0', partnerPayload)
const partnerId = id(context.apiSuccess(module, '创建往来单位', createPartnerResponse))
if (!partnerId) throw new Error('往来单位创建失败')
context.createdResources.partner = { id: partnerId, code: partnerPayload.partnerCode, finalStatus: 'INACTIVE' }
const partnerPath = `/api/platform/v1/partners/${partnerId}?tenantId=0`
const partnerDetailResponse = await context.request('GET', partnerPath)
const partner = context.apiSuccess(module, '查询往来单位详情', partnerDetailResponse)
context.assert(module, '往来单位详情正确', partnerDetailResponse,
id(partner?.id) === partnerId && partner?.types?.includes('SUPPLIER'), `partnerId=${partnerId}`)
const updatePartnerResponse = await context.request('PUT', partnerPath, { ...partnerPayload, partnerName: `${partnerPayload.partnerName}-已编辑`, types: ['SUPPLIER', 'CARRIER'], remark: `${context.marker}-EDITED` })
context.apiSuccess(module, '编辑往来单位', updatePartnerResponse)
const disablePartnerResponse = await context.request('PATCH', `/api/platform/v1/partners/${partnerId}/status?tenantId=0`, { status: 'INACTIVE' })
context.apiSuccess(module, '停用往来单位', disablePartnerResponse)
const partnerListResponse = await context.request('GET', `/api/platform/v1/partners?page=1&size=20&keyword=${partnerPayload.partnerCode}`)
const partnerPage = context.apiSuccess(module, '筛选往来单位列表', partnerListResponse)
context.assert(module, '往来单位列表回显编辑与停用状态', partnerListResponse,
partnerPage?.records?.some((item) => id(item.id) === partnerId && item.status === 'INACTIVE' && item.types?.includes('CARRIER')),
`records=${partnerPage?.records?.length ?? 'n/a'}`)
const templates = [
['仓库', '/api/platform/v1/warehouse-admin/import-template'],
['快递公司', '/api/platform/v1/express-companies/import-template'],
['往来单位', '/api/platform/v1/partners/import-template'],
]
for (const [name, path] of templates) {
const response = await context.requestBinary('GET', path)
context.addStep(module, `下载${name}导入模板`, response, xlsxOk(response),
`bytes=${response.bytes?.length ?? 0}, contentType=${response.contentType || 'missing'}`)
}
const disableWarehouseResponse = await context.request('PATCH', `/api/platform/v1/warehouse-admin/${warehouseId}/status?tenantId=0&status=INACTIVE`)
context.apiSuccess(module, '停用测试仓库', disableWarehouseResponse)
}
+391
View File
@@ -0,0 +1,391 @@
const MODULE = 'orders'
const CONTRACT_OPTIONS = { scenario: 'isolated-order-chain', deploymentBlocker: true }
const MERCHANT_USERNAME = process.env.MERCHANT_TEST_USERNAME || 'merchant_admin'
const MERCHANT_PASSWORD = process.env.MERCHANT_TEST_PASSWORD || 'ChangeMe123!'
const BOXES = [
{ boxType: 'SMALL', lengthCm: 30, widthCm: 20, heightCm: 20 },
{ boxType: 'MEDIUM', lengthCm: 40, widthCm: 30, heightCm: 30 },
{ boxType: 'LARGE', lengthCm: 50, widthCm: 40, heightCm: 40 },
{ boxType: 'EXTRA_LARGE', lengthCm: 60, widthCm: 50, heightCm: 50 },
]
const MEASURABLE_STATUSES = new Set(['WAIT_MEASURE', 'MEASURING', 'MEASURE_REJECTED'])
function id(value) { return value == null ? '' : String(value) }
function records(value) { return Array.isArray(value?.records) ? value.records : [] }
function pageOk(value) { return Array.isArray(value?.records) && Number.isFinite(Number(value?.total)) }
function success(response) { return response.http === 200 && String(response.body?.code) === '200' }
function sleep(milliseconds) { return new Promise((resolve) => setTimeout(resolve, milliseconds)) }
function key(context, operation) { return `${context.marker}-${operation}`.slice(0, 128) }
function tomorrow(days = 30) {
const date = new Date(); date.setUTCDate(date.getUTCDate() + days)
return date.toISOString().slice(0, 10)
}
async function request(context, method, path, body, token, extraHeaders = {}) {
const headers = { Accept: 'application/json', Authorization: `Bearer ${token}`, ...extraHeaders }
let payload = body
if (body !== undefined && !(body instanceof FormData)) {
headers['Content-Type'] = 'application/json; charset=utf-8'
payload = JSON.stringify(body)
}
try {
const response = await fetch(`${context.options.gateway}${path}`, {
method, headers, body: payload, signal: AbortSignal.timeout(30_000),
})
const content = await response.text()
let parsed = null
try { parsed = content ? JSON.parse(content) : null } catch { /* Recorded as a bounded raw response. */ }
return {
method, path, http: response.status, body: parsed,
raw: parsed === null ? content.slice(0, 500) : null,
requestId: parsed?.requestId || response.headers.get('x-request-id'),
traceId: parsed?.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 uploadProductImage(context, merchantToken) {
const form = new FormData()
const png = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64')
form.append('file', new Blob([png], { type: 'image/png' }), `product-${context.marker}.png`)
const response = await request(context, 'POST', '/api/merchant/v1/files/product-images', form, merchantToken)
const data = context.apiSuccess(MODULE, 'Client 上传产品图片', response, 'contentType=image/png', CONTRACT_OPTIONS)
context.assert(MODULE, '产品图片上传返回字符串文件 ID', response,
Boolean(data?.fileId) && typeof data.fileId === 'string' && data.contentType === 'image/png',
`fileIdType=${typeof data?.fileId}, contentType=${data?.contentType ?? 'missing'}`, CONTRACT_OPTIONS)
return id(data?.fileId)
}
async function assertProductImageDownload(context, merchantToken, fileId) {
const path = `/api/merchant/v1/files/product-images/${encodeURIComponent(fileId)}`
try {
const response = await fetch(`${context.options.gateway}${path}`, {
headers: { Accept: 'image/*', Authorization: `Bearer ${merchantToken}` },
signal: AbortSignal.timeout(30_000),
})
const bytes = await response.arrayBuffer()
const evidence = { method: 'GET', path, http: response.status, body: null, raw: null,
requestId: response.headers.get('x-request-id'), traceId: response.headers.get('x-trace-id') }
context.assert(MODULE, 'Client 可下载本轮产品图片', evidence,
response.status === 200 && bytes.byteLength > 0 && (response.headers.get('content-type') || '').startsWith('image/'),
`bytes=${bytes.byteLength}, contentType=${response.headers.get('content-type') || 'missing'}`, CONTRACT_OPTIONS)
} catch (error) {
context.assert(MODULE, 'Client 可下载本轮产品图片', null, false, String(error), CONTRACT_OPTIONS)
}
}
async function loadMasterData(context, merchantToken) {
const domesticResponse = await request(context, 'GET',
'/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=DOMESTIC', undefined, merchantToken)
const domesticPage = context.apiSuccess(MODULE, 'Client 查询国内收货仓', domesticResponse)
context.assert(MODULE, '存在可用国内收货仓', domesticResponse, records(domesticPage).length > 0,
`count=${records(domesticPage).length}`)
const fbaResponse = await request(context, 'GET',
'/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=FBA', undefined, merchantToken)
const fbaPage = context.apiSuccess(MODULE, 'Client 查询 FBA 目的仓', fbaResponse)
context.assert(MODULE, '存在可用 FBA 目的仓', fbaResponse, records(fbaPage).length > 0,
`count=${records(fbaPage).length}`)
const receiving = records(domesticPage)[0]; const destination = records(fbaPage)[0]
if (!receiving || !destination) throw new Error('缺少创建测试订单所需的收货仓或目的仓')
const channelResponse = await request(context, 'GET',
`/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=${encodeURIComponent(receiving.id)}`,
undefined, merchantToken)
const channels = context.apiSuccess(MODULE, 'Client 查询可用计费渠道', channelResponse)
const channel = Array.isArray(channels)
? channels.find((item) => Array.isArray(item.pricingTaxModes) && item.pricingTaxModes.length > 0)
: null
context.assert(MODULE, '存在带计价税制的可用渠道', channelResponse, Boolean(channel),
`count=${Array.isArray(channels) ? channels.length : 'n/a'}`)
if (!channel) throw new Error('缺少创建测试订单所需的可用渠道')
const partnerResponse = await request(context, 'GET',
'/api/merchant/v1/partners/candidates?type=SHIPPER', undefined, merchantToken)
const partners = context.apiSuccess(MODULE, 'Client 查询发货客户候选', partnerResponse)
const partner = Array.isArray(partners)
? partners.find((item) => item.status === 'ACTIVE' && item.types?.includes?.('SHIPPER'))
: null
return { receiving, destination, channel, partner }
}
function buildPayload(context, profile, master, productImageFileId) {
const shipmentId = `SHIP-${context.marker}`.slice(0, 64)
const referenceId = `REF-${context.marker}`.slice(0, 64)
const companyName = profile.tenantName || `订单验收客户-${context.marker}`
return {
customerId: master.partner?.id ?? null,
customerName: master.partner?.partnerName ?? companyName,
transportMode: 'SEA', tradeTerm: 'FOB',
destinationCountryCode: master.destination.countryCode || 'US',
destinationWarehouseId: master.destination.id,
destinationWarehouseName: master.destination.warehouseName,
expectedShipDate: tomorrow(), customerRemark: `${context.marker} Client 创建隔离订单`,
pickupMethod: 'PICKUP', receivingWarehouseId: master.receiving.id,
receivingWarehouseName: master.receiving.warehouseName,
channelCode: master.channel.channelCode, channelName: master.channel.channelName,
carrierId: null, carrierName: null, taxMode: 'BUY_DECLARATION',
pricingTaxMode: master.channel.pricingTaxModes[0], containerMode: 'LCL',
customsInspection: false, destinationType: 'FBA',
amazonWarehouseCode: master.destination.warehouseCode,
shipmentId, referenceId,
sender: {
companyName, contactName: '订单验收联系人', phone: '13800138000',
email: 'order-acceptance@example.com', countryCode: 'CN', province: '广东省',
city: '深圳市', district: '南山区', addressLine: `${context.marker} 测试地址`, postalCode: '518000',
},
receiver: null,
cargoes: BOXES.map((box, index) => {
const volumeCbm = box.lengthCm * box.widthCm * box.heightCm / 1_000_000
const unitsPerPackage = 10 + index
return {
skuNo: `SKU-${box.boxType}-${context.marker}`.slice(0, 64),
cargoName: `${box.boxType} 测试产品`, cargoNameEn: `${box.boxType} test product`,
hsCode: '8518210000', customsHsCode: '8518210000', packageType: 'CARTON',
lengthCm: box.lengthCm, widthCm: box.widthCm, heightCm: box.heightCm,
unitsPerPackage, packageCount: 1, grossWeightKg: 10 + index,
volumeCbm, totalQuantity: unitsPerPackage, netWeightKg: 9 + index,
unitDeclaredValue: 8.5, declaredValue: unitsPerPackage * 8.5, currency: 'USD',
material: '塑料 / Plastic', usage: '接口验收 / API acceptance',
model: `MODEL-${box.boxType}`, brand: 'TEST-NO-LOGO',
productLengthCm: 12 + index, productWidthCm: 8 + index, productHeightCm: 6 + index,
shipmentId, referenceId, upcNo: `${Date.now()}${index}`.slice(-12),
productImageFileId, dangerousGoods: false,
cargoRemark: `${context.marker} boxType=${box.boxType}`,
}
}),
}
}
function assertCargoContract(context, response, cargoes, payload) {
const complete = Array.isArray(cargoes) && cargoes.length === BOXES.length
&& BOXES.every((box, index) => {
const actual = cargoes[index]; const expected = payload.cargoes[index]
return Number(actual?.lengthCm) === box.lengthCm && Number(actual?.widthCm) === box.widthCm
&& Number(actual?.heightCm) === box.heightCm && id(actual?.productImageFileId) === id(expected.productImageFileId)
&& actual?.skuNo === expected.skuNo && actual?.cargoName === expected.cargoName
&& actual?.cargoNameEn === expected.cargoNameEn && actual?.customsHsCode === expected.customsHsCode
&& Number(actual?.unitsPerPackage) === expected.unitsPerPackage
&& Number(actual?.packageCount) === 1 && Number(actual?.totalQuantity) === expected.totalQuantity
&& Number(actual?.netWeightKg) === expected.netWeightKg
&& Number(actual?.unitDeclaredValue) === expected.unitDeclaredValue
&& actual?.material === expected.material && actual?.productUsage === expected.usage
&& actual?.model === expected.model && actual?.brand === expected.brand
&& Number(actual?.productLengthCm) === expected.productLengthCm
&& Number(actual?.productWidthCm) === expected.productWidthCm
&& Number(actual?.productHeightCm) === expected.productHeightCm
&& actual?.shipmentId === expected.shipmentId && actual?.referenceId === expected.referenceId
&& actual?.upcNo === expected.upcNo && actual?.cargoRemark.includes(context.marker)
})
context.assert(MODULE, '四箱型产品字段及图片契约完整回显', response, complete,
`cargoes=${Array.isArray(cargoes) ? cargoes.length : 'n/a'}, imageFileId=${payload.cargoes[0].productImageFileId}`,
CONTRACT_OPTIONS)
}
async function assertDimensionConfig(context, tenantId) {
const path = `/api/platform/v1/first-leg/customer-dimension-config?tenantId=${encodeURIComponent(tenantId)}`
const response = await context.request('GET', path)
const data = context.apiSuccess(MODULE, 'Admin 查询客户固定箱型', response, `tenantId=${tenantId}`, CONTRACT_OPTIONS)
const rules = Array.isArray(data?.rules) ? data.rules : []
const exact = BOXES.every((box, index) => {
const rule = rules[index]
return rule?.boxType === box.boxType && Number(rule.innerLengthCm) === box.lengthCm
&& Number(rule.innerWidthCm) === box.widthCm && Number(rule.innerHeightCm) === box.heightCm
&& Number(rule.edgeAdditionCm) === 2
})
context.assert(MODULE, '固定四箱型顺序、内径及每边加2cm正确', response,
rules.length === BOXES.length && exact,
`types=${rules.map((rule) => rule.boxType).join(',') || 'missing'}`, CONTRACT_OPTIONS)
}
async function waitForInbound(context, tenantId, orderId) {
const path = `/api/platform/v1/orders/${encodeURIComponent(orderId)}?tenantId=${encodeURIComponent(tenantId)}`
let response = null; let detail = null
for (let attempt = 1; attempt <= 20; attempt += 1) {
response = await context.request('GET', path)
if (success(response) && response.body?.data?.summary?.inboundId) {
detail = response.body.data
break
}
if (attempt < 20) await sleep(1_000)
}
context.addStep(MODULE, '审核后生成隔离入库单', response, Boolean(detail),
detail ? `inboundId=${detail.summary.inboundId}` : '20秒内未返回 inboundId', CONTRACT_OPTIONS)
return { response, detail }
}
export async function run(context) {
const loginResponse = await context.request('POST', '/api/auth/login', {
username: MERCHANT_USERNAME, password: MERCHANT_PASSWORD,
}, '')
const login = context.apiSuccess(MODULE, 'Client 测试账号登录', loginResponse,
`username=${MERCHANT_USERNAME}`)
const merchantToken = login?.accessToken
if (!merchantToken) throw new Error('Client 登录未返回 accessToken')
const profileResponse = await request(context, 'GET', '/api/merchant/v1/iam/tenant/profile',
undefined, merchantToken)
const profile = context.apiSuccess(MODULE, 'Client 查询当前企业资料', profileResponse)
const tenantId = id(profile?.id)
context.assert(MODULE, 'Client 测试企业已审核且客户 ID 有效', profileResponse,
Boolean(tenantId) && profile?.status === 'ACTIVE', `tenantId=${tenantId || 'missing'}, status=${profile?.status ?? 'missing'}`)
if (!tenantId) throw new Error('Client 企业资料未返回客户 ID')
await assertDimensionConfig(context, tenantId)
const master = await loadMasterData(context, merchantToken)
const productImageFileId = await uploadProductImage(context, merchantToken)
if (!productImageFileId) throw new Error('产品图片新契约未部署,无法创建带图片的隔离订单')
await assertProductImageDownload(context, merchantToken, productImageFileId)
const payload = buildPayload(context, profile, master, productImageFileId)
const createPath = '/api/merchant/v1/orders/first-leg'
const createResponse = await request(context, 'POST', createPath, payload, merchantToken,
{ 'Idempotency-Key': key(context, 'client-create') })
const orderId = id(context.apiSuccess(MODULE, 'Client 创建四箱型隔离订单', createResponse,
`marker=${context.marker}`, CONTRACT_OPTIONS))
if (!orderId) throw new Error('Client 订单创建失败,后续 Admin 链路停止')
context.createdResources.orders = {
marker: context.marker, tenantId, orderId, productImageFileId,
boxTypes: BOXES.map((box) => box.boxType), inboundId: null,
}
const replayResponse = await request(context, 'POST', createPath, payload, merchantToken,
{ 'Idempotency-Key': key(context, 'client-create') })
const replayedId = id(context.apiSuccess(MODULE, 'Client 创建订单幂等重放', replayResponse))
context.assert(MODULE, 'Client 创建幂等结果一致', replayResponse, replayedId === orderId,
`orderId=${orderId}, replayedId=${replayedId}`)
const clientDetailPath = `/api/merchant/v1/orders/first-leg/${encodeURIComponent(orderId)}`
const draftResponse = await request(context, 'GET', clientDetailPath, undefined, merchantToken)
const draft = context.apiSuccess(MODULE, 'Client 查询隔离订单草稿', draftResponse)
const orderNo = id(draft?.order?.orderNo)
context.assert(MODULE, 'Client 草稿身份、marker 和状态正确', draftResponse,
id(draft?.order?.id) === orderId && draft?.order?.status === 'DRAFT'
&& String(draft?.order?.customerRemark || '').includes(context.marker),
`orderNo=${orderNo || 'missing'}, status=${draft?.order?.status ?? 'missing'}`)
assertCargoContract(context, draftResponse, draft?.cargoes, payload)
context.createdResources.orders.orderNo = orderNo
const submitResponse = await request(context, 'POST', `${clientDetailPath}/submit`, undefined,
merchantToken, { 'Idempotency-Key': key(context, 'client-submit') })
context.apiSuccess(MODULE, 'Client 提交隔离订单', submitResponse, `orderId=${orderId}`)
const submittedResponse = await request(context, 'GET', clientDetailPath, undefined, merchantToken)
const submitted = context.apiSuccess(MODULE, 'Client 回查已提交订单', submittedResponse)
context.assert(MODULE, 'Client 订单已进入待审核状态', submittedResponse,
submitted?.order?.status === 'SUBMITTED', `status=${submitted?.order?.status ?? 'missing'}`)
const unifiedListResponse = await context.request('GET',
`/api/platform/v1/orders?page=1&size=100&tenantId=${encodeURIComponent(tenantId)}&orderNo=${encodeURIComponent(orderNo)}`)
const unifiedPage = context.apiSuccess(MODULE, 'Admin 查询统一订单列表', unifiedListResponse)
const unifiedRow = records(unifiedPage).find((row) => id(row.orderId) === orderId)
context.assert(MODULE, 'Admin 统一订单列表只核对本轮订单', unifiedListResponse,
pageOk(unifiedPage) && Boolean(unifiedRow), `orderId=${orderId}, found=${Boolean(unifiedRow)}`)
const unifiedDetailPath = `/api/platform/v1/orders/${encodeURIComponent(orderId)}?tenantId=${encodeURIComponent(tenantId)}`
const unifiedDetailResponse = await context.request('GET', unifiedDetailPath)
const unifiedDetail = context.apiSuccess(MODULE, 'Admin 查询统一订单详情', unifiedDetailResponse)
context.assert(MODULE, 'Admin 统一订单详情身份与待审核状态正确', unifiedDetailResponse,
id(unifiedDetail?.summary?.orderId) === orderId && unifiedDetail?.summary?.mainStatus === 'PENDING_REVIEW'
&& Array.isArray(unifiedDetail?.timeline),
`mainStatus=${unifiedDetail?.summary?.mainStatus ?? 'missing'}, timeline=${unifiedDetail?.timeline?.length ?? 'n/a'}`)
const firstLegListResponse = await context.request('GET',
`/api/platform/v1/orders/first-leg?tenantId=${encodeURIComponent(tenantId)}&keyword=${encodeURIComponent(orderNo)}&page=1&size=100`)
const firstLegPage = context.apiSuccess(MODULE, 'Admin 查询头程订单列表', firstLegListResponse)
const firstLegRow = records(firstLegPage).find((row) => id(row.id) === orderId)
context.assert(MODULE, 'Admin 头程订单列表只核对本轮订单', firstLegListResponse,
pageOk(firstLegPage) && Boolean(firstLegRow), `orderId=${orderId}, found=${Boolean(firstLegRow)}`)
const firstLegDetailPath = `/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}?tenantId=${encodeURIComponent(tenantId)}`
const firstLegDetailResponse = await context.request('GET', firstLegDetailPath)
const firstLegDetail = context.apiSuccess(MODULE, 'Admin 查询头程订单详情', firstLegDetailResponse)
context.assert(MODULE, 'Admin 头程订单详情聚合结构完整', firstLegDetailResponse,
id(firstLegDetail?.order?.id) === orderId && Array.isArray(firstLegDetail?.cargoes)
&& Array.isArray(firstLegDetail?.parties) && Array.isArray(firstLegDetail?.internalNotes)
&& Array.isArray(firstLegDetail?.statusHistory),
`cargoes=${firstLegDetail?.cargoes?.length ?? 'n/a'}, history=${firstLegDetail?.statusHistory?.length ?? 'n/a'}`)
assertCargoContract(context, firstLegDetailResponse, firstLegDetail?.cargoes, payload)
const noteResponse = await context.request('POST',
`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}/internal-notes?tenantId=${encodeURIComponent(tenantId)}`,
{ content: `${context.marker} Admin 订单链路验收` })
context.apiSuccess(MODULE, 'Admin 为本轮订单添加内部备注', noteResponse)
const approveResponse = await context.request('POST',
`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}/approve?tenantId=${encodeURIComponent(tenantId)}`)
context.apiSuccess(MODULE, 'Admin 审核通过本轮订单', approveResponse, `orderId=${orderId}`)
const inboundResult = await waitForInbound(context, tenantId, orderId)
const inboundId = id(inboundResult.detail?.summary?.inboundId)
if (!inboundId) throw new Error('订单审核后的入库单契约未部署,无法向 OCR/墨家模块交接')
const inboundPath = `/api/platform/inbounds/${encodeURIComponent(inboundId)}?tenantId=${encodeURIComponent(tenantId)}`
const inboundResponse = await context.request('GET', inboundPath)
const inbound = context.apiSuccess(MODULE, 'Admin 查询本轮入库单详情', inboundResponse,
`inboundId=${inboundId}`, CONTRACT_OPTIONS)
const markerValues = [
inbound?.publicRemark, inbound?.summary?.orderNo, inbound?.summary?.inboundCode,
...(Array.isArray(inbound?.internalNotes) ? inbound.internalNotes.map((note) => note.content) : []),
]
const markerFound = markerValues.some((value) => String(value || '').includes(context.marker))
const measurable = MEASURABLE_STATUSES.has(inbound?.summary?.processStatus)
context.assert(MODULE, '隔离入库单 marker 来源与可测量状态正确', inboundResponse,
id(inbound?.summary?.id) === inboundId && id(inbound?.summary?.tenantId) === tenantId
&& markerFound && measurable,
`markerFound=${markerFound}, status=${inbound?.summary?.processStatus ?? 'missing'}`, CONTRACT_OPTIONS)
context.assert(MODULE, '入库单详情包含托盘库位、文件、备注和状态历史', inboundResponse,
Array.isArray(inbound?.palletLocations) && Array.isArray(inbound?.files)
&& Array.isArray(inbound?.internalNotes) && Array.isArray(inbound?.statusHistory),
`inboundId=${inboundId}, history=${inbound?.statusHistory?.length ?? 'n/a'}`)
const inboundListResponse = await context.request('GET',
`/api/platform/inbounds?tenantId=${encodeURIComponent(tenantId)}&inboundCode=${encodeURIComponent(inbound.summary.inboundCode)}&page=1&size=100`)
const inboundPage = context.apiSuccess(MODULE, 'Admin 查询入库单列表', inboundListResponse)
context.assert(MODULE, 'Admin 入库单列表只核对本轮入库单', inboundListResponse,
pageOk(inboundPage) && records(inboundPage).some((row) => id(row.id) === inboundId),
`inboundId=${inboundId}, total=${inboundPage?.total ?? 'n/a'}`)
context.createdResources.orders.inboundId = inboundId
context.createdResources.orders.inboundCode = inbound.summary.inboundCode
context.createdResources.orders.processStatus = inbound.summary.processStatus
context.createdResources.ocrInbound = { tenantId, inboundId, marker: context.marker }
context.createdResources.testInbound = { tenantId, inboundId, marker: context.marker }
context.context.inboundWeightOcr = { tenantId, inboundId, marker: context.marker }
context.context.ocrInbound = context.context.inboundWeightOcr
context.context.testInbound = context.context.inboundWeightOcr
context.context.createdInbound = context.context.inboundWeightOcr
const taskResponse = await context.request('GET',
`/api/platform/v1/first-leg/tasks/by-order/${encodeURIComponent(orderId)}?tenantId=${encodeURIComponent(tenantId)}`)
const task = context.apiSuccess(MODULE, 'Admin 查询本轮订单履约任务', taskResponse,
'新订单应生成可查询的履约任务', CONTRACT_OPTIONS)
context.assert(MODULE, '履约任务与本轮订单和客户一致', taskResponse,
id(task?.orderId) === orderId && id(task?.tenantId) === tenantId,
`taskId=${id(task?.id) || 'missing'}, status=${task?.status ?? 'missing'}`, CONTRACT_OPTIONS)
if (task?.id) {
context.createdResources.orders.taskId = id(task.id)
const historyResponse = await context.request('GET',
`/api/platform/v1/first-leg/tasks/${encodeURIComponent(task.id)}/assignment-history?tenantId=${encodeURIComponent(tenantId)}&page=1&size=20`)
const history = context.apiSuccess(MODULE, 'Admin 查询本轮履约任务分配历史', historyResponse)
context.assert(MODULE, '履约分配历史分页结构正确', historyResponse, pageOk(history),
`taskId=${task.id}, total=${history?.total ?? 'n/a'}`)
}
const acceptResponse = await context.request('POST',
`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}/accept?tenantId=${encodeURIComponent(tenantId)}`)
context.apiSuccess(MODULE, 'Admin 受理本轮订单', acceptResponse)
const startResponse = await context.request('POST',
`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}/start?tenantId=${encodeURIComponent(tenantId)}`)
context.apiSuccess(MODULE, 'Admin 启动本轮订单履约', startResponse)
const finalDetailResponse = await context.request('GET', firstLegDetailPath)
const finalDetail = context.apiSuccess(MODULE, 'Admin 回查本轮订单履约状态', finalDetailResponse)
context.assert(MODULE, '本轮订单已进入处理中且保留状态历史', finalDetailResponse,
finalDetail?.order?.status === 'PROCESSING'
&& finalDetail?.statusHistory?.some?.((item) => item.toStatus === 'APPROVED')
&& finalDetail?.statusHistory?.some?.((item) => item.toStatus === 'ACCEPTED')
&& finalDetail?.statusHistory?.some?.((item) => item.toStatus === 'PROCESSING'),
`status=${finalDetail?.order?.status ?? 'missing'}, history=${finalDetail?.statusHistory?.length ?? 'n/a'}`)
}
+314
View File
@@ -0,0 +1,314 @@
import { createHash } from 'node:crypto'
import { readFile } from 'node:fs/promises'
import { basename } from 'node:path'
const PRICE_BOOK_PATH = '/api/platform/v1/fba-pricing/carrier-rates/price-books'
const DOMESTIC_WAREHOUSE_PATH = '/api/platform/v1/warehouse-admin?page=1&size=200&status=ACTIVE&warehouseType=DOMESTIC'
const OVERSEAS_WAREHOUSE_PATH = '/api/platform/v1/warehouse-admin?page=1&size=200&status=ACTIVE&warehouseType=OVERSEAS'
const DEFAULT_PRICE_BOOK_FILE = 'C:\\Users\\BY-OY\\Documents\\WXWork\\1688854456873012\\Cache\\File\\2026-08\\2026.08.03柏悦价格表 船期2026.08.12.xlsx'
const EXPECTED_CHANNEL_CODES = [
'MATSON_CLX_TRUCK', 'MATSON_MAX_TRUCK', 'EXX_TRUCK', 'HEDE_TRUCK', 'EXX_RAIL_NY',
'OA_LA_TRUCK', 'OA_LA_FULL_TRUCK', 'OA_CHICAGO_RAIL', 'OA_HOUSTON_TRUCK',
'OA_NEW_YORK_TRUCK', 'OA_SAVANNAH_TRUCK', 'MATSON_CLX_PARCEL',
'MATSON_MAX_PARCEL', 'HEDE_PARCEL', 'OA_PARCEL',
]
const MAPPING_ISSUE_CODES = new Set([
'ORIGIN_MAPPING_REQUIRED',
'ORIGIN_WAREHOUSE_INVALID',
'OVERSEAS_MAPPING_REQUIRED',
'OVERSEAS_WAREHOUSE_INVALID',
'WAREHOUSE_VALIDATION_FAILED',
'DUPLICATE_ORIGIN_MAPPING',
'DUPLICATE_OVERSEAS_MAPPING',
'UNUSED_ORIGIN_MAPPING',
'UNUSED_OVERSEAS_MAPPING',
'DUPLICATE_WAREHOUSE_MAPPING',
'DUPLICATE_RATE',
])
function sha256(bytes) {
return createHash('sha256').update(bytes).digest('hex')
}
function normalize(value) {
return String(value || '').trim().toUpperCase()
}
function validId(value) {
return typeof value === 'string' && /^[1-9][0-9]*$/.test(value)
}
function finishScenario(context, module, scenario, start) {
const steps = context.steps.slice(start)
context.scenarios.push({
name: scenario,
module,
success: steps.every((step) => step.passed),
deploymentBlocked: steps.some((step) => step.deploymentBlocker),
passed: steps.filter((step) => step.passed).length,
failed: steps.filter((step) => !step.passed).length,
})
}
async function uploadWorkbook(context, bytes, fileName) {
const form = new FormData()
form.append('file', new Blob([bytes], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}), fileName)
try {
const response = await fetch(`${context.options.gateway}${PRICE_BOOK_PATH}`, {
method: 'POST',
headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}` },
body: form,
signal: AbortSignal.timeout(120_000),
})
const content = await response.text()
let payload = null
try { payload = content ? JSON.parse(content) : null } catch { /* Raw response is diagnostic evidence. */ }
return {
method: 'POST', path: PRICE_BOOK_PATH, http: response.status, body: payload,
raw: payload === null ? content.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: PRICE_BOOK_PATH, http: 0, body: null,
raw: error instanceof Error ? error.message : String(error),
requestId: null, traceId: null,
}
}
}
function buildMappings(detail, warehouses) {
const domestic = warehouses.filter((warehouse) => warehouse.status === 'ACTIVE' && warehouse.warehouseType === 'DOMESTIC')
const overseas = warehouses.filter((warehouse) => warehouse.status === 'ACTIVE' && warehouse.warehouseType === 'OVERSEAS')
const originSources = new Map()
const overseasSources = new Map()
for (const rate of detail.rates || []) {
const originCode = String(rate.originWarehouseCode || '').trim()
if (originCode && !originSources.has(normalize(originCode))) originSources.set(normalize(originCode), originCode)
if (rate.destinationType === 'OVERSEAS' && String(rate.destinationName || '').trim()) {
const sourceName = String(rate.destinationName).trim()
const key = normalize(sourceName)
if (!overseasSources.has(key)) overseasSources.set(key, {
sourceName,
destinationCode: String(rate.destinationCode || '').trim(),
})
}
}
const originMappings = []
const missingOrigins = []
for (const sourceCode of originSources.values()) {
const matches = domestic.filter((warehouse) => normalize(warehouse.warehouseCode) === normalize(sourceCode))
if (matches.length === 1) originMappings.push({ originWarehouseCode: sourceCode, warehouseId: String(matches[0].id) })
else missingOrigins.push(`${sourceCode}:${matches.length}`)
}
const overseasMappings = []
const missingOverseas = []
for (const source of overseasSources.values()) {
const matches = overseas.filter((warehouse) => (
source.destinationCode
? normalize(warehouse.warehouseCode) === normalize(source.destinationCode)
: normalize(warehouse.warehouseName) === normalize(source.sourceName)
))
if (matches.length === 1) overseasMappings.push({ sourceDestinationName: source.sourceName, warehouseId: String(matches[0].id) })
else missingOverseas.push(`${source.sourceName}/${source.destinationCode || '-'}:${matches.length}`)
}
return { originMappings, overseasMappings, missingOrigins, missingOverseas, originSources, overseasSources }
}
export async function run(context) {
const module = 'pricing'
const scenario = 'price-book-lifecycle'
const start = context.steps.length
const blocker = { scenario, deploymentBlocker: true }
const listBeforeResponse = await context.request('GET', PRICE_BOOK_PATH)
const versionsBefore = context.apiSuccess(module, '查询发布前船期价格版本', listBeforeResponse, '', blocker)
const activeBefore = Array.isArray(versionsBefore) ? versionsBefore.filter((version) => version.status === 'ACTIVE') : []
context.assert(module, '发布前最多一份生效版本', listBeforeResponse,
Array.isArray(versionsBefore) && activeBefore.length <= 1,
`versions=${Array.isArray(versionsBefore) ? versionsBefore.length : 'n/a'}, active=${activeBefore.length}`, blocker)
const sourcePath = process.env.ADMIN_TEST_PRICE_BOOK_FILE || DEFAULT_PRICE_BOOK_FILE
let sourceBytes
try {
sourceBytes = await readFile(sourcePath)
} catch (error) {
context.addStep(module, '读取船期价格测试文件', null, false,
`path=${sourcePath}, error=${error instanceof Error ? error.message : String(error)}`, blocker)
finishScenario(context, module, scenario, start)
return
}
const sourceHash = sha256(sourceBytes)
const uploadName = `${context.marker}-${basename(sourcePath)}`
context.addStep(module, '船期价格测试文件已隔离', null,
sourceBytes.length > 0 && uploadName.includes(context.marker) && uploadName.toLowerCase().endsWith('.xlsx'),
`fileName=${uploadName}, bytes=${sourceBytes.length}, sha256=${sourceHash}`, blocker)
const uploadResponse = await uploadWorkbook(context, sourceBytes, uploadName)
const uploaded = context.apiSuccess(module, '上传并解析船期价格表', uploadResponse,
`fileName=${uploadName}, bytes=${sourceBytes.length}, sha256=${sourceHash}`, blocker)
if (!uploaded?.version) {
finishScenario(context, module, scenario, start)
return
}
const versionId = String(uploaded.version.id || '')
context.createdResources.priceBookVersion = { versionId, marker: context.marker, sourceSha256: sourceHash }
context.assert(module, '上传返回隔离的待确认版本', uploadResponse,
validId(versionId)
&& uploaded.version.status === 'PENDING_CONFIRMATION'
&& uploaded.version.originalFileName === uploadName
&& uploaded.version.sourceSha256 === sourceHash,
`versionId=${versionId}, status=${uploaded.version.status}, fileName=${uploaded.version.originalFileName}, shaMatched=${uploaded.version.sourceSha256 === sourceHash}`, blocker)
const channels = Array.isArray(uploaded.channels) ? uploaded.channels : []
const rates = Array.isArray(uploaded.rates) ? uploaded.rates : []
const issues = Array.isArray(uploaded.issues) ? uploaded.issues : []
const channelCodes = channels.map((channel) => String(channel.channelCode))
const channelSet = new Set(channelCodes)
const expectedChannels = EXPECTED_CHANNEL_CODES.every((code) => channelSet.has(code))
context.assert(module, '解析出15个固定渠道且编码唯一', uploadResponse,
channels.length === EXPECTED_CHANNEL_CODES.length && channelSet.size === channels.length && expectedChannels
&& Number(uploaded.version.channelCount) === channels.length,
`channelCount=${channels.length}, unique=${channelSet.size}, missing=${EXPECTED_CHANNEL_CODES.filter((code) => !channelSet.has(code)).join(',') || 'none'}`, blocker)
const ratesValid = rates.length > 0 && rates.every((rate) => (
channelSet.has(String(rate.channelCode))
&& ['FBA_WAREHOUSE_RATE', 'REGION_WEIGHT_TIER'].includes(String(rate.priceModel))
&& String(rate.originWarehouseCode || '').trim()
&& ['KG', 'CBM'].includes(String(rate.chargeUnit))
&& Number(rate.unitPrice) > 0
&& /^[A-Z]{3}$/.test(String(rate.currency || ''))
&& String(rate.sourceSheetName || '').trim()
&& Number(rate.sourceRowNumber) >= 1
))
context.assert(module, '解析价格行完整且可追溯到 Excel', uploadResponse,
ratesValid && Number(uploaded.version.rateCount) === rates.length,
`rateCount=${rates.length}, declared=${uploaded.version.rateCount}, valid=${ratesValid}`, blocker)
const blockingIssues = issues.filter((issue) => issue.severity === 'BLOCKING')
const unexpectedBlocking = blockingIssues.filter((issue) => !MAPPING_ISSUE_CODES.has(issue.issueCode))
const infrastructureIssue = issues.find((issue) => issue.issueCode === 'UPLOAD_OR_PARSE_FAILED'
|| /SQLSyntaxError|bad SQL grammar|Service Unavailable/i.test(String(issue.message || '')))
context.assert(module, '解析阻断仅包含可确认的仓库映射问题', uploadResponse,
!infrastructureIssue && unexpectedBlocking.length === 0
&& Number(uploaded.version.blockingErrorCount) === blockingIssues.length,
`blocking=${blockingIssues.length}, unexpected=${[...new Set(unexpectedBlocking.map((issue) => issue.issueCode))].join(',') || 'none'}, infrastructure=${infrastructureIssue?.issueCode || 'none'}`, blocker)
const listPendingResponse = await context.request('GET', PRICE_BOOK_PATH)
const versionsPending = context.apiSuccess(module, '上传后回查价格版本列表', listPendingResponse, '', blocker)
const pendingEntry = versionsPending?.find?.((version) => String(version.id) === versionId)
const activeAfterUpload = versionsPending?.filter?.((version) => version.status === 'ACTIVE') || []
context.assert(module, '上传只生成待确认版本且不替换当前生效版本', listPendingResponse,
pendingEntry?.status === 'PENDING_CONFIRMATION'
&& activeAfterUpload.length === activeBefore.length
&& activeBefore.every((before) => activeAfterUpload.some((after) => String(after.id) === String(before.id))),
`pending=${pendingEntry?.status || 'missing'}, activeBefore=${activeBefore.map((item) => item.id).join(',') || 'none'}, activeAfterUpload=${activeAfterUpload.map((item) => item.id).join(',') || 'none'}`, blocker)
const detailResponse = await context.request('GET', `${PRICE_BOOK_PATH}/${encodeURIComponent(versionId)}`)
const detail = context.apiSuccess(module, '查询待确认版本渠道和价格预览', detailResponse, '', blocker)
context.assert(module, '详情与上传解析结果一致', detailResponse,
String(detail?.version?.id) === versionId
&& detail?.version?.status === 'PENDING_CONFIRMATION'
&& detail?.channels?.length === channels.length
&& detail?.rates?.length === rates.length
&& detail?.issues?.length === issues.length,
`versionId=${detail?.version?.id || 'missing'}, channels=${detail?.channels?.length ?? 'n/a'}, rates=${detail?.rates?.length ?? 'n/a'}, issues=${detail?.issues?.length ?? 'n/a'}`, blocker)
const sourceResponse = await context.requestBinary('GET', `${PRICE_BOOK_PATH}/${encodeURIComponent(versionId)}/source`)
const downloadedHash = sourceResponse.bytes?.length ? sha256(sourceResponse.bytes) : ''
const zipMagic = sourceResponse.bytes?.[0] === 0x50 && sourceResponse.bytes?.[1] === 0x4b
context.addStep(module, '下载原始 XLSX 且内容散列一致', sourceResponse,
sourceResponse.http === 200 && sourceResponse.bytes?.length === sourceBytes.length
&& zipMagic && downloadedHash === sourceHash
&& !sourceResponse.contentType.toLowerCase().includes('json'),
`bytes=${sourceResponse.bytes?.length ?? 0}, contentType=${sourceResponse.contentType || 'missing'}, zipMagic=${zipMagic}, sha256=${downloadedHash}`, blocker)
if (!detail || unexpectedBlocking.length > 0 || infrastructureIssue) {
finishScenario(context, module, scenario, start)
return
}
const domesticResponse = await context.request('GET', DOMESTIC_WAREHOUSE_PATH)
const domesticPage = context.apiSuccess(module, '查询启用的平台公共国内仓', domesticResponse, '', blocker)
const overseasResponse = await context.request('GET', OVERSEAS_WAREHOUSE_PATH)
const overseasPage = context.apiSuccess(module, '查询启用的平台公共海外仓', overseasResponse, '', blocker)
const warehouses = [
...(Array.isArray(domesticPage?.records) ? domesticPage.records : []),
...(Array.isArray(overseasPage?.records) ? overseasPage.records : []),
]
const mappings = buildMappings(detail, warehouses)
const mappingsComplete = mappings.originSources.size > 0
&& mappings.originMappings.length === mappings.originSources.size
&& mappings.overseasMappings.length === mappings.overseasSources.size
&& mappings.missingOrigins.length === 0
&& mappings.missingOverseas.length === 0
context.assert(module, '国内起运仓和目的海外仓均精确映射', overseasResponse,
mappingsComplete,
`origins=${mappings.originMappings.map((item) => `${item.originWarehouseCode}->${item.warehouseId}`).join(',') || 'none'}, overseas=${mappings.overseasMappings.map((item) => `${item.sourceDestinationName}->${item.warehouseId}`).join(',') || 'none'}, missing=${[...mappings.missingOrigins, ...mappings.missingOverseas].join(',') || 'none'}`, blocker)
if (!mappingsComplete) {
finishScenario(context, module, scenario, start)
return
}
const publishResponse = await context.request('POST', `${PRICE_BOOK_PATH}/${encodeURIComponent(versionId)}/publish`, {
originMappings: mappings.originMappings,
overseasMappings: mappings.overseasMappings,
})
const published = context.apiSuccess(module, '确认仓库映射并立即生效', publishResponse,
`versionId=${versionId}, originMappings=${mappings.originMappings.length}, overseasMappings=${mappings.overseasMappings.length}`, blocker)
context.assert(module, '发布后价格版本为 ACTIVE 且无阻断', publishResponse,
published?.version?.status === 'ACTIVE'
&& Number(published?.version?.blockingErrorCount) === 0
&& Boolean(published?.version?.publishedAt)
&& published?.channels?.length === channels.length
&& published?.rates?.length === rates.length
&& !(published?.issues || []).some((issue) => issue.severity === 'BLOCKING'),
`status=${published?.version?.status || 'missing'}, blocking=${published?.version?.blockingErrorCount ?? 'n/a'}, publishedAt=${published?.version?.publishedAt || 'missing'}, channels=${published?.channels?.length ?? 'n/a'}, rates=${published?.rates?.length ?? 'n/a'}`, blocker)
const listAfterResponse = await context.request('GET', PRICE_BOOK_PATH)
const versionsAfter = context.apiSuccess(module, '发布后回查当前生效和历史版本', listAfterResponse, '', blocker)
const activeAfter = versionsAfter?.filter?.((version) => version.status === 'ACTIVE') || []
const oldStatusesCorrect = activeBefore.every((before) => {
if (String(before.id) === versionId) return true
const after = versionsAfter?.find?.((candidate) => String(candidate.id) === String(before.id))
return after?.status === 'SUPERSEDED' && Boolean(after.supersededAt)
})
context.assert(module, '始终只有一份当前生效版本', listAfterResponse,
activeAfter.length === 1 && String(activeAfter[0]?.id) === versionId,
`active=${activeAfter.map((item) => item.id).join(',') || 'none'}`, blocker)
context.assert(module, '上一生效版本已保留为历史版本', listAfterResponse,
oldStatusesCorrect,
`previous=${activeBefore.map((item) => item.id).join(',') || 'none'}, superseded=${oldStatusesCorrect}`, blocker)
const activeDetailResponse = await context.request('GET', `${PRICE_BOOK_PATH}/${encodeURIComponent(versionId)}`)
const activeDetail = context.apiSuccess(module, '查询当前生效价格版本详情', activeDetailResponse, '', blocker)
context.assert(module, '当前生效版本预览数据完整', activeDetailResponse,
activeDetail?.version?.status === 'ACTIVE'
&& activeDetail?.channels?.length === channels.length
&& activeDetail?.rates?.length === rates.length,
`status=${activeDetail?.version?.status || 'missing'}, channels=${activeDetail?.channels?.length ?? 'n/a'}, rates=${activeDetail?.rates?.length ?? 'n/a'}`, blocker)
const repeatedPublishResponse = await context.request('POST', `${PRICE_BOOK_PATH}/${encodeURIComponent(versionId)}/publish`, {
originMappings: mappings.originMappings,
overseasMappings: mappings.overseasMappings,
})
const repeatRejected = repeatedPublishResponse.http === 200
&& String(repeatedPublishResponse.body?.code) !== '200'
context.addStep(module, '已生效版本禁止重复发布', repeatedPublishResponse, repeatRejected,
`HTTP=${repeatedPublishResponse.http}, code=${repeatedPublishResponse.body?.code ?? 'missing'}`, blocker)
const finalListResponse = await context.request('GET', PRICE_BOOK_PATH)
const finalVersions = context.apiSuccess(module, '重复发布被拒绝后回查版本状态', finalListResponse, '', blocker)
const finalActive = finalVersions?.filter?.((version) => version.status === 'ACTIVE') || []
context.assert(module, '重复发布未改变唯一生效版本', finalListResponse,
finalActive.length === 1 && String(finalActive[0]?.id) === versionId,
`active=${finalActive.map((item) => item.id).join(',') || 'none'}`, blocker)
finishScenario(context, module, scenario, start)
}
+148
View File
@@ -0,0 +1,148 @@
function id(value) { return value == null ? '' : String(value) }
export async function run(context) {
const module = 'system'
const suffix = context.marker.replace(/\D/g, '').slice(-12)
const permissionsResponse = await context.request('GET', '/api/platform/v1/iam/permissions')
const permissions = context.apiSuccess(module, '查询菜单权限资源', permissionsResponse)
context.assert(module, '权限资源返回数组', permissionsResponse, Array.isArray(permissions),
`count=${Array.isArray(permissions) ? permissions.length : 'n/a'}`)
const permissionPayload = {
type: 'BUTTON',
code: `platform:acceptance:${suffix}`,
name: `Admin验收权限-${suffix}`,
parentId: null,
routePath: null,
httpMethod: null,
sortNo: 9999,
}
const createPermissionResponse = await context.request('POST', '/api/platform/v1/iam/permissions', permissionPayload)
const permission = context.apiSuccess(module, '创建自定义权限资源', createPermissionResponse)
const permissionId = id(permission?.id)
context.assert(module, '权限创建字段正确', createPermissionResponse,
permissionId && permission?.permissionCode === permissionPayload.code && permission?.builtIn === false,
`permissionId=${permissionId || 'missing'}`)
if (!permissionId) throw new Error('权限资源创建失败,系统模块无法继续')
context.createdResources.systemPermission = { id: permissionId, code: permissionPayload.code }
const updatePermissionResponse = await context.request('PUT', `/api/platform/v1/iam/permissions/${permissionId}`, {
name: `${permissionPayload.name}-已编辑`, parentId: null, routePath: null, httpMethod: null, sortNo: 9998,
})
const updatedPermission = context.apiSuccess(module, '编辑自定义权限资源', updatePermissionResponse)
context.assert(module, '权限编辑正确回显', updatePermissionResponse,
updatedPermission?.permissionName === `${permissionPayload.name}-已编辑` && Number(updatedPermission?.sortNo) === 9998,
`permissionId=${permissionId}`)
for (const status of ['DISABLED', 'ACTIVE']) {
const response = await context.request('PUT', `/api/platform/v1/iam/permissions/${permissionId}/status`, { status })
const changed = context.apiSuccess(module, `${status === 'ACTIVE' ? '启用' : '停用'}自定义权限`, response)
context.assert(module, `权限状态回显 ${status}`, response, changed?.status === status, `status=${changed?.status ?? 'missing'}`)
}
const rolePayload = { roleCode: `ADMIN_ACCEPT_${suffix}`, roleName: `Admin验收角色-${suffix}`, description: context.marker }
const createRoleResponse = await context.request('POST', '/api/platform/v1/iam/roles', rolePayload)
const role = context.apiSuccess(module, '创建平台自定义角色', createRoleResponse)
const roleId = id(role?.id)
context.assert(module, '角色创建字段正确', createRoleResponse,
roleId && role?.roleCode === rolePayload.roleCode && role?.roleType === 'CUSTOM', `roleId=${roleId || 'missing'}`)
if (!roleId) throw new Error('角色创建失败,系统模块无法继续')
context.createdResources.systemRole = { id: roleId, code: rolePayload.roleCode, finalStatus: 'DISABLED', retainedBecauseAssigned: true }
const editRoleResponse = await context.request('PUT', `/api/platform/v1/iam/roles/${roleId}`, {
roleName: `${rolePayload.roleName}-已编辑`, description: `${context.marker}-EDITED`,
})
const editedRole = context.apiSuccess(module, '编辑平台自定义角色', editRoleResponse)
context.assert(module, '角色编辑正确回显', editRoleResponse,
editedRole?.roleName === `${rolePayload.roleName}-已编辑`, `roleId=${roleId}`)
const grantResponse = await context.request('PUT', '/api/platform/v1/iam/roles/permissions', { roleId, permissionIds: [permissionId] })
context.apiSuccess(module, '角色全量保存权限', grantResponse)
const rolesResponse = await context.request('GET', '/api/platform/v1/iam/roles')
const roles = context.apiSuccess(module, '回查平台角色列表', rolesResponse)
const listedRole = roles?.find?.((item) => id(item.id) === roleId)
context.assert(module, '角色权限 ID 正确回显', rolesResponse,
listedRole && listedRole.permissionIds?.map(id).includes(permissionId),
`permissionIds=${listedRole?.permissionIds?.map(id).join(',') || 'missing'}`)
for (const status of ['DISABLED', 'ACTIVE']) {
const response = await context.request('PUT', `/api/platform/v1/iam/roles/${roleId}/status`, { status })
const changed = context.apiSuccess(module, `${status === 'ACTIVE' ? '启用' : '停用'}自定义角色`, response)
context.assert(module, `角色状态回显 ${status}`, response, changed?.status === status, `status=${changed?.status ?? 'missing'}`)
}
const optionsResponse = await context.request('GET', '/api/platform/v1/iam/users/options')
const options = context.apiSuccess(module, '查询内部用户可选平台角色', optionsResponse)
context.assert(module, '用户选项包含平台与测试角色', optionsResponse,
options?.tenants?.some((item) => id(item.id) === '0') && options?.roles?.some((item) => id(item.id) === roleId),
`tenants=${options?.tenants?.length ?? 'n/a'}, roles=${options?.roles?.length ?? 'n/a'}`)
const phone = await context.findAvailablePhone(module)
const username = `admin_accept_${suffix}`
const initialPassword = `Accept${suffix}A1`
const resetPassword = `Reset${suffix}B2`
const userPayload = {
username, displayName: `Admin验收用户-${suffix}`, phone,
email: `${username}@example.com`, tenantId: '0', roleIds: [roleId],
password: initialPassword, status: 'ACTIVE',
}
const createUserResponse = await context.request('POST', '/api/platform/v1/iam/users', userPayload)
const user = context.apiSuccess(module, '创建平台内部用户', createUserResponse)
const membershipId = id(user?.membershipId)
context.assert(module, '内部用户账号、平台范围和角色正确', createUserResponse,
membershipId && user?.username === username && id(user?.tenantId) === '0' && user?.roleIds?.map(id).includes(roleId),
`membershipId=${membershipId || 'missing'}`)
if (!membershipId) throw new Error('平台内部用户创建失败,系统模块无法继续')
context.createdResources.systemUser = { membershipId, username, finalStatus: 'DISABLED' }
const pageResponse = await context.request('GET', `/api/platform/v1/iam/users?page=1&size=20&keyword=${username}`)
const page = context.apiSuccess(module, '按账号查询平台内部用户', pageResponse)
const listedUser = page?.records?.find((item) => id(item.membershipId) === membershipId)
const missingUserFields = listedUser ? requiredUserFields().filter((field) => !Object.hasOwn(listedUser, field)) : requiredUserFields()
context.assert(module, '用户列表包含新账号且字段完整', pageResponse,
listedUser && missingUserFields.length === 0
&& (!Object.hasOwn(listedUser, 'lastLoginAt') || typeof listedUser.lastLoginAt === 'string'),
`records=${page?.records?.length ?? 'n/a'}, missing=${missingUserFields.join(',') || 'none'}, lastLoginAt=${listedUser?.lastLoginAt ?? 'omitted'}`)
const updateUserPayload = {
displayName: `${userPayload.displayName}-已编辑`, phone,
email: `edited-${userPayload.email}`, tenantId: '0', roleIds: [roleId],
}
const updateUserResponse = await context.request('PUT', `/api/platform/v1/iam/users/${membershipId}`, updateUserPayload)
const updatedUser = context.apiSuccess(module, '编辑平台内部用户', updateUserResponse)
context.assert(module, '用户编辑正确回显', updateUserResponse,
updatedUser?.displayName === updateUserPayload.displayName && updatedUser?.email === updateUserPayload.email,
`membershipId=${membershipId}`)
const loginResponse = await context.request('POST', '/api/auth/login', { username, password: initialPassword }, '')
const loginPassed = loginResponse.http === 200 && String(loginResponse.body?.code) === '200' && Boolean(loginResponse.body?.data?.accessToken)
context.addStep(module, '新建内部用户可使用初始密码登录', loginResponse, loginPassed, `username=${username}`)
const resetResponse = await context.request('PUT', `/api/platform/v1/iam/users/${membershipId}/password`, { newPassword: resetPassword })
context.apiSuccess(module, '重置平台内部用户密码', resetResponse)
const resetLoginResponse = await context.request('POST', '/api/auth/login', { username, password: resetPassword }, '')
const resetLoginPassed = resetLoginResponse.http === 200 && String(resetLoginResponse.body?.code) === '200' && Boolean(resetLoginResponse.body?.data?.accessToken)
context.addStep(module, '内部用户使用重置密码登录', resetLoginResponse, resetLoginPassed, `username=${username}`)
const disableUserResponse = await context.request('PUT', `/api/platform/v1/iam/users/${membershipId}/status`, { status: 'DISABLED' })
const disabledUser = context.apiSuccess(module, '停用平台内部用户', disableUserResponse)
context.assert(module, '内部用户停用状态正确', disableUserResponse, disabledUser?.status === 'DISABLED', `status=${disabledUser?.status ?? 'missing'}`)
const disabledLoginResponse = await context.request('POST', '/api/auth/login', { username, password: resetPassword }, '')
context.addStep(module, '停用用户禁止登录', disabledLoginResponse,
disabledLoginResponse.http === 200 && String(disabledLoginResponse.body?.code) !== '200',
`HTTP=${disabledLoginResponse.http}, code=${disabledLoginResponse.body?.code ?? 'missing'}`)
const clearPermissionResponse = await context.request('PUT', '/api/platform/v1/iam/roles/permissions', { roleId, permissionIds: [] })
context.apiSuccess(module, '清空测试角色权限', clearPermissionResponse)
const deleteRoleResponse = await context.request('DELETE', `/api/platform/v1/iam/roles/${roleId}`)
context.expectConflict(module, '已分配成员的角色禁止删除', deleteRoleResponse, 'system-role-protection')
const finalDisableRoleResponse = await context.request('PUT', `/api/platform/v1/iam/roles/${roleId}/status`, { status: 'DISABLED' })
const finalRole = context.apiSuccess(module, '清理时停用测试角色', finalDisableRoleResponse)
context.assert(module, '保留测试角色处于停用状态', finalDisableRoleResponse, finalRole?.status === 'DISABLED',
`status=${finalRole?.status ?? 'missing'}`)
const deletePermissionResponse = await context.request('DELETE', `/api/platform/v1/iam/permissions/${permissionId}`)
context.apiSuccess(module, '删除临时权限资源', deletePermissionResponse)
}
function requiredUserFields() {
return ['membershipId', 'userId', 'authUserId', 'username', 'displayName', 'phone', 'email',
'tenantId', 'tenantName', 'memberType', 'status', 'roleIds', 'roleNames', 'protectedAccount']
}
File diff suppressed because it is too large Load Diff
+197
View File
@@ -0,0 +1,197 @@
import { readFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const SCENARIO = 'mojia-measurement'
const DEFAULT_IMAGE = resolve(
dirname(fileURLToPath(import.meta.url)), '..', '..', 'apps', 'pda-uniapp', 'static', 'c7.png')
function success(response) {
return response?.http === 200 && String(response?.body?.code) === '200'
}
function data(response) {
return response?.body?.data ?? null
}
function record(run, name, response, passed, evidence = '') {
return run.addStep(SCENARIO, name, response, passed, evidence)
}
function responseOf(http, body, raw = null) {
return {
method: 'POST',
path: null,
http,
body,
raw,
requestId: body?.requestId ?? null,
traceId: body?.traceId ?? null,
}
}
async function multipartRequest(run, path, fields, imagePath) {
const form = new FormData()
form.set('data', JSON.stringify(fields))
form.set('image', new Blob([await readFile(imagePath)], { type: 'image/png' }), 'mojia-e2e.png')
try {
const response = await fetch(`${run.options.gateway}${path}`, {
method: 'POST',
headers: { Accept: 'application/json', Authorization: `Bearer ${run.token}` },
body: form,
signal: AbortSignal.timeout(30_000),
})
const text = await response.text()
let body = null
try { body = text ? JSON.parse(text) : null } catch { /* Keep raw response for the report. */ }
return {
method: 'POST', path, http: response.status, body,
raw: body === null ? text.slice(0, 500) : null,
requestId: body?.requestId ?? response.headers.get('x-request-id'),
traceId: body?.traceId ?? response.headers.get('x-trace-id'),
}
} catch (error) {
return responseOf(0, null, error instanceof Error ? error.message : String(error))
}
}
function requireData(run, name, response, evidence = '') {
record(run, name, response, success(response) && data(response) !== null, evidence)
if (!success(response) || data(response) === null) {
throw new Error(`${name} failed: code=${response?.body?.code}, message=${response?.body?.message}`)
}
return data(response)
}
/**
* Run the internal platform simulation for the MoJia carton measurement flow.
* The final external MoJia key/signature contract is intentionally not defined here.
*
* @param {object} run Existing AcceptanceRun instance from admin-test-env-acceptance.mjs.
* @param {object} [options]
* @param {string|number} [options.tenantId] Customer scope for selecting the test inbound.
* @param {string|number} [options.inboundId] Explicit WAIT_MEASURE inbound, preferred for CI.
* @param {string} [options.imagePath] PNG/JPG evidence image path.
*/
export async function runMojiaMeasurementScenario(run, options = {}) {
const tenantId = options.tenantId ?? process.env.MOJIA_TEST_TENANT_ID
const imagePath = options.imagePath ?? process.env.MOJIA_TEST_IMAGE ?? DEFAULT_IMAGE
let inboundId = options.inboundId ?? process.env.MOJIA_TEST_INBOUND_ID
if (!tenantId && !inboundId) {
const response = responseOf(0, { code: 'DEPLOYMENT_BLOCKED', message: '缺少墨家测试 tenantId 或 inboundId' })
record(run, '墨家测量测试数据配置', response, false, '请传入 MOJIA_TEST_TENANT_ID 或 MOJIA_TEST_INBOUND_ID')
return false
}
try {
if (!inboundId) {
const response = await run.request(
'GET', '/api/platform/inbounds?processStatus=WAIT_MEASURE&page=1&size=200')
const rows = requireData(run, '查找墨家 WAIT_MEASURE 入库单', response)
const candidate = rows.records?.find((row) => String(row.tenantId) === String(tenantId)
&& row.totalPackageCount == null)
if (!candidate) {
record(run, '墨家测量测试数据配置', response, false,
`tenantId=${tenantId} 下没有未冻结总箱数的 WAIT_MEASURE 入库单`)
return false
}
inboundId = String(candidate.id)
}
const detailPath = `/api/platform/inbounds/${inboundId}?tenantId=${tenantId}`
const detailResponse = await run.request('GET', detailPath)
const detail = requireData(run, '查询墨家测试入库单', detailResponse, `inboundId=${inboundId}`)
const summary = detail.summary
if (summary.processStatus !== 'WAIT_MEASURE' || summary.totalPackageCount != null) {
record(run, '墨家测试入库单状态校验', detailResponse, false,
`processStatus=${summary.processStatus}, totalPackageCount=${summary.totalPackageCount}`)
return false
}
const resolvedTenantId = String(summary.tenantId ?? tenantId)
const printPath = `/api/platform/inbounds/${inboundId}/carton-label-pdf?tenantId=${resolvedTenantId}`
const printResponse = await run.requestBinary('POST', printPath)
record(run, '墨家测试生成稳定箱唛 PDF', printResponse,
printResponse.http === 200 && printResponse.bytes?.length > 0
&& printResponse.contentType?.toLowerCase().includes('application/pdf'),
`inboundId=${inboundId}, bytes=${printResponse.bytes?.length ?? 0}`)
const afterPrintResponse = await run.request('GET', detailPath)
const afterPrint = requireData(run, '回查墨家稳定箱号总数', afterPrintResponse)
const printedTotal = Number(afterPrint.summary.totalPackageCount)
if (!Number.isInteger(printedTotal) || printedTotal < 1) {
throw new Error('打印后没有冻结原始总箱数')
}
const boxCode = `${afterPrint.summary.inboundCode}#${printedTotal}-1`
const scanPath = `/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/carton-scans?tenantId=${resolvedTenantId}`
const firstFields = {
boxCode, length: 60.1, width: 40.2, height: 30.3, weight: 12.5,
machineNo: 'MJ-ADMIN-E2E-01', measuredAt: '2026-08-08T10:00:00',
}
const firstResponse = await multipartRequest(run, scanPath, firstFields, imagePath)
const first = requireData(run, '墨家单箱首次测量上传', firstResponse, `boxCode=${boxCode}`)
const firstPassed = first.created === true && first.confirmedPackageCount === 1
&& String(first.boxCode) === boxCode
record(run, '墨家单箱首次记录语义', firstResponse, firstPassed,
`itemId=${first.itemId}, measurementId=${first.measurementId}`)
if (!firstPassed) throw new Error('首次扫描没有创建单箱记录或默认箱数不是 1')
const countPath = `/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/items/${first.itemId}/confirmed-package-count?tenantId=${resolvedTenantId}`
const countResponse = await run.request('PUT', countPath, {
measurementVersion: first.measurementVersion,
confirmedPackageCount: printedTotal,
})
const counted = requireData(run, '人工确认墨家单箱代表箱数', countResponse,
`confirmedPackageCount=${printedTotal}`)
if (counted.confirmedPackageCount !== printedTotal) {
throw new Error('人工确认箱数回显不一致')
}
const rescanResponse = await multipartRequest(run, scanPath, {
boxCode, length: 61.1, width: 41.2, height: 31.3, weight: 13.5,
machineNo: 'MJ-ADMIN-E2E-02', measuredAt: '2026-08-08T10:01:00',
}, imagePath)
const rescan = requireData(run, '墨家同箱复测覆盖', rescanResponse)
const rescanPassed = rescan.created === false
&& String(rescan.itemId) === String(first.itemId)
&& rescan.confirmedPackageCount === printedTotal
&& Number(rescan.lengthCm) === 61.1
record(run, '墨家同箱复测保留人工箱数', rescanResponse, rescanPassed,
`itemId=${rescan.itemId}, confirmedPackageCount=${rescan.confirmedPackageCount}`)
if (!rescanPassed) throw new Error('同箱复测没有覆盖设备值或覆盖人工箱数')
const submitPath = `/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/submit?tenantId=${resolvedTenantId}`
const submitResponse = await run.request('POST', submitPath, {
measurementId: rescan.measurementId,
version: rescan.measurementVersion,
})
const submitted = requireData(run, '提交墨家测量审核', submitResponse)
const submittedPassed = submitted.processStatus === 'MEASURE_PENDING'
&& submitted.measureReviewStatus === 'PENDING'
record(run, '墨家测量进入待审核', submitResponse, submittedPassed,
`processStatus=${submitted.processStatus}, reviewStatus=${submitted.measureReviewStatus}`)
if (!submittedPassed) throw new Error('墨家测量提交后状态不正确')
const blockedResponse = await multipartRequest(run, scanPath, {
boxCode, length: 99.1, width: 99.2, height: 99.3, weight: 99.4,
machineNo: 'MJ-ADMIN-E2E-BLOCKED', measuredAt: '2026-08-08T10:02:00',
}, imagePath)
const blockedPassed = blockedResponse.body?.code === 'A0409'
&& blockedResponse.body?.message?.includes('测量已提交')
record(run, '提交后拒绝墨家设备覆盖', blockedResponse, blockedPassed,
`code=${blockedResponse.body?.code}, message=${blockedResponse.body?.message ?? ''}`)
if (!blockedPassed) throw new Error('提交后没有拒绝墨家设备覆盖')
run.context ??= {}
run.context.mojiaMeasurement = {
tenantId: resolvedTenantId, inboundId: String(inboundId), boxCode,
measurementId: String(rescan.measurementId), itemId: String(rescan.itemId),
}
return true
} catch (error) {
const response = responseOf(0, null, error instanceof Error ? error.message : String(error))
record(run, '墨家测量场景执行', response, false, response.raw)
return false
}
}