为船期节点记录并返回操作人快照
This commit is contained in:
+10
@@ -8,6 +8,16 @@ import java.nio.charset.StandardCharsets;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CarrierScheduleQueryOpenApiContractTest {
|
||||
/** 两个节点读取入口使用相同的操作用户名响应契约。 */
|
||||
@Test
|
||||
void nodeFactsExposeTheRecordedOperatorUsername() throws Exception {
|
||||
String yaml = new ClassPathResource("openapi/carrier-schedule-query-v1.yaml")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
String node = yaml.substring(yaml.indexOf(" NodeFact:"), yaml.indexOf(" ExceptionFact:"));
|
||||
assertThat(node).contains("operatorName: {type: string, maxLength: 128, nullable: true")
|
||||
.contains("节点操作人用户名快照");
|
||||
}
|
||||
|
||||
@Test
|
||||
void publishesBusinessNodesWithoutSchedulerOrAttemptEndpoints() throws Exception {
|
||||
String yaml = new ClassPathResource("openapi/carrier-schedule-query-v1.yaml")
|
||||
|
||||
+17
@@ -16,6 +16,23 @@ import java.util.List;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CarrierScheduleQuerySqlContractTest {
|
||||
/** 模块与部署基线共同保存节点署名,Mapper 同次写入两项快照。 */
|
||||
@Test
|
||||
void nodeFactsPersistOperatorSnapshotInBothBaselines() throws Exception {
|
||||
String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
|
||||
StandardCharsets.UTF_8);
|
||||
for (String source : List.of(moduleSql(), unified)) {
|
||||
String lower = source.toLowerCase(java.util.Locale.ROOT);
|
||||
int start = lower.indexOf("fl_carrier_schedule_node_fact (");
|
||||
String table = lower.substring(start, lower.indexOf("engine=innodb", start));
|
||||
assertThat(table).contains("operator_id bigint unsigned", "operator_name varchar(128)");
|
||||
}
|
||||
String insert = String.join("\n", com.baiyue.firstleg.portschedule.mapper.CarrierScheduleNodeFactMapper.class
|
||||
.getMethod("insertStrict", com.baiyue.firstleg.portschedule.entity.CarrierScheduleNodeFactEntity.class)
|
||||
.getAnnotation(org.apache.ibatis.annotations.Insert.class).value());
|
||||
assertThat(insert).contains("operator_id,operator_name", "#{value.operatorId},#{value.operatorName}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void deployedTriggerPredicateMapsPlatformBookingToScopeZeroAndStillRejectsOtherCustomers() throws Exception {
|
||||
JdbcDataSource database = new JdbcDataSource();
|
||||
|
||||
+9
-2
@@ -133,14 +133,21 @@ class CarrierScheduleExceptionPersistenceTest {
|
||||
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM fl_carrier_schedule_exception_fact WHERE source_reference IS NOT NULL AND raw_payload_hash IS NOT NULL", Integer.class)).isEqualTo(8);
|
||||
}
|
||||
|
||||
/** 真实 Mapper 保存并返回操作用户名,账号更名后重放不改写原署名。 */
|
||||
@Test
|
||||
void originalContainerManualBodyReplaysWithoutUpdateHistoryOrFactMutationAndRejectsChangedFact() {
|
||||
var body = new com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ContainerManualNodeRequest(
|
||||
"ARRIVED_DESTINATION_PORT", TIME.plusDays(2), null, TIME.plusDays(2), "USLAX", "Los Angeles",
|
||||
"synthetic container manual fact");
|
||||
service.addContainerManualNode(3,10001,"test-operator","original-body-key",body);
|
||||
var detail = service.addContainerManualNode(3,10001,"platform_admin","original-body-key",body);
|
||||
assertThat(detail.nodes().stream().filter(node -> "MANUAL".equals(node.providerCode())).toList())
|
||||
.singleElement().satisfies(node -> assertThat(node.operatorName()).isEqualTo("platform_admin"));
|
||||
assertThat(jdbc.queryForObject("SELECT operator_id FROM fl_carrier_schedule_node_fact WHERE provider_code='MANUAL'", Long.class))
|
||||
.isEqualTo(10001L);
|
||||
assertThat(jdbc.queryForObject("SELECT operator_name FROM fl_carrier_schedule_node_fact WHERE provider_code='MANUAL'", String.class))
|
||||
.isEqualTo("platform_admin");
|
||||
var saved = allSnapshot();
|
||||
service.addContainerManualNode(3,10001,"test-operator","original-body-key",body);
|
||||
service.addContainerManualNode(3,10001,"renamed_operator","original-body-key",body);
|
||||
assertThat(allSnapshot()).isEqualTo(saved);
|
||||
assertThatThrownBy(() -> service.addContainerManualNode(3,10001,"test-operator","original-body-key",
|
||||
new com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.ContainerManualNodeRequest(
|
||||
|
||||
+19
-8
@@ -183,6 +183,7 @@ class CarrierScheduleQueryServiceTest {
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("柜号").hasMessageContaining("漂移");
|
||||
}
|
||||
|
||||
/** 出仓节点记录确认人的账号,供两个货柜详情展示。 */
|
||||
@Test
|
||||
void outboundConfirmationCreatesTaskWithContainerTrackingAndDepartureNode() {
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setCustomerId(10001L);
|
||||
@@ -194,7 +195,7 @@ class CarrierScheduleQueryServiceTest {
|
||||
when(portSchedules.allocation(10L, 20L)).thenReturn(new OceanBookingContainerAllocationEntity());
|
||||
when(repository.insertIfAbsent(any(CarrierScheduleNodeFactEntity.class))).thenReturn(true);
|
||||
|
||||
CarrierScheduleQueryEntity result = service.ensureFromOutboundConfirm(10001L, 10L, 20L, 0L, "SYSTEM");
|
||||
CarrierScheduleQueryEntity result = service.ensureFromOutboundConfirm(10001L, 10L, 20L, 7L, "platform_admin");
|
||||
|
||||
assertThat(result.getTriggerSource()).isEqualTo("OUTBOUND_CONFIRM");
|
||||
assertThat(result.getCarrierCode()).isEqualTo("COSCO");
|
||||
@@ -204,23 +205,25 @@ class CarrierScheduleQueryServiceTest {
|
||||
assertThat(result.getTrackingType()).isEqualTo("CONTAINER_NO");
|
||||
assertThat(result.getLatestStandardNode()).isEqualTo("DEPARTED_WAREHOUSE");
|
||||
assertThat(result.getNextAllowedAt()).isEqualTo(LocalDateTime.of(2026, 8, 10, 12, 30));
|
||||
assertThat(result.getCreatedBy()).isZero();
|
||||
assertThat(result.getCreatedByName()).isEqualTo("SYSTEM");
|
||||
assertThat(result.getUpdatedBy()).isZero();
|
||||
assertThat(result.getUpdatedByName()).isEqualTo("SYSTEM");
|
||||
assertThat(result.getCreatedBy()).isEqualTo(7L);
|
||||
assertThat(result.getCreatedByName()).isEqualTo("platform_admin");
|
||||
assertThat(result.getUpdatedBy()).isEqualTo(7L);
|
||||
assertThat(result.getUpdatedByName()).isEqualTo("platform_admin");
|
||||
verify(repository).insertIfAbsent(any(com.baiyue.firstleg.portschedule.entity.CarrierScheduleNodeFactEntity.class));
|
||||
verify(nodeOutbox).publish(eq(result), argThat(fact ->
|
||||
"FIRSTLEG".equals(fact.getProviderCode())
|
||||
&& "OUTBOUND_CONFIRM".equals(fact.getProviderEventId())
|
||||
&& "OUTBOUND_CONFIRM".equals(fact.getProviderNodeCode())
|
||||
&& "DEPARTED_WAREHOUSE".equals(fact.getStandardNode())
|
||||
&& Long.valueOf(7L).equals(fact.getOperatorId())
|
||||
&& "platform_admin".equals(fact.getOperatorName())
|
||||
&& fact.getActualAt() != null), eq("ZCSU7881970"));
|
||||
verify(repository).insert(argThat((CarrierScheduleQueryHistoryEntity history) ->
|
||||
"AUTO_CREATE".equals(history.getAction())
|
||||
&& history.getFromVersion() == null
|
||||
&& Integer.valueOf(0).equals(history.getToVersion())
|
||||
&& Long.valueOf(0L).equals(history.getOperatorId())
|
||||
&& "SYSTEM".equals(history.getOperatorName())
|
||||
&& Long.valueOf(7L).equals(history.getOperatorId())
|
||||
&& "platform_admin".equals(history.getOperatorName())
|
||||
&& "国内确认出仓自动创建".equals(history.getReason())));
|
||||
}
|
||||
|
||||
@@ -360,6 +363,7 @@ class CarrierScheduleQueryServiceTest {
|
||||
verify(repository, never()).insertIfAbsent(any(CarrierScheduleNodeFactEntity.class));
|
||||
}
|
||||
|
||||
/** 自动采集推进原节点,但不冒用平台操作账号。 */
|
||||
@Test
|
||||
void providerActualObservationAdvancesBusinessNodeWithoutChangingTaskStatus() {
|
||||
CarrierScheduleQueryEntity task = task(0, 0);
|
||||
@@ -377,6 +381,8 @@ class CarrierScheduleQueryServiceTest {
|
||||
assertThat(result.query().query().status()).isEqualTo("READY");
|
||||
assertThat(result.query().query().latestActualNode()).isEqualTo("ARRIVED_DESTINATION_PORT");
|
||||
assertThat(result.nodes()).isEmpty();
|
||||
verify(repository).insertIfAbsent(argThat((CarrierScheduleNodeFactEntity fact) ->
|
||||
fact.getOperatorId() == null && fact.getOperatorName() == null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -448,6 +454,7 @@ class CarrierScheduleQueryServiceTest {
|
||||
verify(nodeOutbox).publish(eq(task), any(CarrierScheduleNodeFactEntity.class), eq("ZCSU7881970"));
|
||||
}
|
||||
|
||||
/** 人工实际节点保留操作账号及既有还柜幂等行为。 */
|
||||
@Test
|
||||
void manualActualEmptyReturnedUsesExistingPermissionFlowAndStopsIdempotently() {
|
||||
CarrierScheduleQueryEntity task = task(0, 3);
|
||||
@@ -462,6 +469,8 @@ class CarrierScheduleQueryServiceTest {
|
||||
|
||||
assertThat(result.query().query().status()).isEqualTo("STOPPED");
|
||||
assertThat(result.query().query().latestActualNode()).isEqualTo("EMPTY_RETURNED");
|
||||
verify(repository).insertIfAbsent(argThat((CarrierScheduleNodeFactEntity fact) ->
|
||||
Long.valueOf(7L).equals(fact.getOperatorId()) && "admin".equals(fact.getOperatorName())));
|
||||
verify(repository).completeOperation(eq(1L), eq(7L), eq("manual-empty-1"), anyString());
|
||||
}
|
||||
|
||||
@@ -488,6 +497,7 @@ class CarrierScheduleQueryServiceTest {
|
||||
verify(repository).updateState(before, 3, 7L, "admin");
|
||||
}
|
||||
|
||||
/** 柜级补录沿用当前账号,并保持已有跟踪任务和事件投递。 */
|
||||
@Test
|
||||
void containerManualNodeJoinsAndAdvancesExistingCarrierTask() {
|
||||
CarrierScheduleQueryEntity task = task(0, 8);
|
||||
@@ -509,7 +519,8 @@ class CarrierScheduleQueryServiceTest {
|
||||
"PICKED_UP", NOW, null, NOW, "USLAX", "Los Angeles", "terminal pickup"));
|
||||
|
||||
verify(repository).insertIfAbsent(argThat((CarrierScheduleNodeFactEntity fact) -> Long.valueOf(1L).equals(fact.getQueryId())
|
||||
&& fact.getContainerBookingId() == 20L && "PICKED_UP".equals(fact.getStandardNode())));
|
||||
&& fact.getContainerBookingId() == 20L && "PICKED_UP".equals(fact.getStandardNode())
|
||||
&& Long.valueOf(7L).equals(fact.getOperatorId()) && "admin".equals(fact.getOperatorName())));
|
||||
verify(repository).updateState(task, 8, 7L, "admin");
|
||||
verify(repository).insert(argThat((CarrierScheduleQueryHistoryEntity history) ->
|
||||
"MANUAL_NODE".equals(history.getAction()) && Long.valueOf(1L).equals(history.getQueryId())
|
||||
|
||||
Reference in New Issue
Block a user