package com.baiyue.lastleg.delivery; import com.baiyue.lastleg.delivery.model.SourceDeliveryCompletionPolicy; import com.baiyue.lastleg.delivery.model.SourceDeliveryCompletionPolicy.*; import org.junit.jupiter.api.Test; import java.time.LocalDateTime; import java.util.List; import static org.assertj.core.api.Assertions.*; class SourceDeliveryCompletionPolicyTest { private final LocalDateTime departure = LocalDateTime.of(2026, 10, 3, 1, 0); private final LocalDateTime signature = departure.plusHours(2); private final List picks = List.of(new Pick(1,10,"CODE:A",101,4,1L,null,null,null), new Pick(2,10,"CODE:A",102,6,2L,null,null,null)); private Delivery delivery(long signed) { return new Delivery(20,10,"CODE:A",10,signed,signature,departure); } private Result evaluate(long order, long original, List deliveries, List facts, List returns) { return SourceDeliveryCompletionPolicy.evaluate(new Source("SOURCE-" + order, order, original), deliveries, facts, returns, true); } @Test void onlyReturnedSourceWaitsWhileOtherSourceIsProvenFullySigned() { var returns = List.of(new Returned(30,20,101,2,departure.plusHours(1))); assertThat(evaluate(101,4,List.of(delivery(8)),picks,returns)).satisfies(r -> { assertThat(r.provenSignedPackages()).isEqualTo(2); assertThat(r.fullySigned()).isFalse(); assertThat(r.unresolvedReasons()).isEmpty(); }); assertThat(evaluate(102,6,List.of(delivery(8)),picks,returns).fullySigned()).isTrue(); } @Test void postSignatureReturnDoesNotEraseHistoricSignature() { assertThat(evaluate(101,4,List.of(delivery(10)),picks,List.of(new Returned(30,20,101,2,signature.plusHours(1)))).fullySigned()).isTrue(); } @Test void laterRedeliveryOfPreviouslyUnsignedBoxesCompletesSource() { var deliveries = List.of(delivery(8), new Delivery(21,11,"CODE:A",2,2L,signature.plusDays(1),signature.plusHours(3))); var allPicks = List.of(picks.get(0),picks.get(1),new Pick(3,11,"CODE:A",101,2,null,300L,30L,2L)); assertThat(evaluate(101,4,deliveries,allPicks,List.of(new Returned(30,20,101,2,departure.plusHours(1)))).fullySigned()).isTrue(); } @Test void totalSignatureCannotBeProportionallyAssignedWhenReturnSourceIsMissing() { var r = evaluate(102,6,List.of(delivery(8)),picks,List.of()); assertThat(r.fullySigned()).isFalse(); assertThat(r.unresolvedReasons()).contains("PRE_SIGNATURE_RETURN_UNRESOLVED:20"); } @Test void equalTimestampRequiresReconciliation() { assertThat(evaluate(102,6,List.of(delivery(8)),picks,List.of(new Returned(30,20,101,2,signature))).unresolvedReasons()).isNotEmpty(); } @Test void duplicateLedgerAndIncompleteHistoryNeverProveFullSignature() { var returned = new Returned(30,20,101,1,departure.plusHours(1)); assertThat(evaluate(102,6,List.of(delivery(8)),picks,List.of(returned,returned)).fullySigned()).isFalse(); assertThat(SourceDeliveryCompletionPolicy.evaluate(new Source("S",102,6),List.of(delivery(10)),picks,List.of(),false).fullySigned()).isFalse(); } @Test void ambiguousDestinationOrMissingPickCoverageBlocksCompletion() { assertThat(evaluate(102,6,List.of(delivery(10),new Delivery(21,10,"CODE:A",10,10L,signature,departure)),picks,List.of()).fullySigned()).isFalse(); assertThat(evaluate(102,6,List.of(delivery(10)),List.of(picks.get(1)),List.of()).fullySigned()).isFalse(); } @Test void postSignatureRedeliveryCannotDoubleCountOriginalBoxes() { assertThat(evaluate(101,4,List.of(delivery(10),new Delivery(21,11,"CODE:A",2,2L,signature.plusDays(1),signature.plusHours(3))), List.of(picks.get(0),picks.get(1),new Pick(3,11,"CODE:A",101,2,null,300L,30L,2L)),List.of()).unresolvedReasons()).contains("RETURN_PICK_ORIGIN_UNRESOLVED:3"); } @Test void postSignatureReturnRedeliveryCannotFillTheNeverShippedOriginalRemainder() { var deliveries=List.of(new Delivery(20,10,"CODE:A",6,6L,signature,departure), new Delivery(21,11,"CODE:A",4,4L,signature.plusDays(1),signature.plusHours(3))); var facts=List.of(new Pick(1,10,"CODE:A",101,6,1L,null,null,null), new Pick(2,11,"CODE:A",101,4,null,300L,30L,4L)); var result=evaluate(101,10,deliveries,facts,List.of(new Returned(30,20,101,4,signature.plusHours(1)))); assertThat(result.provenSignedPackages()).isEqualTo(6); assertThat(result.fullySigned()).isFalse(); assertThat(result.unresolvedReasons()).isEmpty(); } @Test void missingReceiptOriginCannotSilentlyBecomeFreshCargo() { var facts=List.of(new Pick(1,10,"CODE:A",101,4,null,null,null,null),picks.get(1)); assertThat(evaluate(101,4,List.of(delivery(10)),facts,List.of()).unresolvedReasons()).contains("PICK_RECEIPT_ORIGIN_UNRESOLVED"); } @Test void unrelatedDestinationMissingEvidenceDoesNotBlockProvenSource() { var result=evaluate(101,4,List.of(new Delivery(20,10,"CODE:A",4,4L,signature,departure), new Delivery(21,10,"CODE:B",6,null,null,departure)),List.of(picks.get(0)),List.of()); assertThat(result.fullySigned()).isTrue();assertThat(result.unresolvedReasons()).isEmpty(); } @Test void completeTwoHopPreSignatureReturnsContributeOnlyNewlySignedBoxes() { var ds=List.of(new Delivery(20,10,"CODE:A",4,2L,departure.plusHours(2),departure), new Delivery(21,11,"CODE:A",2,1L,departure.plusHours(5),departure.plusHours(3)), new Delivery(22,12,"CODE:A",1,1L,departure.plusHours(8),departure.plusHours(6))); var ps=List.of(picks.get(0),new Pick(3,11,"CODE:A",101,2,null,300L,30L,2L), new Pick(4,12,"CODE:A",101,1,null,400L,40L,1L)); var rs=List.of(new Returned(30,20,101,2,departure.plusHours(1)),new Returned(40,21,101,1,departure.plusHours(4))); var result=evaluate(101,4,ds,ps,rs); assertThat(result.fullySigned()).isTrue();assertThat(result.provenSignedPackages()).isEqualTo(4); } @Test void repeatedReturnOfAlreadySignedCohortRemainsNonContributing() { var ds=List.of(new Delivery(20,10,"CODE:A",6,6L,departure.plusHours(2),departure), new Delivery(21,11,"CODE:A",4,2L,departure.plusHours(6),departure.plusHours(4)), new Delivery(22,12,"CODE:A",2,2L,departure.plusHours(9),departure.plusHours(7))); var ps=List.of(new Pick(1,10,"CODE:A",101,6,1L,null,null,null),new Pick(2,11,"CODE:A",101,4,null,300L,30L,4L), new Pick(3,12,"CODE:A",101,2,null,400L,40L,2L)); var rs=List.of(new Returned(30,20,101,4,departure.plusHours(3)),new Returned(40,21,101,2,departure.plusHours(5))); var result=evaluate(101,10,ds,ps,rs); assertThat(result.fullySigned()).isFalse();assertThat(result.provenSignedPackages()).isEqualTo(6); assertThat(result.unresolvedReasons()).isEmpty(); } }