|
@@ -39,6 +39,7 @@ import static org.mockito.Mockito.mock;
|
|
|
import static org.mockito.Mockito.mockStatic;
|
|
import static org.mockito.Mockito.mockStatic;
|
|
|
import static org.mockito.Mockito.never;
|
|
import static org.mockito.Mockito.never;
|
|
|
import static org.mockito.Mockito.verify;
|
|
import static org.mockito.Mockito.verify;
|
|
|
|
|
+import static org.mockito.Mockito.verifyNoInteractions;
|
|
|
import static org.mockito.Mockito.when;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
|
|
|
|
class MultiStoreCheckoutBatchServiceTest {
|
|
class MultiStoreCheckoutBatchServiceTest {
|
|
@@ -53,6 +54,7 @@ class MultiStoreCheckoutBatchServiceTest {
|
|
|
private final OrderPromotionHelper promotions = mock(OrderPromotionHelper.class);
|
|
private final OrderPromotionHelper promotions = mock(OrderPromotionHelper.class);
|
|
|
private final StringRedisTemplate orderNumbers = mock(StringRedisTemplate.class);
|
|
private final StringRedisTemplate orderNumbers = mock(StringRedisTemplate.class);
|
|
|
private final OrderNumberReservationService numberReservations = mock(OrderNumberReservationService.class);
|
|
private final OrderNumberReservationService numberReservations = mock(OrderNumberReservationService.class);
|
|
|
|
|
+ private final MultiStoreCheckoutActivationTrigger activationTrigger = mock(MultiStoreCheckoutActivationTrigger.class);
|
|
|
|
|
|
|
|
@BeforeEach
|
|
@BeforeEach
|
|
|
void setUp() {
|
|
void setUp() {
|
|
@@ -70,7 +72,7 @@ class MultiStoreCheckoutBatchServiceTest {
|
|
|
|
|
|
|
|
private MultiStoreCheckoutBatchService subject() {
|
|
private MultiStoreCheckoutBatchService subject() {
|
|
|
return new MultiStoreCheckoutBatchService(quotes, config, checkout, parents, orders,
|
|
return new MultiStoreCheckoutBatchService(quotes, config, checkout, parents, orders,
|
|
|
- addresses, stores, promotions, orderNumbers, numberReservations);
|
|
|
|
|
|
|
+ addresses, stores, promotions, orderNumbers, numberReservations, activationTrigger);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
@Test
|
|
@@ -78,15 +80,45 @@ class MultiStoreCheckoutBatchServiceTest {
|
|
|
arrangeQuote();
|
|
arrangeQuote();
|
|
|
|
|
|
|
|
assertThrows(ServiceException.class, () -> subject().create(
|
|
assertThrows(ServiceException.class, () -> subject().create(
|
|
|
- new MultiStoreBatchCreateRequest("retry-1", "quote-1", List.of(1L, 3L)), 42L));
|
|
|
|
|
|
|
+ new MultiStoreBatchCreateRequest("retry-1", "quote-1", List.of(1L, 3L), List.of(
|
|
|
|
|
+ new MultiStoreBatchCreateRequest.PaymentMethod(1L, "3"),
|
|
|
|
|
+ new MultiStoreBatchCreateRequest.PaymentMethod(3L, "1"))), 42L));
|
|
|
|
|
|
|
|
verify(checkout, never()).insertOrGetBatch(any());
|
|
verify(checkout, never()).insertOrGetBatch(any());
|
|
|
verify(parents, never()).insert(any(OrderParent.class));
|
|
verify(parents, never()).insert(any(OrderParent.class));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void rejectsMissingDuplicateAndUnsupportedPerStoreMethodsBeforeWriting() {
|
|
|
|
|
+ List<List<MultiStoreBatchCreateRequest.PaymentMethod>> invalid = List.of(
|
|
|
|
|
+ List.of(new MultiStoreBatchCreateRequest.PaymentMethod(1L, "3")),
|
|
|
|
|
+ List.of(new MultiStoreBatchCreateRequest.PaymentMethod(1L, "3"),
|
|
|
|
|
+ new MultiStoreBatchCreateRequest.PaymentMethod(1L, "1")),
|
|
|
|
|
+ List.of(new MultiStoreBatchCreateRequest.PaymentMethod(1L, "6"),
|
|
|
|
|
+ new MultiStoreBatchCreateRequest.PaymentMethod(2L, "1")),
|
|
|
|
|
+ List.of(new MultiStoreBatchCreateRequest.PaymentMethod(1L, "3"),
|
|
|
|
|
+ new MultiStoreBatchCreateRequest.PaymentMethod(3L, "1")));
|
|
|
|
|
+ for (var methods : invalid) {
|
|
|
|
|
+ assertThrows(ServiceException.class, () -> subject().create(new MultiStoreBatchCreateRequest(
|
|
|
|
|
+ "retry-1", "quote-1", List.of(1L, 2L), methods), 42L));
|
|
|
|
|
+ }
|
|
|
|
|
+ verify(checkout, never()).insertOrGetBatch(any());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void rejectsMethodUnavailableAtSelectedStoreBeforeWriting() {
|
|
|
|
|
+ arrangeQuote();
|
|
|
|
|
+ assertThrows(ServiceException.class, () -> subject().create(new MultiStoreBatchCreateRequest(
|
|
|
|
|
+ "retry-1", "quote-1", List.of(1L, 2L), List.of(
|
|
|
|
|
+ new MultiStoreBatchCreateRequest.PaymentMethod(1L, "2"),
|
|
|
|
|
+ new MultiStoreBatchCreateRequest.PaymentMethod(2L, "1"))), 42L));
|
|
|
|
|
+ verify(checkout, never()).insertOrGetBatch(any());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@org.junit.jupiter.params.ParameterizedTest
|
|
@org.junit.jupiter.params.ParameterizedTest
|
|
|
- @org.junit.jupiter.params.provider.ValueSource(booleans = {false, true})
|
|
|
|
|
- void createsPendingBatchWithServerPricesNoDefaultCodAndFrozenAddress(boolean firstNumberOccupied) {
|
|
|
|
|
|
|
+ @org.junit.jupiter.params.provider.CsvSource({"false,false", "true,false", "false,true"})
|
|
|
|
|
+ void createsBatchWithPerStorePaymentChoicesAndFrozenAddress(boolean firstNumberOccupied,
|
|
|
|
|
+ boolean allCod) throws Exception {
|
|
|
if (firstNumberOccupied) {
|
|
if (firstNumberOccupied) {
|
|
|
when(orderNumbers.execute(org.mockito.ArgumentMatchers.<RedisScript<Long>>any(), anyList(), anyString()))
|
|
when(orderNumbers.execute(org.mockito.ArgumentMatchers.<RedisScript<Long>>any(), anyList(), anyString()))
|
|
|
.thenReturn(1789716730511L, 1789716730512L);
|
|
.thenReturn(1789716730511L, 1789716730512L);
|
|
@@ -123,19 +155,25 @@ class MultiStoreCheckoutBatchServiceTest {
|
|
|
when(parents.insert(any(OrderParent.class))).thenReturn(1);
|
|
when(parents.insert(any(OrderParent.class))).thenReturn(1);
|
|
|
when(checkout.insertMember(any())).thenReturn(1);
|
|
when(checkout.insertMember(any())).thenReturn(1);
|
|
|
when(checkout.insertGroup(any())).thenReturn(1);
|
|
when(checkout.insertGroup(any())).thenReturn(1);
|
|
|
|
|
+ when(checkout.activateIfPending(org.mockito.ArgumentMatchers.eq(99L), any())).thenReturn(1);
|
|
|
doAnswer(call -> { ((OrderCheckoutBatch) call.getArgument(0)).setId(99L); return 1; })
|
|
doAnswer(call -> { ((OrderCheckoutBatch) call.getArgument(0)).setId(99L); return 1; })
|
|
|
.when(checkout).insertOrGetBatch(any());
|
|
.when(checkout).insertOrGetBatch(any());
|
|
|
doAnswer(call -> { ((PosOrder) call.getArgument(0)).setId(100L); return 1; })
|
|
doAnswer(call -> { ((PosOrder) call.getArgument(0)).setId(100L); return 1; })
|
|
|
.when(orders).insert(any(PosOrder.class));
|
|
.when(orders).insert(any(PosOrder.class));
|
|
|
|
|
|
|
|
- var result = subject().create(new MultiStoreBatchCreateRequest(
|
|
|
|
|
- "retry-1", "quote-1", List.of(1L, 2L)), 42L);
|
|
|
|
|
|
|
+ var request = new com.fasterxml.jackson.databind.ObjectMapper().readValue("""
|
|
|
|
|
+ {"requestKey":"retry-1","quoteToken":"quote-1","participatingStoreIds":[1,2],
|
|
|
|
|
+ "paymentMethods":[{"storeId":1,"payType":"%s"},{"storeId":2,"payType":"1"}]}
|
|
|
|
|
+ """.formatted(allCod ? "1" : "3"), MultiStoreBatchCreateRequest.class);
|
|
|
|
|
+ var result = subject().create(request, 42L);
|
|
|
|
|
|
|
|
assertEquals(99L, result.batchId());
|
|
assertEquals(99L, result.batchId());
|
|
|
assertEquals("991789716730512", result.parentOrderNo());
|
|
assertEquals("991789716730512", result.parentOrderNo());
|
|
|
- assertEquals("PENDING", result.checkoutStatus());
|
|
|
|
|
|
|
+ assertEquals(allCod ? "ACTIVE" : "PENDING", result.checkoutStatus());
|
|
|
assertEquals(172, result.totalAmount());
|
|
assertEquals(172, result.totalAmount());
|
|
|
assertEquals(2, result.members().size());
|
|
assertEquals(2, result.members().size());
|
|
|
|
|
+ assertEquals(List.of(allCod ? "1" : "3", "1"), result.members().stream()
|
|
|
|
|
+ .map(com.ruoyi.app.order.dto.MultiStoreBatchCreatedView.Member::payType).toList());
|
|
|
// 真实库pos_order.dd_id上限30,积分流水dd_id上限20;预留3位子单序号。
|
|
// 真实库pos_order.dd_id上限30,积分流水dd_id上限20;预留3位子单序号。
|
|
|
assertTrue(result.parentOrderNo().matches("99[0-9]{13}"));
|
|
assertTrue(result.parentOrderNo().matches("99[0-9]{13}"));
|
|
|
assertTrue(result.parentOrderNo().length() <= 17,
|
|
assertTrue(result.parentOrderNo().length() <= 17,
|
|
@@ -158,12 +196,24 @@ class MultiStoreCheckoutBatchServiceTest {
|
|
|
assertEquals(36.0, order.getFreight());
|
|
assertEquals(36.0, order.getFreight());
|
|
|
assertEquals(new BigDecimal("36"), order.getCustomerDeliveryFee());
|
|
assertEquals(new BigDecimal("36"), order.getCustomerDeliveryFee());
|
|
|
assertEquals(BigDecimal.ZERO, order.getMerchantDeliverySubsidy());
|
|
assertEquals(BigDecimal.ZERO, order.getMerchantDeliverySubsidy());
|
|
|
- assertEquals(0L, order.getPayStatus());
|
|
|
|
|
- assertEquals(null, order.getPayType());
|
|
|
|
|
|
|
+ assertEquals(allCod ? 1L : sequence == 2 ? 0L : 1L, order.getPayStatus());
|
|
|
|
|
+ assertEquals(allCod ? "1" : sequence == 2 ? "3" : "1", order.getPayType());
|
|
|
assertEquals(Boolean.FALSE, order.getIsDisplay());
|
|
assertEquals(Boolean.FALSE, order.getIsDisplay());
|
|
|
assertEquals(null, order.getEffectiveAt());
|
|
assertEquals(null, order.getEffectiveAt());
|
|
|
assertTrue(order.getShAddress().contains("22.6000"));
|
|
assertTrue(order.getShAddress().contains("22.6000"));
|
|
|
}
|
|
}
|
|
|
|
|
+ var members = org.mockito.ArgumentCaptor.forClass(com.ruoyi.system.domain.OrderCheckoutMember.class);
|
|
|
|
|
+ verify(checkout, org.mockito.Mockito.times(2)).insertMember(members.capture());
|
|
|
|
|
+ assertEquals(List.of(allCod ? "COD_CONFIRMED" : "ONLINE_PENDING", "COD_CONFIRMED"), members.getAllValues().stream()
|
|
|
|
|
+ .map(com.ruoyi.system.domain.OrderCheckoutMember::getPaymentState).toList());
|
|
|
|
|
+ if (allCod) {
|
|
|
|
|
+ verify(checkout).activateIfPending(org.mockito.ArgumentMatchers.eq(99L), any());
|
|
|
|
|
+ verify(checkout).insertActivationEvent(org.mockito.ArgumentMatchers.eq(99L), any());
|
|
|
|
|
+ verify(activationTrigger).scheduleAfterCommit(99L);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ verify(checkout, never()).activateIfPending(org.mockito.ArgumentMatchers.eq(99L), any());
|
|
|
|
|
+ verifyNoInteractions(activationTrigger);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
@Test
|
|
@@ -171,8 +221,8 @@ class MultiStoreCheckoutBatchServiceTest {
|
|
|
OrderCheckoutBatch existing = new OrderCheckoutBatch();
|
|
OrderCheckoutBatch existing = new OrderCheckoutBatch();
|
|
|
existing.setId(99L); existing.setParentDdId("MS-EXISTING");
|
|
existing.setId(99L); existing.setParentDdId("MS-EXISTING");
|
|
|
existing.setCheckoutStatus("PENDING"); existing.setTotalAmount(172);
|
|
existing.setCheckoutStatus("PENDING"); existing.setTotalAmount(172);
|
|
|
- // 使用同样的请求 token/参与集合产生稳定的幂等摘要。
|
|
|
|
|
- String payload = "quote-1|[1,2]";
|
|
|
|
|
|
|
+ // 使用相同的报价、参与集合和逐店支付方式产生稳定的幂等摘要。
|
|
|
|
|
+ String payload = "quote-1|[1,2]|[\"3\",\"1\"]";
|
|
|
try {
|
|
try {
|
|
|
byte[] digest = java.security.MessageDigest.getInstance("SHA-256")
|
|
byte[] digest = java.security.MessageDigest.getInstance("SHA-256")
|
|
|
.digest(payload.getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
|
.digest(payload.getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
|
@@ -184,9 +234,15 @@ class MultiStoreCheckoutBatchServiceTest {
|
|
|
when(checkout.selectMembersByBatchId(99L)).thenReturn(List.of());
|
|
when(checkout.selectMembersByBatchId(99L)).thenReturn(List.of());
|
|
|
|
|
|
|
|
var result = subject().create(new MultiStoreBatchCreateRequest(
|
|
var result = subject().create(new MultiStoreBatchCreateRequest(
|
|
|
- "retry-1", "quote-1", List.of(1L, 2L)), 42L);
|
|
|
|
|
|
|
+ "retry-1", "quote-1", List.of(1L, 2L), List.of(
|
|
|
|
|
+ new MultiStoreBatchCreateRequest.PaymentMethod(1L, "3"),
|
|
|
|
|
+ new MultiStoreBatchCreateRequest.PaymentMethod(2L, "1"))), 42L);
|
|
|
|
|
|
|
|
assertEquals("MS-EXISTING", result.parentOrderNo());
|
|
assertEquals("MS-EXISTING", result.parentOrderNo());
|
|
|
|
|
+ assertThrows(ServiceException.class, () -> subject().create(new MultiStoreBatchCreateRequest(
|
|
|
|
|
+ "retry-1", "quote-1", List.of(1L, 2L), List.of(
|
|
|
|
|
+ new MultiStoreBatchCreateRequest.PaymentMethod(1L, "2"),
|
|
|
|
|
+ new MultiStoreBatchCreateRequest.PaymentMethod(2L, "1"))), 42L));
|
|
|
org.mockito.Mockito.verifyNoInteractions(orderNumbers);
|
|
org.mockito.Mockito.verifyNoInteractions(orderNumbers);
|
|
|
verify(quotes, never()).loadSnapshot(any(), any());
|
|
verify(quotes, never()).loadSnapshot(any(), any());
|
|
|
verify(parents, never()).insert(any(OrderParent.class));
|
|
verify(parents, never()).insert(any(OrderParent.class));
|