优化
This commit is contained in:
@@ -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
|
||||
|
||||
+7
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+20
@@ -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() {
|
||||
|
||||
+34
-12
@@ -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
@@ -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`,不得跳过或模拟成功。
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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'}`)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user