From 369613a2e7f91bfda316c266c26a05b8bda70841 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sat, 10 Oct 2026 14:01:50 +0800 Subject: [PATCH] test(procurement): verify pending commands and late successful receipts --- .../ProcurementFinancePersistenceTest.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/service/ProcurementFinancePersistenceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/service/ProcurementFinancePersistenceTest.java index f2e69b355..1c1b269cc 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/service/ProcurementFinancePersistenceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/service/ProcurementFinancePersistenceTest.java @@ -120,6 +120,67 @@ class ProcurementFinancePersistenceTest { assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM ll_procurement_wallet_attempt WHERE fee_id=?",Integer.class,fee.getId())).isEqualTo(2); } + @Test void pendingOriginalRejectsReplacementAndLateSuccessKeepsOriginalAmountAndLedger() { + var original=confirm(ProcurementFeePurpose.DUTY,"POSITIVE","10"); + var originalFreeze=result(original,"FREEZE","SUCCEEDED","FROZEN","10"); + assertPendingVersionCannotBeReplaced(original); + // Replaying a timed-out confirmation returns the original command, never a new attempt. + assertThat(confirm(ProcurementFeePurpose.DUTY,"POSITIVE","10").getWalletCommandEventId()) + .isEqualTo(originalFreeze.commandEventId()); + handle(originalFreeze); + var settling=fees.selectById(original.getId()); + assertPendingVersionCannotBeReplaced(settling); + assertThat(confirm(ProcurementFeePurpose.DUTY,"POSITIVE","10").getWalletCommandEventId()) + .isEqualTo(settling.getWalletCommandEventId()); + var lateSuccess=result(settling,"SETTLE","SUCCEEDED","SETTLED","10"); + handle(lateSuccess); + // A delayed freeze receipt after settlement must not regress the settled projection. + handle(originalFreeze); + handle(lateSuccess); + var saved=fees.selectById(original.getId()); + assertThat(saved.getFeeVersion()).isEqualTo(1); + assertThat(saved.getOriginalAmount()).isEqualByComparingTo("10"); + assertThat(saved.getAmount()).isEqualByComparingTo("10"); + assertThat(saved.getWalletStatus()).isEqualTo("SETTLED"); + assertThat(saved.getSettledCommandEventId()).isEqualTo(lateSuccess.commandEventId()); + assertThat(saved.getWalletLedgerId()).isEqualTo(lateSuccess.walletLedgerId()); + assertThat(saved.getSourceFeeId()).isEqualTo(901L); + assertThat(service.all(11)).hasSize(1); + assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM ll_procurement_wallet_attempt WHERE fee_id=? AND status='SUCCEEDED'",Integer.class,original.getId())).isEqualTo(2); + assertThat(jdbc.queryForObject("SELECT fee_id FROM ll_procurement_wallet_attempt WHERE command_event_id=?",Long.class,lateSuccess.commandEventId())).isEqualTo(original.getId()); + verify(commands,times(2)).publish(anyString(),anyLong(),anyString(),anyString(),any(),anyString(),anyLong()); + verify(commands,never()).publishRefund(anyLong(),anyString(),anyString(),any(),anyString(),anyString(),anyLong()); + verify(sources).publishSettled(anyString(),anyString(),argThat(r -> r.amount().compareTo(new BigDecimal("10"))==0),any(), + argThat(ref -> ref.commandEventId().equals(lateSuccess.commandEventId()))); + } + + private void assertPendingVersionCannotBeReplaced(OverseasProcurementFee original) { + assertThatThrownBy(() -> confirm(ProcurementFeePurpose.DUTY,"POSITIVE","20")).hasMessageContaining("已确认"); + assertThatThrownBy(() -> tx.execute(s -> service.replaceFailed("9","PO-1","7",original.getId(),new BigDecimal("20"),1,"corrected target"))) + .hasMessageContaining("须先终止"); + assertThatThrownBy(() -> tx.execute(s -> service.retry("9","PO-1",original.getId()))).hasMessageContaining("未明确失败"); + assertThatThrownBy(() -> tx.executeWithoutResult(s -> service.terminateFailed("9","PO-1",original.getId()))).hasMessageContaining("明确失败"); + assertThatThrownBy(() -> tx.execute(s -> service.refund("9","PO-1","7",original.getId(),BigDecimal.ONE,1,"corrected target","pending-refund"))) + .hasMessageContaining("已成功实扣"); + assertThat(fees.selectById(original.getId()).getWalletCommandEventId()).isEqualTo(original.getWalletCommandEventId()); + } + + @Test void pendingEstimateCannotBeReleasedOrReplacedBeforeLateFreezeResult() { + var estimate=confirm(ProcurementFeePurpose.INTERNATIONAL_ESTIMATE,"POSITIVE","10"); + assertThatThrownBy(() -> tx.executeWithoutResult(s -> service.releaseEstimate("9","PO-1",estimate.getId()))) + .hasMessageContaining("未决"); + assertThatThrownBy(() -> confirm(ProcurementFeePurpose.INTERNATIONAL_ESTIMATE,"POSITIVE","20")).hasMessageContaining("已确认"); + assertThat(confirm(ProcurementFeePurpose.INTERNATIONAL_ESTIMATE,"POSITIVE","10").getId()).isEqualTo(estimate.getId()); + var lateFreeze=result(estimate,"FREEZE","SUCCEEDED","FROZEN","10"); + handle(lateFreeze);handle(lateFreeze); + assertThat(fees.selectById(estimate.getId()).getWalletStatus()).isEqualTo("FROZEN"); + assertThat(fees.selectById(estimate.getId()).getAmount()).isEqualByComparingTo("10"); + assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM ll_procurement_wallet_attempt",Integer.class)).isEqualTo(1); + verify(commands).publish(eq("FREEZE"),anyLong(),anyString(),eq(estimate.getFeeNo()),eq(new BigDecimal("10")),anyString(),anyLong()); + verifyNoMoreInteractions(commands); + verifyNoInteractions(sources); + } + @Test void sourceFailureRollsBackResultProjectionAndRetriesWithoutAnotherDebit() { var fee=confirm(ProcurementFeePurpose.DUTY,"POSITIVE","10"); handle(result(fee,"FREEZE","SUCCEEDED","FROZEN","10"));