优化
This commit is contained in:
+8
-2
@@ -19,8 +19,10 @@ class CarrierScheduleQuerySqlContractTest {
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_carrier_schedule_query_operation")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_carrier_schedule_node_fact")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_carrier_schedule_exception_fact")
|
||||
.contains("attempt_count BETWEEN 0 AND 3")
|
||||
.contains("window_attempt_no BETWEEN 1 AND 3")
|
||||
.contains("累计自动查询调用次数")
|
||||
.contains("下一个09:00、12:30或16:00固定查询槽位")
|
||||
.doesNotContain("attempt_count BETWEEN 0 AND 3")
|
||||
.doesNotContain("window_attempt_no BETWEEN 1 AND 3")
|
||||
.contains("UNIQUE KEY uk_carrier_schedule_operation (operator_id,idempotency_key)")
|
||||
.contains("船司查询尝试不可修改")
|
||||
.contains("船司查询历史不可删除")
|
||||
@@ -61,6 +63,10 @@ class CarrierScheduleQuerySqlContractTest {
|
||||
.contains("uk_carrier_schedule_query_scope")
|
||||
.contains("id IN (5102184,5102186,5102188)")
|
||||
.contains("assert_carrier_schedule_query_schema")
|
||||
.contains("migrate_carrier_schedule_fixed_slots")
|
||||
.contains("DROP CHECK chk_carrier_schedule_query_attempt")
|
||||
.contains("DROP CHECK chk_carrier_schedule_attempt_no")
|
||||
.contains("SET @carrier_schedule_now = CONVERT_TZ(UTC_TIMESTAMP(3),'+00:00','+08:00')")
|
||||
.contains("'PUBLIC_FORM','PUBLIC_WEB_PROXY','PUBLIC_HTML','HCAPTCHA','OFFICIAL_API'")
|
||||
.contains("船司查询任务角色授权未完整创建")
|
||||
.doesNotContain("'platform:firstleg:port-schedule-query:attempt','执行船司查询'");
|
||||
|
||||
+67
@@ -4,18 +4,81 @@ import com.baiyue.firstleg.client.CarrierScheduleIntegrationClient;
|
||||
import com.baiyue.firstleg.portschedule.entity.CarrierScheduleQueryEntity;
|
||||
import com.baiyue.firstleg.portschedule.repository.CarrierScheduleQueryRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class CarrierScheduleQuerySchedulerTest {
|
||||
@Test
|
||||
void declaresExactlyThreeConfigurableShanghaiCronTriggers() throws Exception {
|
||||
Scheduled[] schedules = CarrierScheduleQueryScheduler.class.getMethod("poll").getAnnotationsByType(Scheduled.class);
|
||||
assertThat(schedules).hasSize(3);
|
||||
assertThat(schedules).extracting(Scheduled::cron).containsExactlyInAnyOrder(
|
||||
"${baiyue.carrier-schedule.scheduler.cron-morning:0 0 9 * * *}",
|
||||
"${baiyue.carrier-schedule.scheduler.cron-noon:0 30 12 * * *}",
|
||||
"${baiyue.carrier-schedule.scheduler.cron-afternoon:0 0 16 * * *}");
|
||||
assertThat(schedules).extracting(Scheduled::zone)
|
||||
.containsOnly("${baiyue.carrier-schedule.scheduler.zone:Asia/Shanghai}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void drainsControlledBatchesAndStopsAtThePerTriggerSafetyLimit() {
|
||||
CarrierScheduleQueryRepository repository = mock(CarrierScheduleQueryRepository.class);
|
||||
CarrierScheduleQueryScheduler scheduler = spy(new CarrierScheduleQueryScheduler(repository,
|
||||
mock(CarrierScheduleQueryService.class), mock(CarrierScheduleIntegrationClient.class)));
|
||||
CarrierScheduleQueryEntity first = task(1L); CarrierScheduleQueryEntity second = task(2L);
|
||||
CarrierScheduleQueryEntity third = task(3L);
|
||||
when(repository.due(any(), anyInt())).thenReturn(List.of(first, second), List.of(third));
|
||||
doNothing().when(scheduler).execute(any(), any());
|
||||
ReflectionTestUtils.setField(scheduler, "batchSize", 2);
|
||||
ReflectionTestUtils.setField(scheduler, "maxTasksPerTrigger", 3);
|
||||
ReflectionTestUtils.setField(scheduler, "scheduleZone", "Asia/Shanghai");
|
||||
ReflectionTestUtils.setField(scheduler, "clock",
|
||||
Clock.fixed(Instant.parse("2026-08-21T01:00:00Z"), ZoneOffset.UTC));
|
||||
|
||||
scheduler.poll();
|
||||
|
||||
verify(repository).due(LocalDateTime.of(2026, 8, 21, 9, 0), 2);
|
||||
verify(repository).due(LocalDateTime.of(2026, 8, 21, 9, 0), 1);
|
||||
verify(scheduler, times(3)).execute(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsClaimingControlledBatchesUntilNoDueTasksRemain() {
|
||||
CarrierScheduleQueryRepository repository = mock(CarrierScheduleQueryRepository.class);
|
||||
CarrierScheduleQueryScheduler scheduler = spy(new CarrierScheduleQueryScheduler(repository,
|
||||
mock(CarrierScheduleQueryService.class), mock(CarrierScheduleIntegrationClient.class)));
|
||||
when(repository.due(any(), anyInt())).thenReturn(List.of(task(1L), task(2L)), List.of());
|
||||
doNothing().when(scheduler).execute(any(), any());
|
||||
ReflectionTestUtils.setField(scheduler, "batchSize", 2);
|
||||
ReflectionTestUtils.setField(scheduler, "maxTasksPerTrigger", 10);
|
||||
ReflectionTestUtils.setField(scheduler, "scheduleZone", "Asia/Shanghai");
|
||||
ReflectionTestUtils.setField(scheduler, "clock",
|
||||
Clock.fixed(Instant.parse("2026-08-21T04:30:00Z"), ZoneOffset.UTC));
|
||||
|
||||
scheduler.poll();
|
||||
|
||||
verify(repository, times(2)).due(LocalDateTime.of(2026, 8, 21, 12, 30), 2);
|
||||
verify(scheduler, times(2)).execute(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void leaseWinnerPersistsStandardNodesAndCompletesAttempt() {
|
||||
CarrierScheduleQueryRepository repository = mock(CarrierScheduleQueryRepository.class);
|
||||
@@ -42,4 +105,8 @@ class CarrierScheduleQuerySchedulerTest {
|
||||
verify(service).applyProviderException(anyLong(), any());
|
||||
verify(service).completeScheduledAttempt(1L, "SUCCESS", null, null);
|
||||
}
|
||||
|
||||
private static CarrierScheduleQueryEntity task(long id) {
|
||||
CarrierScheduleQueryEntity value = new CarrierScheduleQueryEntity(); value.setId(id); return value;
|
||||
}
|
||||
}
|
||||
|
||||
+29
-2
@@ -71,7 +71,9 @@ class CarrierScheduleQueryServiceTest {
|
||||
assertThat(result.query().query().carrierCode()).isEqualTo("ZIM");
|
||||
assertThat(result.query().query().status()).isEqualTo("READY");
|
||||
assertThat(result.query().expired()).isTrue();
|
||||
verify(repository).insert(any(CarrierScheduleQueryEntity.class));
|
||||
ArgumentCaptor<CarrierScheduleQueryEntity> created = ArgumentCaptor.forClass(CarrierScheduleQueryEntity.class);
|
||||
verify(repository).insert(created.capture());
|
||||
assertThat(created.getValue().getNextAllowedAt()).isEqualTo(LocalDateTime.of(2026, 8, 10, 12, 30));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -90,6 +92,7 @@ class CarrierScheduleQueryServiceTest {
|
||||
assertThat(result.getCarrierCode()).isEqualTo("COSCO");
|
||||
assertThat(result.getTrackingType()).isEqualTo("CONTAINER_NO");
|
||||
assertThat(result.getLatestStandardNode()).isEqualTo("DEPARTED_WAREHOUSE");
|
||||
assertThat(result.getNextAllowedAt()).isEqualTo(LocalDateTime.of(2026, 8, 10, 12, 30));
|
||||
verify(repository).insertIfAbsent(any(com.baiyue.firstleg.portschedule.entity.CarrierScheduleNodeFactEntity.class));
|
||||
}
|
||||
|
||||
@@ -212,7 +215,31 @@ class CarrierScheduleQueryServiceTest {
|
||||
verify(repository).insert(attempt.capture());
|
||||
assertThat(attempt.getValue().getAttemptStatus()).isEqualTo("NO_CHANGE");
|
||||
assertThat(attempt.getValue().getOperatorName()).isEqualTo("carrier-scheduler");
|
||||
assertThat(attempt.getValue().getWindowAttemptNo()).isEqualTo(1);
|
||||
assertThat(task.getStatus()).isEqualTo("READY");
|
||||
assertThat(task.getNextAllowedAt()).isEqualTo(LocalDateTime.of(2026, 8, 10, 12, 30));
|
||||
}
|
||||
|
||||
@Test
|
||||
void everySchedulerOutcomeUsesTheNextFixedSlotWithoutExtraRetryTimes() {
|
||||
List<String> outcomes = List.of("SUCCESS", "NO_CHANGE", "MANUAL_REQUIRED", "RETRYABLE_FAILURE",
|
||||
"PERMANENT_FAILURE");
|
||||
List<CarrierScheduleQueryEntity> tasks = outcomes.stream().map(value -> task(0, 0)).toList();
|
||||
when(repository.lock(1L)).thenReturn(tasks.get(0), tasks.get(1), tasks.get(2), tasks.get(3), tasks.get(4));
|
||||
when(repository.find(1L)).thenReturn(tasks.get(0), tasks.get(1), tasks.get(2), tasks.get(3), tasks.get(4));
|
||||
when(repository.updateState(any(), any(Integer.class), anyLong(), anyString())).thenReturn(1);
|
||||
|
||||
outcomes.forEach(outcome -> service.completeScheduledAttempt(1L, outcome, null, null));
|
||||
|
||||
assertThat(tasks).allSatisfy(value -> {
|
||||
assertThat(value.getNextAllowedAt()).isEqualTo(LocalDateTime.of(2026, 8, 10, 12, 30));
|
||||
assertThat(value.getAttemptCount()).isEqualTo(1);
|
||||
});
|
||||
assertThat(tasks.get(0).getStatus()).isEqualTo("READY");
|
||||
assertThat(tasks.get(1).getStatus()).isEqualTo("READY");
|
||||
assertThat(tasks.get(2).getStatus()).isEqualTo("MANUAL_REQUIRED");
|
||||
assertThat(tasks.get(3).getStatus()).isEqualTo("READY");
|
||||
assertThat(tasks.get(4).getStatus()).isEqualTo("MANUAL_REQUIRED");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -291,7 +318,7 @@ class CarrierScheduleQueryServiceTest {
|
||||
value.setId(1L); value.setQueryNo("CSQ1"); value.setCustomerId(10001L);
|
||||
value.setOceanBookingId(10L); value.setContainerBookingId(20L); value.setCarrierCode("ZIM");
|
||||
value.setTrackingType("BILL_OF_LADING"); value.setTrackingNo("GAOU6898053");
|
||||
value.setOfficialUrl("https://www.zimchina.com/tools/track-a-shipment"); value.setAccessMode("HCAPTCHA");
|
||||
value.setOfficialUrl("https://api.zim.com/"); value.setAccessMode("OFFICIAL_API");
|
||||
value.setStatus("MANUAL_REQUIRED"); value.setWindowStartedAt(NOW.minusDays(1));
|
||||
value.setAttemptCount(attempts); value.setNextAllowedAt(NOW); value.setVersion(version);
|
||||
return value;
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.baiyue.firstleg.portschedule.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CarrierScheduleQuerySlotsTest {
|
||||
@Test
|
||||
void returnsTheFirstStrictlyLaterShanghaiSlotAtExactBoundaries() {
|
||||
assertThat(CarrierScheduleQuerySlots.nextSlot(at(8, 59, 59))).isEqualTo(at(9, 0, 0));
|
||||
assertThat(CarrierScheduleQuerySlots.nextSlot(at(9, 0, 0))).isEqualTo(at(12, 30, 0));
|
||||
assertThat(CarrierScheduleQuerySlots.nextSlot(at(12, 29, 59))).isEqualTo(at(12, 30, 0));
|
||||
assertThat(CarrierScheduleQuerySlots.nextSlot(at(12, 30, 0))).isEqualTo(at(16, 0, 0));
|
||||
assertThat(CarrierScheduleQuerySlots.nextSlot(at(15, 59, 59))).isEqualTo(at(16, 0, 0));
|
||||
assertThat(CarrierScheduleQuerySlots.nextSlot(at(16, 0, 0)))
|
||||
.isEqualTo(LocalDateTime.of(2026, 8, 22, 9, 0));
|
||||
}
|
||||
|
||||
private static LocalDateTime at(int hour, int minute, int second) {
|
||||
return LocalDateTime.of(2026, 8, 21, hour, minute, second);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user