优化
This commit is contained in:
+2
@@ -39,6 +39,8 @@ class PlatformInboundMeasurementControllerContractTest {
|
||||
+ " and (!#request.hasAdjustment() or hasAuthority('platform:firstleg:inbound:weight:edit'))",
|
||||
AuthenticatedUser.class, long.class, long.class, long.class,
|
||||
InboundMeasurementRequests.ConfirmWeightResultRequest.class);
|
||||
assertAuthority("deleteWeightResult", "hasAuthority('platform:firstleg:inbound:weight:edit')",
|
||||
AuthenticatedUser.class, long.class, int.class, long.class, long.class);
|
||||
}
|
||||
|
||||
private void assertAuthority(String methodName, String expected, Class<?>... parameterTypes)
|
||||
|
||||
+112
-35
@@ -1,65 +1,142 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.baiyue.firstleg.domain.CustomerDimensionBoxType;
|
||||
import com.baiyue.firstleg.mapper.CustomerDimensionConfigMapper;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CustomerDimensionConfigSqlContractTest {
|
||||
@Test
|
||||
void moduleMigrationDefinesUniqueCustomerConfigAndDefault() throws IOException {
|
||||
String sql = new ClassPathResource("db/customer-dimension-config.sql")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
void allFourDatabaseEntrypointsDefineTheSameFourRowStructure() throws Exception {
|
||||
List<String> sqlEntrypoints = List.of(
|
||||
resource("db/customer-dimension-config.sql"),
|
||||
resource("db/firstleg.sql"),
|
||||
resource("db/phase2-inbound-schema.sql"),
|
||||
Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
|
||||
StandardCharsets.UTF_8)
|
||||
);
|
||||
|
||||
assertThat(sql)
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_customer_dimension_config")
|
||||
.contains("edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000")
|
||||
.contains("initialization_key VARCHAR(128) NOT NULL")
|
||||
.contains("UNIQUE KEY uk_customer_dimension_config_tenant (tenant_id)");
|
||||
for (String sql : sqlEntrypoints) {
|
||||
assertThat(sql)
|
||||
.contains("fl_customer_dimension_config")
|
||||
.contains("box_type VARCHAR(24) NOT NULL")
|
||||
.contains("inner_length_cm DECIMAL(9,3) NOT NULL")
|
||||
.contains("inner_width_cm DECIMAL(9,3) NOT NULL")
|
||||
.contains("inner_height_cm DECIMAL(9,3) NOT NULL")
|
||||
.contains("edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000")
|
||||
.contains("initialization_key VARCHAR(128) NOT NULL")
|
||||
.contains("created_by BIGINT UNSIGNED NOT NULL")
|
||||
.contains("updated_by BIGINT UNSIGNED NOT NULL")
|
||||
.contains("version INT NOT NULL DEFAULT 0")
|
||||
.contains("UNIQUE KEY uk_customer_dimension_config_tenant_box (tenant_id, box_type)")
|
||||
.contains("CHECK (box_type IN ('SMALL', 'MEDIUM', 'LARGE', 'EXTRA_LARGE'))")
|
||||
.doesNotContain("UNIQUE KEY uk_customer_dimension_config_tenant (tenant_id)");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void unifiedMigrationIncludesConfigAndFinalAssertion() throws IOException {
|
||||
String sql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
|
||||
StandardCharsets.UTF_8);
|
||||
void fixedDefaultsAndBatchInitializationCoverExactlyFourBoxTypes() throws Exception {
|
||||
assertThat(CustomerDimensionBoxType.values()).extracting(CustomerDimensionBoxType::name)
|
||||
.containsExactly("SMALL", "MEDIUM", "LARGE", "EXTRA_LARGE");
|
||||
assertThat(List.of(CustomerDimensionBoxType.values()))
|
||||
.extracting(CustomerDimensionBoxType::defaultLengthCm)
|
||||
.containsExactly(new java.math.BigDecimal("30.000"), new java.math.BigDecimal("40.000"),
|
||||
new java.math.BigDecimal("50.000"), new java.math.BigDecimal("60.000"));
|
||||
|
||||
Insert annotation = CustomerDimensionConfigMapper.class
|
||||
.getDeclaredMethod("insertDefaults", List.class, long.class)
|
||||
.getAnnotation(Insert.class);
|
||||
String sql = String.join(" ", annotation.value());
|
||||
assertThat(sql)
|
||||
.contains("CREATE TABLE IF NOT EXISTS baiyue_firstleg.fl_customer_dimension_config")
|
||||
.contains("客户尺寸计算配置表未创建")
|
||||
.contains("uk_customer_dimension_config_tenant")
|
||||
.contains("CREATE PROCEDURE unified_guard_dimension_config_permissions()")
|
||||
.contains("id = 5102111 AND COALESCE(permission_code, '') <> 'firstleg:dimension-config:view'")
|
||||
.contains("permission_code = 'firstleg:dimension-config:view' AND id <> 5102111")
|
||||
.contains("id = 5102112 AND COALESCE(permission_code, '') <> 'firstleg:dimension-config:edit'")
|
||||
.contains("permission_code = 'firstleg:dimension-config:edit' AND id <> 5102112")
|
||||
.contains("(5102111, 0, 'PLATFORM', 'BUTTON', 5100010")
|
||||
.contains("(5102112, 0, 'PLATFORM', 'BUTTON', 5100010")
|
||||
.contains("r.role_code = 'PLATFORM_ADMIN'")
|
||||
.contains("r.role_code = 'ORDER_OPERATOR'")
|
||||
.contains("客户尺寸计算配置权限父节点企业客户不存在")
|
||||
.contains("客户尺寸计算配置权限码、固定ID或父节点不正确")
|
||||
.contains("平台管理员未获得客户尺寸计算配置读写权限")
|
||||
.contains("订单运营角色的客户尺寸计算配置权限不是只读");
|
||||
.contains("INSERT IGNORE INTO fl_customer_dimension_config")
|
||||
.contains("box_type, inner_length_cm, inner_width_cm, inner_height_cm")
|
||||
.contains("<foreach collection=\"rows\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void compensationDeleteRequiresExactKeyDefaultValueAndUnmodifiedVersion() throws Exception {
|
||||
Delete annotation = com.baiyue.firstleg.mapper.CustomerDimensionConfigMapper.class
|
||||
.getDeclaredMethod("deleteUnchangedDefault", long.class, long.class,
|
||||
String.class, java.math.BigDecimal.class)
|
||||
void compensationLocksThenDeletesOnlyExactDefaultRows() throws Exception {
|
||||
Select select = CustomerDimensionConfigMapper.class
|
||||
.getDeclaredMethod("selectDefaultsForCompensation", long.class, String.class)
|
||||
.getAnnotation(Select.class);
|
||||
assertThat(String.join(" ", select.value()))
|
||||
.contains("tenant_id = #{tenantId}")
|
||||
.contains("initialization_key = #{initializationKey}")
|
||||
.contains("FOR UPDATE");
|
||||
|
||||
Delete annotation = CustomerDimensionConfigMapper.class
|
||||
.getDeclaredMethod("deleteUnchangedDefaults", long.class, String.class)
|
||||
.getAnnotation(Delete.class);
|
||||
String sql = String.join(" ", annotation.value());
|
||||
|
||||
assertThat(sql)
|
||||
.contains("tenant_id = #{tenantId}")
|
||||
.contains("initialization_key = #{initializationKey}")
|
||||
.contains("edge_addition_cm = #{edgeAdditionCm}")
|
||||
.contains("version = 0");
|
||||
.contains("version = 0")
|
||||
.contains("box_type IN ('SMALL', 'MEDIUM', 'LARGE', 'EXTRA_LARGE')")
|
||||
.contains("inner_length_cm = CASE box_type")
|
||||
.contains("edge_addition_cm = 2.000")
|
||||
.contains("remark = CASE box_type");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unifiedMigrationKeepsPermissionGuardsAndAssertsFinalFourRowSchema() throws Exception {
|
||||
String sql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(sql)
|
||||
.contains("CREATE PROCEDURE unified_guard_dimension_config_permissions()")
|
||||
.contains("(5102111, 0, 'PLATFORM', 'BUTTON', 5100010")
|
||||
.contains("r.role_code = 'PLATFORM_ADMIN'")
|
||||
.contains("r.role_code = 'ORDER_OPERATOR'")
|
||||
.contains("index_name = 'uk_customer_dimension_config_tenant_box'")
|
||||
.contains("客户四箱规尺寸配置字段不完整")
|
||||
.contains("客户尺寸计算配置权限码、固定ID或父节点不正确");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unifiedMigrationUpgradesOnlyRecognizedLegacyTableAndBackfillsIdempotently() throws Exception {
|
||||
String sql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
int upgrade = sql.indexOf("CREATE PROCEDURE unified_upgrade_legacy_customer_dimension_config()");
|
||||
int create = sql.indexOf("CREATE TABLE IF NOT EXISTS baiyue_firstleg.fl_customer_dimension_config");
|
||||
int backfill = sql.indexOf("INSERT INTO baiyue_firstleg.fl_customer_dimension_config (", create);
|
||||
assertThat(upgrade).isGreaterThanOrEqualTo(0);
|
||||
assertThat(create).isGreaterThan(upgrade);
|
||||
assertThat(backfill).isGreaterThan(create);
|
||||
assertThat(sql)
|
||||
.contains("AND column_name = 'box_type') = 0")
|
||||
.contains("AND column_name IN ('id', 'tenant_id', 'edge_addition_cm', 'remark', 'initialization_key'")
|
||||
.contains("AND index_name = 'uk_customer_dimension_config_tenant'")
|
||||
.contains("DROP TABLE baiyue_firstleg.fl_customer_dimension_config")
|
||||
.contains("客户尺寸配置表不是可识别的旧单行结构")
|
||||
.contains("SELECT id AS tenant_id, created_by")
|
||||
.contains("FROM baiyue_iam.iam_tenant")
|
||||
.contains("WHERE tenant_type = 'MERCHANT' AND deleted = 0 AND id > 0")
|
||||
.contains("CONCAT('unified-four-box-default-v1:', tenant.tenant_id)")
|
||||
.contains("ON DUPLICATE KEY UPDATE id = id")
|
||||
.contains("现有企业客户未完整持有固定四箱规尺寸配置");
|
||||
|
||||
String repeatSafeClause = sql.substring(backfill,
|
||||
sql.indexOf("DROP PROCEDURE IF EXISTS unified_assert_final_extensions", backfill));
|
||||
assertThat(repeatSafeClause)
|
||||
.doesNotContain("edge_addition_cm = VALUES")
|
||||
.doesNotContain("inner_length_cm = VALUES")
|
||||
.doesNotContain("remark = VALUES")
|
||||
.doesNotContain("version = VALUES");
|
||||
}
|
||||
|
||||
private static String resource(String path) throws Exception {
|
||||
return new ClassPathResource(path).getContentAsString(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
+141
-48
@@ -1,22 +1,30 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.domain.CustomerDimensionBoxType;
|
||||
import com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.GroupResponse;
|
||||
import com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.InitializeResponse;
|
||||
import com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.RuleUpdateRequest;
|
||||
import com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.UpdateRequest;
|
||||
import com.baiyue.firstleg.entity.CustomerDimensionConfigEntity;
|
||||
import com.baiyue.firstleg.repository.CustomerDimensionConfigRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
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 java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -28,82 +36,167 @@ class CustomerDimensionConfigServiceTest {
|
||||
private CustomerDimensionConfigService service;
|
||||
|
||||
@Test
|
||||
void initializesTwoCentimeterDefaultExactlyOnce() {
|
||||
CustomerDimensionConfigEntity persisted = config(91L, 8L, "2.000", 0, "registration-1");
|
||||
when(repository.find(8L)).thenReturn(null, persisted);
|
||||
when(repository.insertDefault(anyLong(), eq(8L), eq(new BigDecimal("2.000")),
|
||||
eq("默认每侧加宽 2cm"), eq("registration-1"), eq(100L))).thenReturn(true);
|
||||
|
||||
InitializeResponse result = service.initializeDefault(8L, 100L, "registration-1");
|
||||
|
||||
assertThat(result.createdByThisInitialization()).isTrue();
|
||||
assertThat(result.configId()).isEqualTo(91L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryAfterLostResponseKeepsCreationOwnershipForSameKey() {
|
||||
when(repository.find(8L)).thenReturn(config(91L, 8L, "2.000", 0, "registration-1"));
|
||||
void initializesFourDefaultsAtomicallyInFixedOrder() {
|
||||
List<CustomerDimensionConfigEntity> persisted = defaultConfigs("registration-1");
|
||||
when(repository.findAll(8L)).thenReturn(List.of(), persisted);
|
||||
when(repository.insertDefaults(any(), eq(100L))).thenReturn(4);
|
||||
|
||||
InitializeResponse result = service.initializeDefault(8L, 100L, "registration-1");
|
||||
|
||||
assertThat(result).isEqualTo(new InitializeResponse(91L, true));
|
||||
verify(repository, never()).insertDefault(anyLong(), anyLong(),
|
||||
eq(new BigDecimal("2.000")), eq("默认每侧加宽 2cm"),
|
||||
eq("registration-1"), anyLong());
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<CustomerDimensionConfigEntity>> captor = ArgumentCaptor.forClass(List.class);
|
||||
verify(repository).insertDefaults(captor.capture(), eq(100L));
|
||||
assertThat(captor.getValue()).extracting(CustomerDimensionConfigEntity::getBoxType)
|
||||
.containsExactly(CustomerDimensionBoxType.values());
|
||||
assertThat(captor.getValue()).extracting(CustomerDimensionConfigEntity::getInnerLengthCm)
|
||||
.containsExactly(new BigDecimal("30.000"), new BigDecimal("40.000"),
|
||||
new BigDecimal("50.000"), new BigDecimal("60.000"));
|
||||
assertThat(captor.getValue()).extracting(CustomerDimensionConfigEntity::getInnerWidthCm)
|
||||
.containsExactly(new BigDecimal("20.000"), new BigDecimal("30.000"),
|
||||
new BigDecimal("40.000"), new BigDecimal("50.000"));
|
||||
assertThat(captor.getValue()).extracting(CustomerDimensionConfigEntity::getInnerHeightCm)
|
||||
.containsExactly(new BigDecimal("20.000"), new BigDecimal("30.000"),
|
||||
new BigDecimal("40.000"), new BigDecimal("50.000"));
|
||||
assertThat(captor.getValue()).extracting(CustomerDimensionConfigEntity::getEdgeAdditionCm)
|
||||
.containsOnly(new BigDecimal("2.000"));
|
||||
assertThat(captor.getValue()).extracting(CustomerDimensionConfigEntity::getRemark)
|
||||
.containsExactly("标准小箱", "标准中箱", "标准大箱", "特大箱");
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryAfterLostResponseKeepsCreationOwnershipForSameKey() {
|
||||
when(repository.findAll(8L)).thenReturn(defaultConfigs("registration-1"));
|
||||
|
||||
InitializeResponse result = service.initializeDefault(8L, 100L, "registration-1");
|
||||
|
||||
assertThat(result).isEqualTo(new InitializeResponse(91L, true));
|
||||
verify(repository, never()).insertDefaults(any(), eq(100L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void differentInitializationKeyDoesNotTakeOwnershipOrOverwrite() {
|
||||
when(repository.find(8L)).thenReturn(config(91L, 8L, "2.000", 0, "registration-1"));
|
||||
when(repository.findAll(8L)).thenReturn(defaultConfigs("registration-1"));
|
||||
|
||||
InitializeResponse result = service.initializeDefault(8L, 100L, "registration-2");
|
||||
|
||||
assertThat(result).isEqualTo(new InitializeResponse(91L, false));
|
||||
verify(repository, never()).insertDefault(anyLong(), anyLong(),
|
||||
eq(new BigDecimal("2.000")), eq("默认每侧加宽 2cm"),
|
||||
eq("registration-2"), anyLong());
|
||||
verify(repository, never()).insertDefaults(any(), eq(100L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsStaleUpdateVersion() {
|
||||
when(repository.find(8L)).thenReturn(config(91L, 8L, "2.000", 3, "registration-1"));
|
||||
void returnsFixedOrderCalculatedOuterDimensionsAndThreeDecimalVolume() {
|
||||
List<CustomerDimensionConfigEntity> shuffled = new ArrayList<>(defaultConfigs("registration-1"));
|
||||
Collections.reverse(shuffled);
|
||||
when(repository.findAll(8L)).thenReturn(shuffled);
|
||||
|
||||
assertThatThrownBy(() -> service.update(8L, 100L,
|
||||
new UpdateRequest(new BigDecimal("2.500"), "新配置", 2)))
|
||||
GroupResponse result = service.get(8L);
|
||||
|
||||
assertThat(result.rules()).extracting(rule -> rule.boxType())
|
||||
.containsExactly(CustomerDimensionBoxType.values());
|
||||
assertThat(result.rules().get(0).boxTypeName()).isEqualTo("小箱");
|
||||
assertThat(result.rules().get(0).outerLengthCm()).isEqualByComparingTo("34.000");
|
||||
assertThat(result.rules().get(0).outerWidthCm()).isEqualByComparingTo("24.000");
|
||||
assertThat(result.rules().get(0).outerHeightCm()).isEqualByComparingTo("24.000");
|
||||
assertThat(result.rules().get(0).volumeCbm()).isEqualByComparingTo("0.020");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsMissingOrDuplicateBoxTypes() {
|
||||
List<RuleUpdateRequest> rules = new ArrayList<>(updateRequest(0).rules());
|
||||
rules.set(3, rules.get(0));
|
||||
|
||||
assertThatThrownBy(() -> service.update(8L, 100L, new UpdateRequest(rules)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("重复");
|
||||
verify(repository, never()).findAll(8L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsAnyStaleVersionBeforeWritingRows() {
|
||||
List<CustomerDimensionConfigEntity> persisted = defaultConfigs("registration-1");
|
||||
persisted.forEach(value -> value.setVersion(3));
|
||||
when(repository.findAll(8L)).thenReturn(persisted);
|
||||
UpdateRequest request = updateRequest(3);
|
||||
List<RuleUpdateRequest> rules = new ArrayList<>(request.rules());
|
||||
RuleUpdateRequest stale = rules.get(2);
|
||||
rules.set(2, new RuleUpdateRequest(stale.boxType(), stale.innerLengthCm(), stale.innerWidthCm(),
|
||||
stale.innerHeightCm(), stale.edgeAdditionCm(), stale.remark(), 2));
|
||||
|
||||
assertThatThrownBy(() -> service.update(8L, 100L, new UpdateRequest(rules)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("刷新后重试");
|
||||
verify(repository, never()).update(org.mockito.ArgumentMatchers.any());
|
||||
verify(repository, never()).update(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void compensationUsesTenantKeyDefaultValueAndUnmodifiedVersion() {
|
||||
when(repository.find(8L)).thenReturn(config(91L, 8L, "2.000", 0, "registration-1"));
|
||||
void abortsWholeTransactionalUpdateWhenAnyOptimisticWriteFails() {
|
||||
when(repository.findAll(8L)).thenReturn(defaultConfigs("registration-1"));
|
||||
when(repository.update(any())).thenReturn(1, 0);
|
||||
|
||||
service.compensateDefault(8L, "registration-1");
|
||||
|
||||
verify(repository).deleteUnchangedDefault(
|
||||
8L, 91L, "registration-1", new BigDecimal("2.000"));
|
||||
assertThatThrownBy(() -> service.update(8L, 100L, updateRequest(0)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("刷新后重试");
|
||||
verify(repository, times(2)).update(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void editedConfigIsNotCompensatedByDelayedRollback() {
|
||||
when(repository.find(8L)).thenReturn(config(91L, 8L, "2.500", 1, "registration-1"));
|
||||
void compensationLocksAndDeletesExactlyFourUnchangedDefaults() {
|
||||
when(repository.findDefaultsForCompensation(8L, "registration-1"))
|
||||
.thenReturn(defaultConfigs("registration-1"));
|
||||
when(repository.deleteUnchangedDefaults(8L, "registration-1")).thenReturn(4);
|
||||
|
||||
service.compensateDefault(8L, "registration-1");
|
||||
|
||||
verify(repository).deleteUnchangedDefault(
|
||||
8L, 91L, "registration-1", new BigDecimal("2.000"));
|
||||
verify(repository).deleteUnchangedDefaults(8L, "registration-1");
|
||||
}
|
||||
|
||||
private static CustomerDimensionConfigEntity config(long id, long tenantId,
|
||||
String edgeAddition, int version,
|
||||
String initializationKey) {
|
||||
CustomerDimensionConfigEntity value = new CustomerDimensionConfigEntity();
|
||||
value.setId(id);
|
||||
value.setTenantId(tenantId);
|
||||
value.setEdgeAdditionCm(new BigDecimal(edgeAddition));
|
||||
value.setVersion(version);
|
||||
value.setInitializationKey(initializationKey);
|
||||
return value;
|
||||
@Test
|
||||
void compensationDoesNotDeleteAnEditedGroup() {
|
||||
List<CustomerDimensionConfigEntity> values = defaultConfigs("registration-1");
|
||||
values.get(2).setVersion(1);
|
||||
when(repository.findDefaultsForCompensation(8L, "registration-1")).thenReturn(values);
|
||||
|
||||
service.compensateDefault(8L, "registration-1");
|
||||
|
||||
verify(repository, never()).deleteUnchangedDefaults(8L, "registration-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void compensationRollsBackInsteadOfAcceptingPartialDelete() {
|
||||
when(repository.findDefaultsForCompensation(8L, "registration-1"))
|
||||
.thenReturn(defaultConfigs("registration-1"));
|
||||
when(repository.deleteUnchangedDefaults(8L, "registration-1")).thenReturn(3);
|
||||
|
||||
assertThatThrownBy(() -> service.compensateDefault(8L, "registration-1"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("补偿删除失败");
|
||||
}
|
||||
|
||||
private static UpdateRequest updateRequest(int version) {
|
||||
return new UpdateRequest(List.of(CustomerDimensionBoxType.values()).stream()
|
||||
.map(type -> new RuleUpdateRequest(type, type.defaultLengthCm(), type.defaultWidthCm(),
|
||||
type.defaultHeightCm(), new BigDecimal("2.000"), type.defaultRemark(), version))
|
||||
.toList());
|
||||
}
|
||||
|
||||
private static List<CustomerDimensionConfigEntity> defaultConfigs(String initializationKey) {
|
||||
List<CustomerDimensionConfigEntity> values = new ArrayList<>();
|
||||
long id = 91L;
|
||||
for (CustomerDimensionBoxType type : CustomerDimensionBoxType.values()) {
|
||||
CustomerDimensionConfigEntity value = new CustomerDimensionConfigEntity();
|
||||
value.setId(id++);
|
||||
value.setTenantId(8L);
|
||||
value.setBoxType(type);
|
||||
value.setInnerLengthCm(type.defaultLengthCm());
|
||||
value.setInnerWidthCm(type.defaultWidthCm());
|
||||
value.setInnerHeightCm(type.defaultHeightCm());
|
||||
value.setEdgeAdditionCm(new BigDecimal("2.000"));
|
||||
value.setRemark(type.defaultRemark());
|
||||
value.setVersion(0);
|
||||
value.setInitializationKey(initializationKey);
|
||||
values.add(value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.firstleg.client.MeasurementImageClient;
|
||||
import com.baiyue.firstleg.dto.InboundMeasurementRequests;
|
||||
import com.baiyue.firstleg.dto.InboundMeasurementResponses;
|
||||
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
|
||||
@@ -18,6 +20,7 @@ import org.junit.jupiter.params.provider.CsvSource;
|
||||
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 java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
@@ -27,17 +30,21 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.tuple;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("平台入库测量应用服务")
|
||||
@SpringJUnitConfig(InboundMeasurementService.class)
|
||||
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
|
||||
class InboundMeasurementServiceTest {
|
||||
@MockBean private InboundMeasurementRepository repository;
|
||||
@MockBean private MeasurementBatchService batchService;
|
||||
@MockBean private MeasurementImageClient imageClient;
|
||||
@Resource private InboundMeasurementService service;
|
||||
|
||||
@Test
|
||||
@@ -357,6 +364,58 @@ class InboundMeasurementServiceTest {
|
||||
verify(repository, never()).updateInbound(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletingPendingWeightRemovesResultAndOwnedImage() {
|
||||
InboundOrderEntity inbound = inbound("MEASURING");
|
||||
InboundWeightResultEntity weight = weight(20L, "285.500", "KG", "PENDING_CONFIRM", 1);
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
|
||||
when(repository.findWeight(1L, 10L, 20L)).thenReturn(weight);
|
||||
when(repository.deleteWeight(1L, 10L, 20L, 1)).thenReturn(true);
|
||||
when(imageClient.delete("internal-token", 9L, 1L, 10L, 120L))
|
||||
.thenReturn(ApiResponse.success(null));
|
||||
|
||||
service.deleteWeightResult(1L, 10L, 20L, 1, 9L);
|
||||
|
||||
verify(repository).deleteWeight(1L, 10L, 20L, 1);
|
||||
verify(imageClient).delete("internal-token", 9L, 1L, 10L, 120L);
|
||||
verify(repository, never()).updateInbound(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletingLastConfirmedWeightClearsInboundTotal() {
|
||||
InboundOrderEntity inbound = inbound("MEASURING");
|
||||
inbound.setActualGrossWeightKg(new BigDecimal("285.500"));
|
||||
InboundWeightResultEntity weight = weight(20L, "285.500", "KG", "CONFIRMED", 2);
|
||||
weight.setFinalWeightKg(new BigDecimal("285.500"));
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
|
||||
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(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);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletingWeightRejectsStaleVersionBeforeCallingFileService() {
|
||||
InboundOrderEntity inbound = inbound("MEASURING");
|
||||
InboundWeightResultEntity weight = weight(20L, "285.500", "KG", "PENDING_CONFIRM", 2);
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
|
||||
when(repository.findWeight(1L, 10L, 20L)).thenReturn(weight);
|
||||
|
||||
assertThatThrownBy(() -> service.deleteWeightResult(1L, 10L, 20L, 1, 9L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("版本已变化");
|
||||
|
||||
verify(repository, never()).deleteWeight(anyLong(), anyLong(), anyLong(), anyInt());
|
||||
verifyNoInteractions(imageClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void submitAdvancesOnlyToMeasurePendingAfterAllWeightsConfirmed() {
|
||||
InboundOrderEntity inbound = inbound("MEASURING");
|
||||
|
||||
Reference in New Issue
Block a user