feat(appointment): integrate work calendar and stable cargo batches
This commit is contained in:
+2
-2
@@ -17,11 +17,11 @@ class AppointmentBatchPreviewMapperTest {
|
||||
s.execute("CREATE TABLE ll_order(id BIGINT, customer_id VARCHAR(32),detained BOOLEAN,fba_warehouse_code VARCHAR(32))");
|
||||
s.execute("CREATE TABLE ll_order_detail(order_id BIGINT,customer_id VARCHAR(32),destination_warehouse_code VARCHAR(32))");
|
||||
s.execute("CREATE TABLE ll_inbound_fba_appointment(id BIGINT,appointment_status VARCHAR(32))");
|
||||
s.execute("CREATE TABLE ll_inbound_fba_appointment_item(order_id BIGINT,customer_id VARCHAR(32),appointment_id BIGINT)");
|
||||
s.execute("CREATE TABLE ll_inbound_fba_appointment_item(order_id BIGINT,customer_id VARCHAR(32),appointment_id BIGINT,active BOOLEAN)");
|
||||
s.execute("INSERT INTO ll_order VALUES(1,'A',FALSE,'ONT8'),(2,'B',TRUE,'ONT8'),(3,'C',FALSE,'ONT8')");
|
||||
s.execute("INSERT INTO ll_order_detail VALUES(1,'OTHER','LAX9'),(2,'B','ONT8'),(3,'C','LAX9')");
|
||||
s.execute("INSERT INTO ll_inbound_fba_appointment VALUES(10,'CANCELLED'),(11,'SUCCESS')");
|
||||
s.execute("INSERT INTO ll_inbound_fba_appointment_item VALUES(1,'A',10),(1,'OTHER',11),(3,'C',11)");
|
||||
s.execute("INSERT INTO ll_inbound_fba_appointment_item VALUES(1,'A',10,TRUE),(1,'OTHER',11,TRUE),(3,'C',11,TRUE)");
|
||||
}
|
||||
var config=new Configuration(new Environment("test",new JdbcTransactionFactory(),ds));config.addMapper(AppointmentBatchPreviewMapper.class);
|
||||
try(var session=new SqlSessionFactoryBuilder().build(config).openSession()) {
|
||||
|
||||
+20
-5
@@ -7,6 +7,21 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class AppointmentBatchPreviewServiceTest {
|
||||
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));
|
||||
}
|
||||
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());
|
||||
}
|
||||
@@ -16,7 +31,7 @@ class AppointmentBatchPreviewServiceTest {
|
||||
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)));
|
||||
when(sources.destinationTimezone("ONT8")).thenReturn("America/Los_Angeles");
|
||||
var preview=new AppointmentBatchPreviewService(sources,mapper).preview(refs);
|
||||
var preview=new AppointmentBatchPreviewService(sources,mapper,verification()).preview(refs);
|
||||
assertEquals("America/Los_Angeles",preview.destinationTimezone());
|
||||
assertEquals(2,preview.group().customerCount());assertEquals(20,preview.group().packages());
|
||||
assertTrue(preview.group().readinessIncomplete());assertNull(preview.group().readyFrom());
|
||||
@@ -28,9 +43,9 @@ class AppointmentBatchPreviewServiceTest {
|
||||
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")));
|
||||
var service=new AppointmentBatchPreviewService(sources,mapper);
|
||||
var service=new AppointmentBatchPreviewService(sources,mapper,verification());
|
||||
for(var flags:List.of(List.<AppointmentBatchPreviewMapper.Flags>of(),List.of(new AppointmentBatchPreviewMapper.Flags(1L,true,false,false)),
|
||||
List.of(new AppointmentBatchPreviewMapper.Flags(1L,false,true,false)),List.of(new AppointmentBatchPreviewMapper.Flags(1L,false,false,true)))) {
|
||||
List.of(new AppointmentBatchPreviewMapper.Flags(1L,false,true,false)))) {
|
||||
when(mapper.flags(List.of(1L))).thenReturn(flags);
|
||||
assertEquals(400,assertThrows(ResponseStatusException.class,()->service.preview(refs)).getStatusCode().value());
|
||||
}
|
||||
@@ -39,14 +54,14 @@ class AppointmentBatchPreviewServiceTest {
|
||||
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")));
|
||||
assertThrows(ResponseStatusException.class,()->new AppointmentBatchPreviewService(sources,mapper).preview(refs));
|
||||
assertThrows(ResponseStatusException.class,()->new AppointmentBatchPreviewService(sources,mapper,verification()).preview(refs));
|
||||
verifyNoInteractions(mapper);
|
||||
}
|
||||
@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")));
|
||||
var service=new AppointmentBatchPreviewService(sources,mapper);
|
||||
var service=new AppointmentBatchPreviewService(sources,mapper,verification());
|
||||
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());
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ class AppointmentCargoMapperTest {
|
||||
s.execute("CREATE TABLE ll_order(id BIGINT,customer_id VARCHAR,customer_name VARCHAR,inbound_code VARCHAR,source_container_booking_id VARCHAR,container_no VARCHAR,overseas_warehouse_id VARCHAR,overseas_warehouse_code VARCHAR,overseas_warehouse_name VARCHAR,fba_warehouse_code VARCHAR,package_count INT,received_package_count INT,shipped_package_count INT,pallet_count INT,volume_cbm DECIMAL,estimated_arrival_at TIMESTAMP,estimated_warehouse_at TIMESTAMP,version INT,detained BOOLEAN,order_type VARCHAR,destination_type VARCHAR,current_status VARCHAR,created_at TIMESTAMP)");
|
||||
s.execute("CREATE TABLE ll_container_shipping_gate(container_booking_id VARCHAR,node_sequence INT,standard_node VARCHAR,aggregate_version BIGINT,observed_at TIMESTAMP,provider_code VARCHAR)");
|
||||
s.execute("CREATE TABLE ll_inbound_plan(id BIGINT,source_order_id BIGINT,customer_id VARCHAR)");
|
||||
s.execute("CREATE TABLE ll_inbound_fba_appointment_item(order_id BIGINT,customer_id VARCHAR,appointment_id BIGINT)");
|
||||
s.execute("CREATE TABLE ll_inbound_fba_appointment_item(order_id BIGINT,customer_id VARCHAR,appointment_id BIGINT,active BOOLEAN)");
|
||||
s.execute("CREATE TABLE ll_inbound_fba_appointment(id BIGINT,appointment_status VARCHAR)");
|
||||
s.execute("CREATE TABLE ll_order_detail(order_id BIGINT,customer_id VARCHAR,destination_warehouse_code VARCHAR)");
|
||||
s.execute("INSERT INTO ll_order(id,customer_id,inbound_code,source_container_booking_id,overseas_warehouse_id,fba_warehouse_code,package_count,order_type,destination_type,current_status,created_at) VALUES(1,'A','IN-A','C1','100','ONT8',10,'FIRST_LEG','FBA','WAIT_INBOUND',CURRENT_TIMESTAMP),(2,'B','IN-B','C2','100','ONT8',8,'FIRST_LEG','FBA','WAIT_INBOUND',CURRENT_TIMESTAMP)");
|
||||
@@ -33,7 +33,7 @@ class AppointmentCargoMapperTest {
|
||||
try (var s=session.getConnection().createStatement()) {
|
||||
s.execute("INSERT INTO ll_inbound_plan VALUES(11,1,'A'),(12,1,'A')");
|
||||
s.execute("INSERT INTO ll_inbound_fba_appointment VALUES(20,'SUCCESS')");
|
||||
s.execute("INSERT INTO ll_inbound_fba_appointment_item VALUES(1,'A',20)");
|
||||
s.execute("INSERT INTO ll_inbound_fba_appointment_item VALUES(1,'A',20,TRUE)");
|
||||
}
|
||||
session.clearCache();
|
||||
var after=mapper.page(List.of("100"),List.of("ONT8"),null,null,null,20,0);
|
||||
|
||||
+8
-1
@@ -24,6 +24,11 @@ class AppointmentCoverageMapperTest {
|
||||
s.execute("INSERT INTO ll_warehouse_transfer_order VALUES(30,3,'C','FBA','ACY2','SHIP-C',NULL)");
|
||||
s.execute("INSERT INTO ll_inbound_fba_appointment VALUES(1000,'AP-1','SUCCESS',CURRENT_TIMESTAMP,NULL,CURRENT_TIMESTAMP,'EXT-1','America/Los_Angeles'),(2000,'CANCELLED-1','CANCELLED',CURRENT_TIMESTAMP,NULL,CURRENT_TIMESTAMP,'EXT-2','America/Los_Angeles')");
|
||||
s.execute("INSERT INTO ll_inbound_fba_appointment_item VALUES(1,1000,1,'A','ORDER:1','ORDER:1',20,'ONT8',TRUE),(2,1000,2,'B','ORDER:2','ORDER:1',8,'ONT8',TRUE),(3,2000,1,'A','ORDER:1','ORDER:1',20,'ONT8',FALSE),(4,1000,NULL,'D','BOOKING:B-1','BOOKING:1',NULL,'ONT8',TRUE)");
|
||||
s.execute("ALTER TABLE ll_inbound_fba_appointment_item ADD cargo_verification_version INT");
|
||||
String baseline;try(var input=getClass().getResourceAsStream("/db/lastleg.sql")){baseline=new String(java.util.Objects.requireNonNull(input).readAllBytes(),java.nio.charset.StandardCharsets.UTF_8);}
|
||||
int start=baseline.indexOf("CREATE TABLE IF NOT EXISTS ll_appointment_cargo_verification (");
|
||||
s.execute(baseline.substring(start,baseline.indexOf(';',start)+1));
|
||||
s.execute("INSERT INTO ll_appointment_cargo_verification(id,customer_id,source_key,destination_key,version,package_count,shipment_references_json,operator_id,command_key,request_hash,created_at) VALUES(1,'A','ORDER:1','FBA:ONT8',1,19,'[]','operator','one',REPEAT('a',64),CURRENT_TIMESTAMP),(2,'A','ORDER:1','FBA:ONT8',2,20,'[\"FBA-A\"]','operator','two',REPEAT('b',64),CURRENT_TIMESTAMP),(3,'B','ORDER:1','FBA:ONT8',1,8,'[]','operator','one',REPEAT('c',64),CURRENT_TIMESTAMP)");
|
||||
}
|
||||
var config=new Configuration(new Environment("test",new JdbcTransactionFactory(),ds));config.setMapUnderscoreToCamelCase(true);config.addMapper(AppointmentCoverageMapper.class);
|
||||
try(var session=new SqlSessionFactoryBuilder().build(config).openSession(true)) {
|
||||
@@ -35,6 +40,8 @@ class AppointmentCoverageMapperTest {
|
||||
var external=mapper.appointments(List.of(new AppointmentCoverageService.SourceRef("D","BOOKING:B-1","FBA:ONT8"))).get(0);assertNull(external.orderId());assertNull(external.packages());
|
||||
var transfer=mapper.sources(List.of(new AppointmentCoverageMapper.OrderRef("C",3L))).get(0);assertEquals("FBA",transfer.destinationType());assertEquals("ACY2",transfer.fbaWarehouseCode());assertFalse(transfer.shipmentReferencesMissing());
|
||||
assertTrue(mapper.sources(List.of(new AppointmentCoverageMapper.OrderRef("B",2L))).get(0).shipmentReferencesMissing());
|
||||
var evidence=mapper.verifications(List.of(new AppointmentCoverageService.SourceRef("A","ORDER:1","FBA:ONT8")));
|
||||
assertEquals(1,evidence.size());assertEquals(2,evidence.get(0).getVersion());assertEquals(20,evidence.get(0).getPackageCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-3
@@ -13,7 +13,7 @@ class AppointmentCoverageServiceTest {
|
||||
return new AppointmentCoverageMapper.Source(1L,"A","FBA","ONT8",20,2,BEFORE,false);
|
||||
}
|
||||
private static AppointmentCoverageMapper.Fact fact(long line,int packages,String status) {
|
||||
return new AppointmentCoverageMapper.Fact(1L,"A","AP-1",status,AFTER.plusDays(1),null,AFTER,line,packages,"ONT8","ORDER:1","ORDER:2","EXT-1","America/Los_Angeles");
|
||||
return new AppointmentCoverageMapper.Fact(1L,"A","AP-1",status,AFTER.plusDays(1),null,AFTER,line,packages,"ONT8","ORDER:1","ORDER:2","EXT-1","America/Los_Angeles",null);
|
||||
}
|
||||
@Test void completeQuantitySharesOneAppointmentWithItsActualConfirmationAndTimezone() {
|
||||
var mapper=mock(AppointmentCoverageMapper.class); var gateway=mock(AppointmentExternalSourceGateway.class);
|
||||
@@ -42,7 +42,7 @@ class AppointmentCoverageServiceTest {
|
||||
when(gateway.resolve(anyList())).thenReturn(List.of(new AppointmentExternalSourceGateway.Source("A","B-1","TR-1","CONFIRMED","100","FBA:ONT8",null,20,1L)));
|
||||
assertEquals("SOURCE_NOT_MATERIALIZED",service.query(refs).get(0).status()); verify(mapper,never()).sources(anyList());
|
||||
when(gateway.resolve(anyList())).thenReturn(List.of(new AppointmentExternalSourceGateway.Source("A","B-1","TR-1","BOUND","100","FBA:ONT8",1L,20,2L)));
|
||||
when(mapper.sources(anyList())).thenReturn(List.of(order())); when(mapper.appointments(anyList())).thenReturn(List.of(new AppointmentCoverageMapper.Fact(1L,"A","AP-1","SUCCESS",AFTER.plusDays(1),null,AFTER,10L,20,"ONT8","BOOKING:B-1","BOOKING:2/ORDER:2","EXT-1","America/Los_Angeles")));
|
||||
when(mapper.sources(anyList())).thenReturn(List.of(order())); when(mapper.appointments(anyList())).thenReturn(List.of(new AppointmentCoverageMapper.Fact(1L,"A","AP-1","SUCCESS",AFTER.plusDays(1),null,AFTER,10L,20,"ONT8","BOOKING:B-1","BOOKING:2/ORDER:2","EXT-1","America/Los_Angeles",null)));
|
||||
var result=service.query(refs).get(0); assertEquals("BOOKING:B-1",result.sourceKey()); assertEquals("CONFIRMED",result.status());
|
||||
assertThrows(IllegalArgumentException.class, () -> service.query(List.of(refs.get(0),new AppointmentCoverageService.SourceRef("A","ORDER:1","FBA:ONT8"))));
|
||||
}
|
||||
@@ -60,6 +60,22 @@ class AppointmentCoverageServiceTest {
|
||||
var result=new AppointmentCoverageService(mapper,mock(AppointmentExternalSourceGateway.class)).query(List.of(new AppointmentCoverageService.SourceRef("A","ORDER:1","FBA:ONT8"))).get(0);
|
||||
assertTrue(result.changeReviewRequired()); assertTrue(result.missingFields().containsAll(List.of("SOURCE_CHANGED_REVIEW","FBA_SHIPMENT_REFERENCES")));
|
||||
assertEquals(AFTER.plusDays(1),result.appointments().get(0).confirmedAt());
|
||||
verify(mapper,times(1)).sources(anyList()); verify(mapper,times(1)).appointments(anyList()); verifyNoMoreInteractions(mapper);
|
||||
verify(mapper,times(1)).sources(anyList()); verify(mapper,times(1)).appointments(anyList()); verify(mapper,times(1)).verifications(anyList()); verifyNoMoreInteractions(mapper);
|
||||
}
|
||||
@Test void independentVerificationSuppliesQuantityAndReferencesWithoutRewritingOriginalOrderFacts() {
|
||||
var mapper=mock(AppointmentCoverageMapper.class);
|
||||
when(mapper.sources(anyList())).thenReturn(List.of(new AppointmentCoverageMapper.Source(1L,"A","FBA","ONT8",null,2,BEFORE,true)));
|
||||
var evidence=new AppointmentCargoVerification();evidence.setCustomerId("A");evidence.setSourceKey("ORDER:1");evidence.setDestinationKey("FBA:ONT8");
|
||||
evidence.setSourceVersion("ORDER:2");evidence.setPackageCount(20);evidence.setVersion(1);evidence.setShipmentReferencesJson("[\"FBA-A\"]");
|
||||
when(mapper.verifications(anyList())).thenReturn(List.of(evidence));
|
||||
when(mapper.appointments(anyList())).thenReturn(List.of(new AppointmentCoverageMapper.Fact(1L,"A","AP-1","SUCCESS",AFTER.plusDays(1),null,AFTER,10L,20,"ONT8","ORDER:1","ORDER:2","EXT-1","America/Los_Angeles",1)));
|
||||
var service=new AppointmentCoverageService(mapper,mock(AppointmentExternalSourceGateway.class));
|
||||
var refs=List.of(new AppointmentCoverageService.SourceRef("A","ORDER:1","FBA:ONT8"));
|
||||
var result=service.query(refs).get(0);
|
||||
assertEquals("CONFIRMED",result.status());assertFalse(result.changeReviewRequired());assertFalse(result.missingFields().contains("FBA_SHIPMENT_REFERENCES"));
|
||||
evidence.setVersion(2);evidence.setShipmentReferencesJson("[\"FBA-CHANGED\"]");
|
||||
var changed=service.query(refs).get(0);
|
||||
assertTrue(changed.changeReviewRequired());assertTrue(changed.missingFields().contains("SOURCE_CHANGED_REVIEW"));
|
||||
assertEquals(result.appointments(),changed.appointments(),"核实资料变更不能重写原确认时间");
|
||||
}
|
||||
}
|
||||
|
||||
+53
@@ -17,6 +17,53 @@ import static org.mockito.Mockito.*;
|
||||
|
||||
class AppointmentPlanningAuthorizationTest {
|
||||
private static final String LIST="platform:appointment:list", CARGO="platform:overseas-warehouse:last-mile-order:list", WRITE="platform:appointment:followup";
|
||||
@Test void orderLookupUsesCanonicalBookingSourceAndEnforcesCustomerAndReadPermissions() {
|
||||
try(var context=new AnnotationConfigApplicationContext(Config.class)) {
|
||||
var controller=context.getBean(AppointmentPlanningController.class);
|
||||
var sources=context.getBean(AppointmentSourceService.class);
|
||||
var coverage=context.getBean(AppointmentCoverageService.class);
|
||||
login(LIST);assertThrows(AccessDeniedException.class,()->controller.resolveOrder(user(0L,"PLATFORM"),1L,"A","FBA:ONT8"));
|
||||
login(CARGO);assertThrows(AccessDeniedException.class,()->controller.resolveOrder(user(0L,"PLATFORM"),1L,"A","FBA:ONT8"));
|
||||
login(LIST,CARGO);
|
||||
assertThrows(ResponseStatusException.class,()->controller.resolveOrder(user(1L,"CLIENT"),1L,"A","FBA:ONT8"));
|
||||
assertThrows(ResponseStatusException.class,()->controller.resolveOrder(user(0L,"PLATFORM"),0L,"A","FBA:ONT8"));
|
||||
verifyNoInteractions(sources);
|
||||
var ref=new AppointmentCoverageService.SourceRef("A","BOOKING:B-1","FBA:ONT8");
|
||||
var source=new AppointmentSourceService.Source("BOOKING:B-1","A",1L,"IN-A","B-1",null,"100","美国仓","FBA:ONT8","ONT8",10,"BOOKING:2/ORDER:1",true,List.of());
|
||||
when(sources.canonicalOrders(List.of(new AppointmentExternalSourceGateway.OrderRef("A",1L)))).thenReturn(List.of(ref));
|
||||
when(sources.resolve("BOOKING:B-1","A","FBA:ONT8")).thenReturn(source);
|
||||
var snapshot=new AppointmentCoverageService.Coverage("A","BOOKING:B-1","FBA:ONT8","PENDING",List.of(),List.of(),source.sourceVersion(),false);
|
||||
when(coverage.query(List.of(ref))).thenReturn(List.of(snapshot));
|
||||
assertEquals(source,controller.resolveOrder(user(0L,"PLATFORM"),1L,"A","FBA:ONT8").data().source());
|
||||
verify(sources,never()).resolve("ORDER:1","A","FBA:ONT8");
|
||||
when(sources.canonicalOrders(List.of(new AppointmentExternalSourceGateway.OrderRef("OTHER",1L)))).thenReturn(List.of(new AppointmentCoverageService.SourceRef("OTHER","ORDER:1",null)));
|
||||
when(sources.resolve("ORDER:1","OTHER","FBA:ONT8")).thenThrow(new ResponseStatusException(org.springframework.http.HttpStatus.NOT_FOUND));
|
||||
assertEquals(404,assertThrows(ResponseStatusException.class,()->controller.resolveOrder(user(0L,"PLATFORM"),1L,"OTHER","FBA:ONT8")).getStatusCode().value());
|
||||
verify(coverage,times(1)).query(anyList());
|
||||
} finally {SecurityContextHolder.clearContext();}
|
||||
}
|
||||
@Test void batchContextRequiresBothListPermissionsAndRejectsCustomerIdentity() {
|
||||
try(var context=new AnnotationConfigApplicationContext(Config.class)) {
|
||||
var controller=context.getBean(com.baiyue.lastleg.inboundplan.controller.FbaAppointmentBatchController.class);
|
||||
login(LIST);assertThrows(AccessDeniedException.class,()->controller.detail(user(0L,"PLATFORM"),"FBA_APPOINTMENT:1"));
|
||||
login(LIST,CARGO);assertThrows(ResponseStatusException.class,()->controller.detail(user(1L,"CLIENT"),"FBA_APPOINTMENT:1"));
|
||||
assertDoesNotThrow(()->controller.detail(user(0L,"PLATFORM"),"FBA_APPOINTMENT:1"));
|
||||
} finally {SecurityContextHolder.clearContext();}
|
||||
}
|
||||
@Test void cargoVerificationRequiresIndependentWritePermissionAndPlatformIdentity() {
|
||||
try(var context=new AnnotationConfigApplicationContext(Config.class)) {
|
||||
var controller=context.getBean(AppointmentCargoVerificationController.class);
|
||||
var request=new AppointmentCargoVerificationService.Save("FBA:ONT8","ORDER:1",0,10,List.of("FBA-A"),null,"verify");
|
||||
login(LIST);assertThrows(AccessDeniedException.class,()->controller.get(user(0L,"PLATFORM"),"ORDER:1","A",null));
|
||||
login(LIST,CARGO);controller.get(user(0L,"PLATFORM"),"ORDER:1","A",null);
|
||||
assertThrows(AccessDeniedException.class,()->controller.save(user(0L,"PLATFORM"),"ORDER:1","A",request));
|
||||
login(LIST,CARGO,WRITE);assertThrows(AccessDeniedException.class,()->controller.save(user(0L,"PLATFORM"),"ORDER:1","A",request));
|
||||
login(LIST,CARGO,com.baiyue.lastleg.security.OverseasPermissions.INBOUND_ORDER_FBA_APPOINTMENT);
|
||||
assertThrows(ResponseStatusException.class,()->controller.save(user(1L,"CLIENT"),"ORDER:1","A",request));
|
||||
controller.save(user(0L,"PLATFORM"),"ORDER:1","A",request);
|
||||
verify(context.getBean(AppointmentCargoVerificationService.class)).save("ORDER:1","A",request,"7","operator");
|
||||
} finally {SecurityContextHolder.clearContext();}
|
||||
}
|
||||
@Test void savingSourceAppointmentsNeedsTheExistingFbaWritePermissionAndNeverAcceptsClientIdentity() {
|
||||
try(var context=new AnnotationConfigApplicationContext(Config.class)) {
|
||||
var controller=context.getBean(AppointmentSourceCreateController.class);
|
||||
@@ -24,9 +71,12 @@ class AppointmentPlanningAuthorizationTest {
|
||||
"100","ONT8",new com.baiyue.lastleg.inboundplan.dto.FbaAppointmentDetails(null,null,null,null,null,null,"PENDING",null,null,null),"key");
|
||||
login(LIST,CARGO,WRITE);
|
||||
assertThrows(AccessDeniedException.class,()->controller.create(user(0L,"PLATFORM"),request));
|
||||
assertThrows(AccessDeniedException.class,()->controller.repartition(user(0L,"PLATFORM"),null));
|
||||
login(LIST,CARGO,com.baiyue.lastleg.security.OverseasPermissions.INBOUND_ORDER_FBA_APPOINTMENT);
|
||||
assertThrows(ResponseStatusException.class,()->controller.create(user(1L,"CLIENT"),request));
|
||||
assertThrows(ResponseStatusException.class,()->controller.repartition(user(1L,"CLIENT"),null));
|
||||
assertDoesNotThrow(()->controller.create(user(0L,"PLATFORM"),request));
|
||||
assertDoesNotThrow(()->controller.repartition(user(0L,"PLATFORM"),null));
|
||||
login(LIST);assertDoesNotThrow(()->controller.unscheduled(user(0L,"PLATFORM"),1,20));
|
||||
} finally {SecurityContextHolder.clearContext();}
|
||||
}
|
||||
@@ -91,6 +141,9 @@ class AppointmentPlanningAuthorizationTest {
|
||||
private static void login(String... permissions) { SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("operator","unused",java.util.Arrays.stream(permissions).map(SimpleGrantedAuthority::new).toList())); }
|
||||
@Configuration @EnableMethodSecurity
|
||||
static class Config {
|
||||
@Bean com.baiyue.lastleg.inboundplan.controller.FbaAppointmentBatchController batchController() {return new com.baiyue.lastleg.inboundplan.controller.FbaAppointmentBatchController(mock(com.baiyue.lastleg.inboundplan.service.InboundFbaAppointmentService.class));}
|
||||
@Bean AppointmentCargoVerificationService verificationService() {return mock(AppointmentCargoVerificationService.class);}
|
||||
@Bean AppointmentCargoVerificationController verificationController(AppointmentCargoVerificationService service) {return new AppointmentCargoVerificationController(service);}
|
||||
@Bean AppointmentSourceCreateController sourceCreateController() { return new AppointmentSourceCreateController(mock(com.baiyue.lastleg.inboundplan.service.InboundFbaAppointmentService.class)); }
|
||||
@Bean AppointmentCoverageMapper destinationFacts() { return mock(AppointmentCoverageMapper.class); }
|
||||
@Bean AppointmentBatchPreviewService batchPreviews() { return mock(AppointmentBatchPreviewService.class); }
|
||||
|
||||
Reference in New Issue
Block a user