优化
This commit is contained in:
+13
@@ -14,6 +14,8 @@ import java.util.Set;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@SpringJUnitConfig(MerchantInboundBillingController.class)
|
||||
class MerchantInboundBillingControllerContractTest {
|
||||
@@ -38,6 +40,17 @@ class MerchantInboundBillingControllerContractTest {
|
||||
"admin", "PLATFORM", "WEB", Set.of());
|
||||
assertThatThrownBy(() -> controller.find(platform, 77L)).isInstanceOf(BizException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantTechnicalTenantCannotReadCustomerBilling() {
|
||||
var invalidMerchant = new AuthenticatedUser(1L, 0L, 0L, 1L,
|
||||
"merchant", "MERCHANT", "WEB", Set.of());
|
||||
|
||||
assertThatThrownBy(() -> controller.find(invalidMerchant, 77L))
|
||||
.isInstanceOf(BizException.class);
|
||||
verify(service, never()).find(0L, 77L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleCase03() throws Exception {
|
||||
var method = MerchantInboundBillingController.class.getDeclaredMethod(
|
||||
|
||||
+29
@@ -1,9 +1,13 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baiyue.firstleg.entity.CustomerChargeRuleEntity;
|
||||
import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
|
||||
import com.baiyue.firstleg.entity.InboundPricingIdempotencyEntity;
|
||||
import com.baiyue.firstleg.mapper.CarrierFbaRateMapper;
|
||||
import com.baiyue.firstleg.mapper.CustomerChargeRuleMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundFeeItemMapper;
|
||||
@@ -85,6 +89,26 @@ class FbaPricingRepositoryRuleListTest {
|
||||
assertThat(result).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void idempotencyCompletionKeepsTenantAndRecordFilters() {
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), "test"),
|
||||
InboundPricingIdempotencyEntity.class);
|
||||
InboundPricingIdempotencyEntity value = new InboundPricingIdempotencyEntity();
|
||||
value.setTenantId(10001L);
|
||||
value.setId(88L);
|
||||
|
||||
repository.completeIdempotency(value, 501L, java.time.LocalDateTime.now());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<com.baomidou.mybatisplus.core.conditions.Wrapper<InboundPricingIdempotencyEntity>> wrapper =
|
||||
ArgumentCaptor.forClass(com.baomidou.mybatisplus.core.conditions.Wrapper.class);
|
||||
verify(idempotency).update(org.mockito.ArgumentMatchers.isNull(), wrapper.capture());
|
||||
assertThat(wrapper.getValue().getCustomSqlSegment())
|
||||
.contains("tenant_id")
|
||||
.containsPattern("(?i)(^|\\W)id\\s*=");
|
||||
assertThat(params(wrapper.getValue()).values()).contains(10001L, 88L);
|
||||
}
|
||||
|
||||
private Collection<Object> queryValues(Long tenantId, String ruleCode) {
|
||||
when(rules.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of());
|
||||
|
||||
@@ -111,4 +135,9 @@ class FbaPricingRepositoryRuleListTest {
|
||||
return wrapper.getValue().getParamNameValuePairs().values();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static java.util.Map<String, Object> params(Wrapper<?> wrapper) {
|
||||
return ((AbstractWrapper) wrapper).getParamNameValuePairs();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class InboundWorkflowSqlContractTest {
|
||||
|
||||
assertThat(migration)
|
||||
.contains("phase2_fba_add_column('fl_inbound_pallet_allocation', 'warehouse_resource_tenant_id'")
|
||||
.contains("SET warehouse_resource_tenant_id = tenant_id")
|
||||
.contains("SET warehouse_resource_tenant_id = 0")
|
||||
.contains("MODIFY COLUMN warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL");
|
||||
}
|
||||
}
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baiyue.firstleg.entity.FeeItemEntity;
|
||||
import com.baiyue.firstleg.entity.FulfillmentTaskEntity;
|
||||
import com.baiyue.firstleg.entity.InboundFeeDetailEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.entity.InboundWeightResultEntity;
|
||||
import com.baiyue.firstleg.mapper.AssignmentHistoryMapper;
|
||||
import com.baiyue.firstleg.mapper.FeeItemMapper;
|
||||
import com.baiyue.firstleg.mapper.FulfillmentTaskMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundFeeDetailMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundMeasurementItemMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundMeasurementMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundOrderMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundWeightResultMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundWeightRevisionMapper;
|
||||
import com.baiyue.firstleg.mapper.MeasureReviewRecordMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig({FulfillmentTaskRepository.class, InboundMeasurementRepository.class,
|
||||
FeeItemRepository.class})
|
||||
class TenantScopedMutationRepositoryTest {
|
||||
private static final long TENANT = 10001L;
|
||||
private static final long ID = 88L;
|
||||
|
||||
@MockBean private FulfillmentTaskMapper tasks;
|
||||
@MockBean private AssignmentHistoryMapper histories;
|
||||
@MockBean private InboundOrderMapper inboundOrders;
|
||||
@MockBean private InboundMeasurementMapper measurements;
|
||||
@MockBean private InboundMeasurementItemMapper measurementItems;
|
||||
@MockBean private InboundWeightResultMapper weights;
|
||||
@MockBean private InboundWeightRevisionMapper revisions;
|
||||
@MockBean private MeasureReviewRecordMapper reviews;
|
||||
@MockBean private FeeItemMapper feeItems;
|
||||
@MockBean private InboundFeeDetailMapper feeDetails;
|
||||
@Resource private FulfillmentTaskRepository fulfillmentRepository;
|
||||
@Resource private InboundMeasurementRepository measurementRepository;
|
||||
@Resource private FeeItemRepository feeRepository;
|
||||
|
||||
@BeforeAll
|
||||
static void initializeMetadata() {
|
||||
initialize(FulfillmentTaskEntity.class);
|
||||
initialize(InboundWeightResultEntity.class);
|
||||
initialize(InboundFeeDetailEntity.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
void fulfillmentUpdateMatchesTenantAndId() {
|
||||
FulfillmentTaskEntity task = new FulfillmentTaskEntity();
|
||||
task.setTenantId(TENANT);
|
||||
task.setId(ID);
|
||||
when(tasks.update(any(), any(Wrapper.class))).thenReturn(1);
|
||||
|
||||
assertThat(fulfillmentRepository.update(task)).isTrue();
|
||||
|
||||
ArgumentCaptor<Wrapper<FulfillmentTaskEntity>> wrapper = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(tasks).update(eq(task), wrapper.capture());
|
||||
assertTenantAndId(wrapper.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
void measurementUpdateMatchesTenantAndId() {
|
||||
InboundWeightResultEntity weight = new InboundWeightResultEntity();
|
||||
weight.setTenantId(TENANT);
|
||||
weight.setId(ID);
|
||||
when(weights.update(any(), any(Wrapper.class))).thenReturn(1);
|
||||
|
||||
assertThat(measurementRepository.updateWeight(weight)).isTrue();
|
||||
|
||||
ArgumentCaptor<Wrapper<InboundWeightResultEntity>> wrapper = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(weights).update(eq(weight), wrapper.capture());
|
||||
assertTenantAndId(wrapper.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
void feeDetailDeleteMatchesTenantAndId() {
|
||||
when(feeDetails.delete(any(Wrapper.class))).thenReturn(1);
|
||||
|
||||
assertThat(feeRepository.deleteDetail(TENANT, ID)).isEqualTo(1);
|
||||
|
||||
ArgumentCaptor<Wrapper<InboundFeeDetailEntity>> wrapper = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(feeDetails).delete(wrapper.capture());
|
||||
assertTenantAndId(wrapper.getValue());
|
||||
}
|
||||
|
||||
private static void initialize(Class<?> entityType) {
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), "test"), entityType);
|
||||
}
|
||||
|
||||
private static void assertTenantAndId(Wrapper<?> wrapper) {
|
||||
assertThat(wrapper.getCustomSqlSegment())
|
||||
.contains("tenant_id")
|
||||
.containsPattern("(?i)(^|\\W)id\\s*=");
|
||||
assertThat(((AbstractWrapper<?, ?, ?>) wrapper).getParamNameValuePairs().values())
|
||||
.contains(TENANT, ID);
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,18 @@ class FbaPricingServiceTest {
|
||||
@MockBean private FbaPricingRepository repository;
|
||||
@Resource private FbaPricingService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("客户B不能读取客户A的费用快照")
|
||||
void rejectsCrossCustomerFeeLookup() {
|
||||
assertThatThrownBy(() -> service.latest(10002L, INBOUND))
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(error -> ((BizException) error).getResultCode())
|
||||
.isEqualTo(ResultCode.NOT_FOUND);
|
||||
|
||||
verify(repository).findLatestFee(10002L, INBOUND);
|
||||
verify(repository, never()).findLatestFee(TENANT, INBOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("包税KG按配置系数取体积重较大值并按规则向上舍入")
|
||||
void calculatesTaxIncludedKgWithConfiguredFactorAndPrecision() {
|
||||
|
||||
@@ -22,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -35,6 +36,17 @@ class FeeManagementServiceTest {
|
||||
@Resource
|
||||
private InboundFeeDetailService details;
|
||||
|
||||
@Test
|
||||
@DisplayName("客户B不能删除客户A的费用明细")
|
||||
void rejectsCrossCustomerFeeDetailDelete() {
|
||||
assertThatThrownBy(() -> details.delete(10002L, 21L, 31L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("费用明细不存在");
|
||||
|
||||
verify(repository).findDetail(10002L, 21L, 31L);
|
||||
verify(repository, never()).deleteDetail(10002L, 31L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("费用项目列表返回分页结果")
|
||||
void pagesItems() {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class FirstlegScopeGuardTest {
|
||||
@Test
|
||||
void platformRequiresZeroTenantAndPositiveTargetCustomer() {
|
||||
AuthenticatedUser platform = user(0L, "PLATFORM");
|
||||
|
||||
assertThat(FirstlegScopeGuard.platform(platform, 10001L)).isEqualTo(10001L);
|
||||
assertForbidden(() -> FirstlegScopeGuard.platform(user(10001L, "PLATFORM"), 10002L));
|
||||
assertForbidden(() -> FirstlegScopeGuard.platform(user(0L, "MERCHANT"), 10002L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantRequiresPositiveAuthenticatedCustomer() {
|
||||
assertThat(FirstlegScopeGuard.merchant(user(10001L, "MERCHANT"))).isEqualTo(10001L);
|
||||
assertForbidden(() -> FirstlegScopeGuard.merchant(user(0L, "MERCHANT")));
|
||||
assertForbidden(() -> FirstlegScopeGuard.merchant(user(10001L, "PLATFORM")));
|
||||
}
|
||||
|
||||
private static AuthenticatedUser user(long tenantId, String appCode) {
|
||||
return new AuthenticatedUser(9L, tenantId, tenantId, 1L,
|
||||
"operator", appCode, "WEB", Set.of());
|
||||
}
|
||||
|
||||
private static void assertForbidden(org.assertj.core.api.ThrowableAssert.ThrowingCallable call) {
|
||||
assertThatThrownBy(call)
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(error -> ((BizException) error).getResultCode())
|
||||
.isEqualTo(ResultCode.FORBIDDEN);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -115,7 +115,7 @@ class InboundCompletionOrchestratorTest {
|
||||
|
||||
private Fixture fixture(String reviewStatus, boolean feeRelease) {
|
||||
when(idempotency.begin(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(new InboundIdempotencyService.Context(1L, false));
|
||||
.thenReturn(new InboundIdempotencyService.Context(10001L, 1L, false));
|
||||
InboundOrderEntity order = new InboundOrderEntity();
|
||||
order.setProcessStatus("FEE_CONFIRMING");
|
||||
order.setFeeStatus("CONFIRMED");
|
||||
|
||||
+9
-4
@@ -37,7 +37,11 @@ class InboundIdempotencyServiceTest {
|
||||
InboundIdempotencyService.Context context = service.begin(
|
||||
10001L, 9L, 10L, "MEASURE_APPROVE", "key-1", "10|20|3|");
|
||||
|
||||
assertThat(context.replayed()).isTrue();
|
||||
assertThat(context)
|
||||
.extracting(InboundIdempotencyService.Context::tenantId,
|
||||
InboundIdempotencyService.Context::id,
|
||||
InboundIdempotencyService.Context::replayed)
|
||||
.containsExactly(10001L, 1L, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -59,11 +63,12 @@ class InboundIdempotencyServiceTest {
|
||||
@Test
|
||||
@DisplayName("新请求完成时只允许PROCESSING条件更新一次")
|
||||
void shouldCompleteProcessingRecordOnce() {
|
||||
when(repository.completeIdempotency(1L, "{\"inboundId\":\"10\"}")).thenReturn(1);
|
||||
when(repository.completeIdempotency(10001L, 1L, "{\"inboundId\":\"10\"}"))
|
||||
.thenReturn(1);
|
||||
|
||||
service.complete(new InboundIdempotencyService.Context(1L, false), 10L);
|
||||
service.complete(new InboundIdempotencyService.Context(10001L, 1L, false), 10L);
|
||||
|
||||
verify(repository).completeIdempotency(1L, "{\"inboundId\":\"10\"}");
|
||||
verify(repository).completeIdempotency(10001L, 1L, "{\"inboundId\":\"10\"}");
|
||||
}
|
||||
|
||||
private InboundIdempotencyEntity existing(String status) {
|
||||
|
||||
+17
@@ -69,6 +69,23 @@ class InboundMeasurementOpenApiServiceTest {
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Resource private InboundMeasurementOpenApiService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("第三方客户A不能访问只存在于客户B的入库单")
|
||||
void shouldRejectInboundOwnedByAnotherCustomer() {
|
||||
Fixture fixture = fixture();
|
||||
when(fixture.repository.findInbound(10002L, "RK202608010001")).thenReturn(inbound());
|
||||
|
||||
assertThatThrownBy(() -> fixture.service.receive(headers("nonce-cross"), BODY, image()))
|
||||
.isInstanceOf(com.baiyue.common.core.exception.BizException.class)
|
||||
.hasMessageContaining("入仓码不存在或不属于绑定仓库");
|
||||
|
||||
verify(fixture.repository).findInbound(10001L, "RK202608010001");
|
||||
verify(fixture.repository, never()).findInbound(10002L, "RK202608010001");
|
||||
verify(fixture.images, never()).upload(anyString(),
|
||||
org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong(),
|
||||
org.mockito.ArgumentMatchers.anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("回调只写批次明细和幂等结果,不存在入库主单update入口")
|
||||
void shouldPersistMeasurementsWithoutUpdatingInboundOrder() {
|
||||
|
||||
@@ -23,6 +23,7 @@ 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.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -35,6 +36,17 @@ class InboundMeasurementServiceTest {
|
||||
@MockBean private MeasurementBatchService batchService;
|
||||
@Resource private InboundMeasurementService service;
|
||||
|
||||
@Test
|
||||
void customerCannotReadAnotherCustomersMeasurement() {
|
||||
assertThatThrownBy(() -> service.detail(2L, 10L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("入库单不存在");
|
||||
|
||||
verify(repository).findInbound(2L, 10L);
|
||||
verify(repository, never()).findInbound(1L, 10L);
|
||||
verify(repository, never()).listWeights(anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void correctingWeightKeepsOcrOriginalAndWritesImmutableRevision() {
|
||||
InboundOrderEntity inbound = inbound("MEASURING");
|
||||
|
||||
+20
-12
@@ -30,7 +30,7 @@ import static org.mockito.Mockito.when;
|
||||
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
|
||||
class MeasurementLocationOccupationServiceTest {
|
||||
private static final long BUSINESS_TENANT = 10001L;
|
||||
private static final long RESOURCE_TENANT = 90001L;
|
||||
private static final long PLATFORM_RESOURCE_TENANT = 0L;
|
||||
private static final long WAREHOUSE = 200L;
|
||||
private static final long INBOUND = 88L;
|
||||
private static final long OPERATOR = 9L;
|
||||
@@ -43,8 +43,8 @@ class MeasurementLocationOccupationServiceTest {
|
||||
@Resource private MeasurementLocationOccupationService service;
|
||||
|
||||
@Test
|
||||
void sharedWarehouseResourcesUseResourceTenantWhileSnapshotsRemainInBusinessTenant() {
|
||||
properties.setWarehouseResourceTenantId(RESOURCE_TENANT);
|
||||
void platformWarehouseResourcesUseZeroWhileSnapshotsRemainInBusinessTenant() {
|
||||
properties.setWarehouseResourceTenantId(90001L);
|
||||
when(workflow.findOwned(BUSINESS_TENANT, INBOUND)).thenReturn(inbound());
|
||||
when(warehouse.reserve(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
org.mockito.ArgumentMatchers.any())).thenReturn(ApiResponse.success(batch("RESERVED")));
|
||||
@@ -56,27 +56,27 @@ class MeasurementLocationOccupationServiceTest {
|
||||
ArgumentCaptor<WarehousePalletLocationClient.ReserveRequest> reserve =
|
||||
ArgumentCaptor.forClass(WarehousePalletLocationClient.ReserveRequest.class);
|
||||
verify(warehouse).reserve(org.mockito.ArgumentMatchers.eq("internal-token"), reserve.capture());
|
||||
assertThat(reserve.getValue().tenantId()).isEqualTo(RESOURCE_TENANT);
|
||||
assertThat(reserve.getValue().tenantId()).isEqualTo(PLATFORM_RESOURCE_TENANT);
|
||||
assertThat(reserve.getValue().businessId()).isEqualTo(INBOUND);
|
||||
assertThat(reserve.getValue().idempotencyKey()).isEqualTo("measure-88-30-3");
|
||||
|
||||
ArgumentCaptor<WarehousePalletLocationClient.ConfirmRequest> confirm =
|
||||
ArgumentCaptor.forClass(WarehousePalletLocationClient.ConfirmRequest.class);
|
||||
verify(warehouse).confirm(org.mockito.ArgumentMatchers.eq("internal-token"), confirm.capture());
|
||||
assertThat(confirm.getValue().tenantId()).isEqualTo(RESOURCE_TENANT);
|
||||
assertThat(confirm.getValue().tenantId()).isEqualTo(PLATFORM_RESOURCE_TENANT);
|
||||
assertThat(confirm.getValue().businessId()).isEqualTo(INBOUND);
|
||||
|
||||
verify(allocations, times(2)).replace(
|
||||
org.mockito.ArgumentMatchers.eq(BUSINESS_TENANT),
|
||||
org.mockito.ArgumentMatchers.eq(INBOUND),
|
||||
org.mockito.ArgumentMatchers.eq(WAREHOUSE),
|
||||
org.mockito.ArgumentMatchers.eq(RESOURCE_TENANT),
|
||||
org.mockito.ArgumentMatchers.eq(PLATFORM_RESOURCE_TENANT),
|
||||
org.mockito.ArgumentMatchers.eq(new BigDecimal("1.200000")),
|
||||
org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void zeroResourceTenantFallsBackToBusinessTenant() {
|
||||
void defaultResourceTenantRemainsPlatformZero() {
|
||||
properties.setWarehouseResourceTenantId(0L);
|
||||
when(workflow.findOwned(BUSINESS_TENANT, INBOUND)).thenReturn(inbound());
|
||||
when(warehouse.reserve(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
@@ -89,17 +89,25 @@ class MeasurementLocationOccupationServiceTest {
|
||||
ArgumentCaptor<WarehousePalletLocationClient.ReserveRequest> reserve =
|
||||
ArgumentCaptor.forClass(WarehousePalletLocationClient.ReserveRequest.class);
|
||||
verify(warehouse).reserve(org.mockito.ArgumentMatchers.eq("internal-token"), reserve.capture());
|
||||
assertThat(reserve.getValue().tenantId()).isEqualTo(BUSINESS_TENANT);
|
||||
assertThat(reserve.getValue().tenantId()).isEqualTo(PLATFORM_RESOURCE_TENANT);
|
||||
|
||||
ArgumentCaptor<WarehousePalletLocationClient.ConfirmRequest> confirm =
|
||||
ArgumentCaptor.forClass(WarehousePalletLocationClient.ConfirmRequest.class);
|
||||
verify(warehouse).confirm(org.mockito.ArgumentMatchers.eq("internal-token"), confirm.capture());
|
||||
assertThat(confirm.getValue().tenantId()).isEqualTo(BUSINESS_TENANT);
|
||||
assertThat(confirm.getValue().tenantId()).isEqualTo(PLATFORM_RESOURCE_TENANT);
|
||||
|
||||
verify(allocations, times(2)).replace(
|
||||
org.mockito.ArgumentMatchers.eq(BUSINESS_TENANT),
|
||||
org.mockito.ArgumentMatchers.eq(INBOUND),
|
||||
org.mockito.ArgumentMatchers.eq(WAREHOUSE),
|
||||
org.mockito.ArgumentMatchers.eq(PLATFORM_RESOURCE_TENANT),
|
||||
org.mockito.ArgumentMatchers.eq(new BigDecimal("1.200000")),
|
||||
org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmFailureReleasesReservationInResourceTenantAndKeepsOriginalFailure() {
|
||||
properties.setWarehouseResourceTenantId(RESOURCE_TENANT);
|
||||
properties.setWarehouseResourceTenantId(90001L);
|
||||
when(workflow.findOwned(BUSINESS_TENANT, INBOUND)).thenReturn(inbound());
|
||||
when(warehouse.reserve(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
org.mockito.ArgumentMatchers.any())).thenReturn(ApiResponse.success(batch("RESERVED")));
|
||||
@@ -115,9 +123,9 @@ class MeasurementLocationOccupationServiceTest {
|
||||
ArgumentCaptor<WarehousePalletLocationClient.ReleaseRequest> release =
|
||||
ArgumentCaptor.forClass(WarehousePalletLocationClient.ReleaseRequest.class);
|
||||
verify(warehouse).release(org.mockito.ArgumentMatchers.eq("internal-token"), release.capture());
|
||||
assertThat(release.getValue().tenantId()).isEqualTo(RESOURCE_TENANT);
|
||||
assertThat(release.getValue().tenantId()).isEqualTo(PLATFORM_RESOURCE_TENANT);
|
||||
assertThat(release.getValue().businessId()).isEqualTo(INBOUND);
|
||||
verify(allocations).replace(BUSINESS_TENANT, INBOUND, WAREHOUSE, RESOURCE_TENANT,
|
||||
verify(allocations).replace(BUSINESS_TENANT, INBOUND, WAREHOUSE, PLATFORM_RESOURCE_TENANT,
|
||||
new BigDecimal("1.200000"), batch("RESERVED"));
|
||||
}
|
||||
|
||||
|
||||
+19
-2
@@ -40,6 +40,23 @@ class MeasurementReviewServiceTest {
|
||||
@MockBean private InboundIdempotencyService idempotency;
|
||||
@MockBean private InboundProperties properties;
|
||||
@Resource private MeasurementReviewService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("客户B不能审核客户A的测量")
|
||||
void shouldRejectCrossCustomerReview() {
|
||||
when(idempotency.begin(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(new InboundIdempotencyService.Context(10002L, 1L, false));
|
||||
|
||||
assertThatThrownBy(() -> service.approve(10002L, 9L, "审核员", 10L, "cross-customer",
|
||||
new MeasurementReviewRequest(20L, 3, null)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(error -> ((BizException) error).getResultCode())
|
||||
.isEqualTo(ResultCode.NOT_FOUND);
|
||||
|
||||
verify(repository).findOwned(10002L, 10L);
|
||||
verify(repository, never()).transitionReview(anyLong(), anyLong(), anyLong(), anyInt(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("审核通过是测量到计费的唯一正常推进关口")
|
||||
void shouldApproveOnlyPendingSubmittedMeasurement() {
|
||||
@@ -73,7 +90,7 @@ class MeasurementReviewServiceTest {
|
||||
void shouldReplaySameReviewRequest() {
|
||||
Fixture fixture = fixture(false);
|
||||
when(fixture.idempotency.begin(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(new InboundIdempotencyService.Context(1L, true));
|
||||
.thenReturn(new InboundIdempotencyService.Context(10001L, 1L, true));
|
||||
|
||||
fixture.service.approve(10001L, 9L, "审核员", 10L, "approve-1",
|
||||
new MeasurementReviewRequest(20L, 3, null));
|
||||
@@ -147,7 +164,7 @@ class MeasurementReviewServiceTest {
|
||||
|
||||
private Fixture fixture(boolean allowRelease) {
|
||||
when(idempotency.begin(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(new InboundIdempotencyService.Context(1L, false));
|
||||
.thenReturn(new InboundIdempotencyService.Context(10001L, 1L, false));
|
||||
when(properties.isAllowRejectedMeasurement()).thenReturn(allowRelease);
|
||||
InboundOrderEntity order = new InboundOrderEntity();
|
||||
order.setProcessStatus("MEASURE_PENDING");
|
||||
|
||||
Reference in New Issue
Block a user