2026-10-10 10:38:06 +08:00
|
|
|
package com.baiyue.lastleg.appointmentplanning;
|
|
|
|
|
|
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
|
import org.springframework.web.server.ResponseStatusException;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
|
import static org.mockito.Mockito.*;
|
|
|
|
|
|
|
|
|
|
class AppointmentBatchPreviewServiceTest {
|
2026-10-15 16:55:57 +08:00
|
|
|
private static AppointmentCargoVerificationService verification() {
|
|
|
|
|
var service=mock(AppointmentCargoVerificationService.class);
|
|
|
|
|
when(service.inspect(any())).thenAnswer(call->{AppointmentSourceService.Source source=call.getArgument(0);return new AppointmentCargoVerificationService.Context(source,null,0,0,source.packages(),source.packages()==null?null:source.packages().longValue(),false,List.of("VERIFY_CARGO"),List.of());});
|
|
|
|
|
return service;
|
|
|
|
|
}
|
|
|
|
|
@Test void previewUsesVerifiedRemainingQuantityAndDoesNotRejectAnEntirePartiallyAllocatedSource() {
|
|
|
|
|
var sources=mock(AppointmentSourceService.class);var mapper=mock(AppointmentBatchPreviewMapper.class);var verification=verification();
|
|
|
|
|
var refs=List.of(new AppointmentCoverageService.SourceRef("A","ORDER:1","FBA:ONT8"));var source=source("ORDER:1","A",1L,"100","FBA:ONT8");
|
|
|
|
|
when(sources.resolveBatch(refs)).thenReturn(List.of(source));when(mapper.flags(List.of(1L))).thenReturn(List.of(new AppointmentBatchPreviewMapper.Flags(1L,false,false,true)));
|
|
|
|
|
when(verification.inspect(source)).thenReturn(new AppointmentCargoVerificationService.Context(source,null,6,0,10,4L,false,List.of("VERIFY_CARGO"),List.of()));
|
|
|
|
|
var service=new AppointmentBatchPreviewService(sources,mapper,verification);
|
|
|
|
|
assertEquals(4,service.preview(refs).group().packages());
|
|
|
|
|
when(verification.inspect(source)).thenReturn(new AppointmentCargoVerificationService.Context(source,null,10,0,10,0L,false,List.of("VERIFY_CARGO"),List.of()));
|
|
|
|
|
assertThrows(ResponseStatusException.class,()->service.preview(refs));
|
|
|
|
|
}
|
2026-10-10 10:38:06 +08:00
|
|
|
private static AppointmentSourceService.Source source(String key,String customer,Long orderId,String origin,String destination) {
|
|
|
|
|
return new AppointmentSourceService.Source(key,customer,orderId,null,null,null,origin,"美国仓",destination,destination.substring(4),10,"1",true,List.of());
|
|
|
|
|
}
|
|
|
|
|
@Test void firstLegAndUnmaterializedExternalCargoCanBeCheckedTogetherAcrossCustomers() {
|
|
|
|
|
var sources=mock(AppointmentSourceService.class);var mapper=mock(AppointmentBatchPreviewMapper.class);
|
|
|
|
|
var refs=List.of(new AppointmentCoverageService.SourceRef("A","ORDER:1","FBA:ONT8"),new AppointmentCoverageService.SourceRef("B","BOOKING:B-1","FBA:ONT8"));
|
|
|
|
|
when(sources.resolveBatch(refs)).thenReturn(List.of(source("ORDER:1","A",1L,"100","FBA:ONT8"),source("BOOKING:B-1","B",null,"100","FBA:ONT8")));
|
|
|
|
|
when(mapper.flags(List.of(1L))).thenReturn(List.of(new AppointmentBatchPreviewMapper.Flags(1L,false,false,false)));
|
2026-10-10 18:37:38 +08:00
|
|
|
when(sources.destinationTimezone("ONT8")).thenReturn("America/Los_Angeles");
|
2026-10-15 16:55:57 +08:00
|
|
|
var preview=new AppointmentBatchPreviewService(sources,mapper,verification()).preview(refs);
|
2026-10-10 18:37:38 +08:00
|
|
|
assertEquals("America/Los_Angeles",preview.destinationTimezone());
|
2026-10-10 10:38:06 +08:00
|
|
|
assertEquals(2,preview.group().customerCount());assertEquals(20,preview.group().packages());
|
|
|
|
|
assertTrue(preview.group().readinessIncomplete());assertNull(preview.group().readyFrom());
|
|
|
|
|
assertEquals(List.of("READY_WINDOW_UNVERIFIED"),preview.missingFields());
|
|
|
|
|
assertNull(preview.sources().get(1).orderId());
|
2026-10-10 18:37:38 +08:00
|
|
|
verify(sources).resolveBatch(refs);verify(mapper).flags(List.of(1L));verify(sources).destinationTimezone("ONT8");verifyNoMoreInteractions(sources,mapper);
|
2026-10-10 10:38:06 +08:00
|
|
|
}
|
|
|
|
|
@Test void missingRowsDetentionMixedDestinationsAndPriorAppointmentsBlockGrouping() {
|
|
|
|
|
var sources=mock(AppointmentSourceService.class);var mapper=mock(AppointmentBatchPreviewMapper.class);
|
|
|
|
|
var refs=List.of(new AppointmentCoverageService.SourceRef("A","ORDER:1","FBA:ONT8"));
|
|
|
|
|
when(sources.resolveBatch(refs)).thenReturn(List.of(source("ORDER:1","A",1L,"100","FBA:ONT8")));
|
2026-10-15 16:55:57 +08:00
|
|
|
var service=new AppointmentBatchPreviewService(sources,mapper,verification());
|
2026-10-10 10:38:06 +08:00
|
|
|
for(var flags:List.of(List.<AppointmentBatchPreviewMapper.Flags>of(),List.of(new AppointmentBatchPreviewMapper.Flags(1L,true,false,false)),
|
2026-10-15 16:55:57 +08:00
|
|
|
List.of(new AppointmentBatchPreviewMapper.Flags(1L,false,true,false)))) {
|
2026-10-10 10:38:06 +08:00
|
|
|
when(mapper.flags(List.of(1L))).thenReturn(flags);
|
|
|
|
|
assertEquals(400,assertThrows(ResponseStatusException.class,()->service.preview(refs)).getStatusCode().value());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@Test void groupingDifferentOriginsIsRejectedEvenWhenEveryIndividualSourceIsValid() {
|
|
|
|
|
var sources=mock(AppointmentSourceService.class);var mapper=mock(AppointmentBatchPreviewMapper.class);
|
|
|
|
|
var refs=List.of(new AppointmentCoverageService.SourceRef("A","BOOKING:A","FBA:ONT8"),new AppointmentCoverageService.SourceRef("B","BOOKING:B","FBA:ONT8"));
|
|
|
|
|
when(sources.resolveBatch(refs)).thenReturn(List.of(source("BOOKING:A","A",null,"100","FBA:ONT8"),source("BOOKING:B","B",null,"200","FBA:ONT8")));
|
2026-10-15 16:55:57 +08:00
|
|
|
assertThrows(ResponseStatusException.class,()->new AppointmentBatchPreviewService(sources,mapper,verification()).preview(refs));
|
2026-10-10 10:38:06 +08:00
|
|
|
verifyNoInteractions(mapper);
|
|
|
|
|
}
|
2026-10-10 18:37:38 +08:00
|
|
|
@Test void absentTimezoneStaysUnknownAndLookupFailureIsNotMasked() {
|
|
|
|
|
var sources=mock(AppointmentSourceService.class);var mapper=mock(AppointmentBatchPreviewMapper.class);
|
|
|
|
|
var refs=List.of(new AppointmentCoverageService.SourceRef("A","BOOKING:A","FBA:ONT8"));
|
|
|
|
|
when(sources.resolveBatch(refs)).thenReturn(List.of(source("BOOKING:A","A",null,"100","FBA:ONT8")));
|
2026-10-15 16:55:57 +08:00
|
|
|
var service=new AppointmentBatchPreviewService(sources,mapper,verification());
|
2026-10-10 18:37:38 +08:00
|
|
|
assertNull(service.preview(refs).destinationTimezone());
|
|
|
|
|
when(sources.destinationTimezone("ONT8")).thenThrow(new ResponseStatusException(org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE,"lookup failed"));
|
|
|
|
|
assertEquals(503,assertThrows(ResponseStatusException.class,()->service.preview(refs)).getStatusCode().value());
|
|
|
|
|
}
|
2026-10-10 10:38:06 +08:00
|
|
|
}
|