feat(appointments): expose authoritative arrival forecast observations
This commit is contained in:
+31
@@ -0,0 +1,31 @@
|
||||
package com.baiyue.firstleg.portschedule.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
|
||||
import org.apache.ibatis.mapping.Environment;
|
||||
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.util.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
class AppointmentForecastMapperTest {
|
||||
@Test void keepsNewestTwoObservationsIncludingWithdrawnEtaAndExcludesOtherNodes() throws Exception {
|
||||
var ds=new JdbcDataSource();ds.setURL("jdbc:h2:mem:"+UUID.randomUUID()+";MODE=MySQL;DB_CLOSE_DELAY=-1");
|
||||
try(var c=ds.getConnection();var s=c.createStatement()) {
|
||||
s.execute("CREATE TABLE fl_container_booking(id BIGINT PRIMARY KEY,record_status VARCHAR(32))");
|
||||
s.execute("CREATE TABLE fl_carrier_schedule_node_fact(id BIGINT PRIMARY KEY,container_booking_id BIGINT,standard_node VARCHAR(64),provider_code VARCHAR(64),source_reference VARCHAR(128),observed_at TIMESTAMP,estimated_at TIMESTAMP,actual_at TIMESTAMP)");
|
||||
s.execute("INSERT INTO fl_container_booking VALUES(1,'ACTIVE'),(2,'CANCELLED'),(3,'ACTIVE')");
|
||||
for(int id=1;id<=3;id++) s.execute("INSERT INTO fl_carrier_schedule_node_fact VALUES("+id+",1,'ARRIVED_DESTINATION_PORT','carrier','REF','2026-10-10 12:00:00',"+(id==3?"NULL":"'2026-10-15 09:00:00'")+",NULL)");
|
||||
s.execute("INSERT INTO fl_carrier_schedule_node_fact VALUES(4,1,'DEPARTED_ORIGIN_PORT','carrier','OTHER','2026-10-11',NULL,NULL),(5,2,'ARRIVED_DESTINATION_PORT','carrier','REF','2026-10-11','2026-10-15',NULL),(6,3,'ARRIVED_DESTINATION_PORT','carrier','REF','2026-10-11',NULL,'2026-10-11')");
|
||||
}
|
||||
var config=new MybatisConfiguration();config.setMapUnderscoreToCamelCase(true);
|
||||
config.setEnvironment(new Environment("test",new JdbcTransactionFactory(),ds));config.addMapper(AppointmentForecastMapper.class);
|
||||
try(var session=new MybatisSqlSessionFactoryBuilder().build(config).openSession()) {
|
||||
var rows=session.getMapper(AppointmentForecastMapper.class).latest(List.of(1L,2L,3L));
|
||||
assertThat(rows).extracting(AppointmentForecastMapper.Fact::factId).containsExactly(3L,2L,6L);
|
||||
assertThat(rows.get(0).estimatedAt()).isNull();assertThat(rows.get(1).estimatedAt()).isNotNull();
|
||||
assertThat(rows.get(2).actualAt()).isNotNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.baiyue.firstleg.portschedule.service;
|
||||
|
||||
import com.baiyue.firstleg.portschedule.mapper.AppointmentForecastMapper;
|
||||
import com.baiyue.firstleg.portschedule.controller.InternalAppointmentForecastController;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class AppointmentForecastServiceTest {
|
||||
@Test void preservesMissingForecastAndSourceLocalTimesWithoutSynthesizingEta() {
|
||||
var mapper=mock(AppointmentForecastMapper.class);var service=new AppointmentForecastService(mapper);
|
||||
var observed=LocalDateTime.of(2026,10,10,12,0);
|
||||
var latest=new AppointmentForecastMapper.Fact(1L,20L,"carrier","REF",observed,null,null);
|
||||
var previous=new AppointmentForecastMapper.Fact(1L,10L,"carrier","REF-OLD",observed.minusDays(1),observed.plusDays(5),null);
|
||||
when(mapper.latest(List.of(1L,2L))).thenReturn(List.of(latest,previous));
|
||||
var result=service.batch(List.of(1L,2L));
|
||||
assertThat(result.get(0).latest()).isSameAs(latest);assertThat(result.get(0).previous()).isSameAs(previous);
|
||||
assertThat(result.get(1).latest()).isNull();assertThat(result.get(1).timeBasis()).isEqualTo("SOURCE_LOCAL_UNSPECIFIED");
|
||||
verify(mapper).latest(List.of(1L,2L));verifyNoMoreInteractions(mapper);
|
||||
}
|
||||
@Test void rejectsInvalidBatchesAndMissingOrBlankInternalCredentials() {
|
||||
var mapper=mock(AppointmentForecastMapper.class);var service=new AppointmentForecastService(mapper);
|
||||
for(var ids:List.of(List.<Long>of(),List.of(1L,1L),List.of(-1L),Collections.nCopies(201,1L)))
|
||||
assertThatThrownBy(()->service.batch(ids)).isInstanceOf(org.springframework.web.server.ResponseStatusException.class);
|
||||
assertThatThrownBy(()->service.batch(null)).isInstanceOf(org.springframework.web.server.ResponseStatusException.class);
|
||||
var controller=new InternalAppointmentForecastController(service,"expected");
|
||||
assertThatThrownBy(()->controller.batch(null,new InternalAppointmentForecastController.Request(List.of(1L)))).isInstanceOf(org.springframework.web.server.ResponseStatusException.class);
|
||||
assertThatThrownBy(()->controller.batch("wrong",new InternalAppointmentForecastController.Request(List.of(1L)))).isInstanceOf(org.springframework.web.server.ResponseStatusException.class);
|
||||
assertThatThrownBy(()->new InternalAppointmentForecastController(service,"").batch("",new InternalAppointmentForecastController.Request(List.of(1L)))).isInstanceOf(org.springframework.web.server.ResponseStatusException.class);
|
||||
verifyNoInteractions(mapper);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user