Merge branch 'test' of http://git.buggoglobal.com/java-project/baiyue-firstleg into dev
This commit is contained in:
@@ -120,6 +120,10 @@ class PriceBookContractTest {
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
assertThat(firstlegOpenapi)
|
||||
.contains("/internal/firstleg/v1/pricing/channels/validate:")
|
||||
.contains("/internal/firstleg/v1/pricing/order-channel-changes/validate:")
|
||||
.contains("/internal/firstleg/v1/pricing/order-channel-changes/apply:")
|
||||
.contains("tenantId + orderId + orderVersion 构成幂等边界")
|
||||
.contains("实际净重、实际体积和测量审核事实保持不变")
|
||||
.contains("required: [tenantId, originWarehouseId, channelCode, destinationType, requireInboundCodePrefix]")
|
||||
.contains("LEGACY_MANUAL 模式仅校验渠道可用性");
|
||||
}
|
||||
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.OrderChannelChangePricing.Request;
|
||||
import com.baiyue.firstleg.entity.InboundFeeEntity;
|
||||
import com.baiyue.firstleg.entity.InboundPricingEntity;
|
||||
import com.baiyue.firstleg.entity.InboundPricingIdempotencyEntity;
|
||||
import com.baiyue.firstleg.pricing.ActivePriceBookEstimator;
|
||||
import com.baiyue.firstleg.repository.FbaPricingRepository;
|
||||
import com.baiyue.firstleg.repository.FeeItemRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
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.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(OrderChannelChangePricingService.class)
|
||||
class OrderChannelChangePricingServiceTest {
|
||||
@MockBean private FbaPricingRepository repository;
|
||||
@MockBean private FeeItemRepository feeItems;
|
||||
@MockBean private MerchantPriceBookService priceBooks;
|
||||
@MockBean private ActivePriceBookEstimator estimator;
|
||||
@Resource private OrderChannelChangePricingService service;
|
||||
|
||||
@Test
|
||||
void rejectsConfirmedFeeBeforeChannelCanChange() {
|
||||
InboundPricingEntity inbound = inbound("CONFIRMED");
|
||||
InboundFeeEntity fee = fee(91L, "CONFIRMED");
|
||||
when(repository.findInboundByOrder(10001L, 77L)).thenReturn(inbound);
|
||||
when(repository.findLatestFee(10001L, 88L)).thenReturn(fee);
|
||||
|
||||
assertThatThrownBy(() -> service.validate(request(4)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("费用已确认或已发布");
|
||||
verify(estimator, never()).estimate(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validatesExactPriceAndMarksCalculatedEstimateForInvalidation() {
|
||||
InboundPricingEntity inbound = inbound("CALCULATED");
|
||||
when(repository.findInboundByOrder(10001L, 77L)).thenReturn(inbound);
|
||||
when(repository.findLatestFee(10001L, 88L)).thenReturn(fee(91L, "CALCULATED"));
|
||||
when(estimator.estimate(any())).thenReturn(estimate());
|
||||
|
||||
var response = service.validate(request(4));
|
||||
|
||||
assertThat(response.priceBookVersionId()).isEqualTo(12L);
|
||||
assertThat(response.calculatedFeeWillBeInvalidated()).isTrue();
|
||||
assertThat(response.feeStatus()).isEqualTo("CALCULATED");
|
||||
verify(priceBooks).requireAvailableChannel(10001L, 9L, "MATSON_NEW", "FBA", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void appliesByOrderVersionAndOnlyInvalidatesCalculatedFacts() {
|
||||
InboundPricingEntity inbound = inbound("CALCULATED");
|
||||
InboundFeeEntity fee = fee(91L, "CALCULATED");
|
||||
when(repository.findInboundByOrderForUpdate(10001L, 77L)).thenReturn(inbound);
|
||||
when(repository.findLatestFeeForUpdate(10001L, 88L)).thenReturn(fee);
|
||||
when(estimator.estimate(any())).thenReturn(estimate());
|
||||
when(repository.supersedeCurrent(10001L, 91L, "CALCULATED")).thenReturn(1);
|
||||
when(repository.applyOrderChannelChange(inbound, "MATSON_NEW", "新渠道")).thenReturn(1);
|
||||
when(repository.completeIdempotency(any(), org.mockito.ArgumentMatchers.eq(4L), any())).thenReturn(1);
|
||||
|
||||
var response = service.apply(request(4));
|
||||
|
||||
assertThat(response.feeStatus()).isEqualTo("UNPRICED");
|
||||
assertThat(response.invalidatedFeeId()).isEqualTo(91L);
|
||||
assertThat(inbound.getActualNetWeightKg()).isEqualByComparingTo("37");
|
||||
assertThat(inbound.getActualVolumeCbm()).isEqualByComparingTo("0.102");
|
||||
verify(feeItems).supersedeFreight(
|
||||
org.mockito.ArgumentMatchers.eq(10001L), org.mockito.ArgumentMatchers.eq(88L),
|
||||
org.mockito.ArgumentMatchers.eq(0L), org.mockito.ArgumentMatchers.eq(0L), any());
|
||||
verify(repository).insertIdempotency(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameOrderVersionReplaysWithoutInvalidatingAgain() {
|
||||
InboundPricingEntity inbound = inbound("UNPRICED");
|
||||
InboundPricingIdempotencyEntity token = token(4L, "COMPLETED");
|
||||
token.setRequestHash(requestHash(request(4)));
|
||||
when(repository.findInboundByOrderForUpdate(10001L, 77L)).thenReturn(inbound);
|
||||
when(repository.findIdempotency(10001L, 0L, "ORDER_CHANNEL_CHANGE", "77:4")).thenReturn(token);
|
||||
when(estimator.estimate(any())).thenReturn(estimate());
|
||||
|
||||
assertThat(service.apply(request(4)).orderVersion()).isEqualTo(4L);
|
||||
|
||||
verify(repository, never()).supersedeCurrent(any(Long.class), any(Long.class), any());
|
||||
verify(repository, never()).applyOrderChannelChange(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsOlderOrderVersionAfterNewerVersionWasApplied() {
|
||||
InboundPricingEntity inbound = inbound("UNPRICED");
|
||||
when(repository.findInboundByOrderForUpdate(10001L, 77L)).thenReturn(inbound);
|
||||
when(repository.findLatestCompletedOrderChannelChange(10001L, 88L))
|
||||
.thenReturn(token(5L, "COMPLETED"));
|
||||
|
||||
assertThatThrownBy(() -> service.apply(request(4)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("订单渠道版本已推进");
|
||||
verify(estimator, never()).estimate(any());
|
||||
}
|
||||
|
||||
private static Request request(long orderVersion) {
|
||||
return new Request(10001L, 77L, orderVersion, 9L, "MATSON_NEW", "新渠道",
|
||||
"FBA", 501L, "ONT8", "TAX_INCLUDED");
|
||||
}
|
||||
|
||||
private static InboundPricingEntity inbound(String feeStatus) {
|
||||
InboundPricingEntity value = new InboundPricingEntity();
|
||||
value.setId(88L);
|
||||
value.setTenantId(10001L);
|
||||
value.setOrderId(77L);
|
||||
value.setWarehouseId(9L);
|
||||
value.setDestinationType("FBA");
|
||||
value.setDestinationWarehouseId(501L);
|
||||
value.setDestinationCode("ONT8");
|
||||
value.setDestinationName("ONT8");
|
||||
value.setPricingTaxMode("TAX_INCLUDED");
|
||||
value.setProcessStatus("WAIT_SPLIT");
|
||||
value.setFeeStatus(feeStatus);
|
||||
value.setActualNetWeightKg(new BigDecimal("37"));
|
||||
value.setActualVolumeCbm(new BigDecimal("0.102"));
|
||||
return value;
|
||||
}
|
||||
|
||||
private static InboundFeeEntity fee(long id, String status) {
|
||||
InboundFeeEntity value = new InboundFeeEntity();
|
||||
value.setId(id);
|
||||
value.setInboundId(88L);
|
||||
value.setFeeStatus(status);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static ActivePriceBookEstimator.Estimate estimate() {
|
||||
return new ActivePriceBookEstimator.Estimate(12L, "MATSON_NEW", null, null, null,
|
||||
BigDecimal.ONE, "KG", BigDecimal.ONE, BigDecimal.ONE, "CNY", BigDecimal.ONE, 6, "HALF_UP");
|
||||
}
|
||||
|
||||
private static InboundPricingIdempotencyEntity token(long version, String status) {
|
||||
InboundPricingIdempotencyEntity value = new InboundPricingIdempotencyEntity();
|
||||
value.setResultResourceId(version);
|
||||
value.setStatus(status);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String requestHash(Request request) {
|
||||
try {
|
||||
var method = OrderChannelChangePricingService.class.getDeclaredMethod("hash", Request.class);
|
||||
method.setAccessible(true);
|
||||
return (String) method.invoke(null, request);
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
throw new AssertionError(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user