This commit is contained in:
2026-08-20 18:02:38 +08:00
parent b55b049e77
commit 1cd8b6a473
210 changed files with 6974 additions and 825 deletions
@@ -0,0 +1,24 @@
package com.baiyue.lastleg.masterdata;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class OverseasMasterDataOpenApiContractTest {
@Test
void documentsExchangeRateAndVehicleApisWithoutDeliveryFulfillment() throws Exception {
String yaml = Files.readString(Path.of("src/main/resources/openapi/overseas-master-data-v1.yaml"));
assertThat(yaml).contains("/api/platform/v1/last-leg/master-data/overseas-quotes:")
.contains("缓存合作仓目录30分钟")
.contains("/api/platform/v1/last-leg/master-data/exchange-rates:")
.contains("/api/platform/v1/last-leg/master-data/vehicles:")
.contains("platform:overseas-master-data:exchange-rate:export")
.contains("platform:overseas-master-data:vehicle:status")
.contains("不包含派送履约或车辆更换历史")
.doesNotContain("deliveryOrders:")
.doesNotContain("replacementHistory:");
}
}
@@ -0,0 +1,19 @@
package com.baiyue.lastleg.masterdata.exchange;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class MonthlyExchangeRateSqlContractTest {
@Test
void baselineContainsRateAndHistoryTablesWithOptimisticLock() throws Exception {
String sql = Files.readString(Path.of("src/main/resources/db/lastleg.sql"));
assertThat(sql).contains("CREATE TABLE IF NOT EXISTS ll_monthly_exchange_rate (")
.contains("UNIQUE KEY uk_ll_exchange_rate_month (rate_month)")
.contains("record_version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本'")
.contains("CREATE TABLE IF NOT EXISTS ll_monthly_exchange_rate_history (");
}
}
@@ -0,0 +1,29 @@
package com.baiyue.lastleg.masterdata.exchange.controller;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
class PlatformMonthlyExchangeRateControllerContractTest {
@Test
void exposesStablePlatformBaseAndCanonicalAuthorities() {
RequestMapping base = PlatformMonthlyExchangeRateController.class.getAnnotation(RequestMapping.class);
assertThat(base.value()).containsExactly("/api/platform/v1/last-leg/master-data/exchange-rates");
Map<String, String> authorities = Stream.of(PlatformMonthlyExchangeRateController.class.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(PreAuthorize.class))
.collect(Collectors.toMap(method -> method.getName(), method -> method.getAnnotation(PreAuthorize.class).value()));
assertThat(authorities.get("page")).contains("platform:overseas-master-data:exchange-rate:list");
assertThat(authorities.get("summary")).contains("platform:overseas-master-data:exchange-rate:list");
assertThat(authorities.get("detail")).contains("platform:overseas-master-data:exchange-rate:list");
assertThat(authorities.get("create")).contains("platform:overseas-master-data:exchange-rate:create");
assertThat(authorities.get("update")).contains("platform:overseas-master-data:exchange-rate:edit");
assertThat(authorities.get("status")).contains("platform:overseas-master-data:exchange-rate:status");
assertThat(authorities.get("export")).contains("platform:overseas-master-data:exchange-rate:export");
}
}
@@ -0,0 +1,39 @@
package com.baiyue.lastleg.masterdata.exchange.service;
import com.baiyue.lastleg.masterdata.exchange.dto.MonthlyExchangeRateDtos;
import com.baiyue.lastleg.masterdata.exchange.entity.MonthlyExchangeRate;
import com.baiyue.lastleg.masterdata.exchange.mapper.MonthlyExchangeRateHistoryMapper;
import com.baiyue.lastleg.masterdata.exchange.mapper.MonthlyExchangeRateMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class MonthlyExchangeRateServiceTest {
private MonthlyExchangeRateMapper mapper;
private MonthlyExchangeRateService service;
@BeforeEach
void setUp() {
mapper = mock(MonthlyExchangeRateMapper.class);
service = new MonthlyExchangeRateService();
ReflectionTestUtils.setField(service, "rates", mapper);
ReflectionTestUtils.setField(service, "histories", mock(MonthlyExchangeRateHistoryMapper.class));
}
@Test
void rejectsStaleVersion() {
MonthlyExchangeRate value = new MonthlyExchangeRate(); value.setRecordVersion(4L); value.setRateMonth("2026-08");
when(mapper.lockMonth("2026-08")).thenReturn(value);
var request = new MonthlyExchangeRateDtos.Update(3L, new BigDecimal("7.18000000"), null);
assertThatThrownBy(() -> service.update("2026-08", "9", request))
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409 CONFLICT");
}
}
@@ -0,0 +1,30 @@
package com.baiyue.lastleg.masterdata.quote.controller;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
class PlatformOverseasQuoteControllerContractTest {
@Test
void exposesStablePlatformBaseAndCanonicalAuthorities() {
RequestMapping base = PlatformOverseasQuoteController.class.getAnnotation(RequestMapping.class);
assertThat(base.value()).containsExactly("/api/platform/v1/last-leg/master-data/overseas-quotes");
Map<String, String> authorities = Stream.of(PlatformOverseasQuoteController.class.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(PreAuthorize.class))
.collect(Collectors.toMap(method -> method.getName(),
method -> method.getAnnotation(PreAuthorize.class).value()));
assertThat(authorities.get("page")).contains("platform:overseas-master-data:quote:list");
assertThat(authorities.get("detail")).contains("platform:overseas-master-data:quote:list");
assertThat(authorities.get("warehouseOptions")).contains("platform:overseas-master-data:quote:list");
assertThat(authorities.get("create")).contains("platform:overseas-master-data:quote:create");
assertThat(authorities.get("update")).contains("platform:overseas-master-data:quote:edit");
assertThat(authorities.get("status")).contains("platform:overseas-master-data:quote:status");
assertThat(authorities.get("export")).contains("platform:overseas-master-data:quote:export");
}
}
@@ -0,0 +1,56 @@
package com.baiyue.lastleg.masterdata.quote.service;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.lastleg.masterdata.quote.client.DomesticPartnerWarehouseClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class PartnerWarehouseDirectoryTest {
@Mock private DomesticPartnerWarehouseClient client;
private PartnerWarehouseDirectory directory;
@BeforeEach
void setUp() {
directory = new PartnerWarehouseDirectory();
ReflectionTestUtils.setField(directory, "client", client);
ReflectionTestUtils.setField(directory, "internalToken", "internal-token");
ReflectionTestUtils.setField(directory, "ttl", Duration.ofMinutes(30));
ReflectionTestUtils.setField(directory, "clock", Clock.fixed(
Instant.parse("2026-08-20T00:00:00Z"), ZoneOffset.UTC));
}
@Test
void cachesDomesticOptionsForThirtyMinutesAndReturnsIdsAsStrings() {
when(client.activePartners("internal-token")).thenReturn(ApiResponse.success(List.of(
new DomesticPartnerWarehouseClient.Option(Long.valueOf("9007199254740993"),
"US-LAX", "洛杉矶合作仓", "US"))));
assertThat(directory.options()).singleElement().satisfies(option -> {
assertThat(option.warehouseId()).isEqualTo("9007199254740993");
assertThat(option.warehouseCode()).isEqualTo("US-LAX");
assertThat(option.warehouseName()).isEqualTo("洛杉矶合作仓");
assertThat(option.countryCode()).isEqualTo("US");
});
directory.options();
verify(client, times(1)).activePartners("internal-token");
ReflectionTestUtils.setField(directory, "clock", Clock.fixed(
Instant.parse("2026-08-20T00:30:01Z"), ZoneOffset.UTC));
directory.options();
verify(client, times(2)).activePartners("internal-token");
}
}
@@ -0,0 +1,21 @@
package com.baiyue.lastleg.masterdata.vehicle;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class VehicleSqlContractTest {
@Test
void baselineContainsVehicleMasterDataWithoutDeliveryHistory() throws Exception {
String sql = Files.readString(Path.of("src/main/resources/db/lastleg.sql"));
String vehicleSql = sql.substring(sql.indexOf("CREATE TABLE IF NOT EXISTS ll_vehicle ("));
assertThat(vehicleSql).contains("UNIQUE KEY uk_ll_vehicle_plate (plate_number)")
.contains("record_version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本'")
.contains("ownership VARCHAR(16) NOT NULL")
.doesNotContain("CREATE TABLE IF NOT EXISTS ll_vehicle_delivery")
.doesNotContain("CREATE TABLE IF NOT EXISTS ll_vehicle_replacement");
}
}
@@ -0,0 +1,28 @@
package com.baiyue.lastleg.masterdata.vehicle.controller;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
class PlatformVehicleControllerContractTest {
@Test
void exposesStablePlatformBaseAndCanonicalAuthorities() {
RequestMapping base = PlatformVehicleController.class.getAnnotation(RequestMapping.class);
assertThat(base.value()).containsExactly("/api/platform/v1/last-leg/master-data/vehicles");
Map<String, String> authorities = Stream.of(PlatformVehicleController.class.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(PreAuthorize.class))
.collect(Collectors.toMap(method -> method.getName(), method -> method.getAnnotation(PreAuthorize.class).value()));
assertThat(authorities.get("page")).contains("platform:overseas-master-data:vehicle:list");
assertThat(authorities.get("detail")).contains("platform:overseas-master-data:vehicle:list");
assertThat(authorities.get("create")).contains("platform:overseas-master-data:vehicle:create");
assertThat(authorities.get("update")).contains("platform:overseas-master-data:vehicle:edit");
assertThat(authorities.get("status")).contains("platform:overseas-master-data:vehicle:status");
assertThat(authorities.get("export")).contains("platform:overseas-master-data:vehicle:export");
}
}
@@ -0,0 +1,63 @@
package com.baiyue.lastleg.masterdata.vehicle.service;
import com.baiyue.lastleg.masterdata.vehicle.dto.VehicleDtos;
import com.baiyue.lastleg.masterdata.vehicle.entity.Vehicle;
import com.baiyue.lastleg.masterdata.vehicle.mapper.VehicleMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class VehicleServiceTest {
private VehicleMapper mapper;
private VehicleService service;
@BeforeEach
void setUp() {
mapper = mock(VehicleMapper.class);
service = new VehicleService();
ReflectionTestUtils.setField(service, "vehicles", mapper);
}
@Test
void rejectsStaleVersionBeforeUpdating() {
when(mapper.lockById(1L)).thenReturn(vehicle(3L, 1000L));
VehicleDtos.Update request = update(2L, 1000L);
assertThatThrownBy(() -> service.update(1L, "9", request))
.isInstanceOf(ResponseStatusException.class)
.hasMessageContaining("409 CONFLICT");
}
@Test
void rejectsDecreasingOdometer() {
when(mapper.lockById(1L)).thenReturn(vehicle(3L, 1000L));
VehicleDtos.Update request = update(3L, 999L);
assertThatThrownBy(() -> service.update(1L, "9", request))
.isInstanceOf(ResponseStatusException.class)
.hasMessageContaining("400 BAD_REQUEST");
}
@Test
void rejectsUnknownStatus() {
when(mapper.lockById(1L)).thenReturn(vehicle(3L, 1000L));
assertThatThrownBy(() -> service.changeStatus(1L, "9", new VehicleDtos.ChangeStatus("BUSY", 3L)))
.isInstanceOf(ResponseStatusException.class)
.hasMessageContaining("400 BAD_REQUEST");
}
private static VehicleDtos.Update update(long version, long mileage) {
return new VehicleDtos.Update(version, "ABC-001", "Box Truck", new BigDecimal("12.50"), mileage,
"Driver", "123", "OVERSEAS", "OWNED", null);
}
private static Vehicle vehicle(long version, long mileage) {
Vehicle value = new Vehicle(); value.setId(1L); value.setRecordVersion(version); value.setMileageKm(mileage);
value.setStatus("IN_SERVICE"); return value;
}
}