优化
This commit is contained in:
@@ -3,13 +3,30 @@ package com.baiyue.firstleg.controller;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("FBA计费OpenAPI契约")
|
||||
class FbaPricingContractTest {
|
||||
@Test
|
||||
@DisplayName("只有客户规则列表允许省略企业参数")
|
||||
void onlyCustomerRuleListAllowsOptionalTenant() {
|
||||
Method rules = method("rules");
|
||||
assertThat(rules.getParameterTypes()[1]).isEqualTo(Long.class);
|
||||
assertThat(rules.getParameters()[1].getAnnotation(RequestParam.class).required()).isFalse();
|
||||
|
||||
for (String scopedMethod : new String[] {"rule", "createRule", "retireRule", "rates",
|
||||
"rate", "createRate", "retireRate"}) {
|
||||
Method method = method(scopedMethod);
|
||||
assertThat(method.getParameterTypes()[1]).isEqualTo(long.class);
|
||||
assertThat(method.getParameters()[1].getAnnotation(RequestParam.class).required()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("契约声明自动发布、已发布费用调整和幂等头")
|
||||
void documentsPricingLifecycle() throws Exception {
|
||||
@@ -28,5 +45,26 @@ class FbaPricingContractTest {
|
||||
.doesNotContain("应收")
|
||||
.doesNotContain("应付")
|
||||
.doesNotContain("待客户确认");
|
||||
String customerRules = openapi.substring(
|
||||
openapi.indexOf("/api/platform/v1/fba-pricing/customer-rules:"),
|
||||
openapi.indexOf("/api/platform/v1/fba-pricing/customer-rules/versions:"));
|
||||
assertThat(customerRules)
|
||||
.contains("OptionalTenantId")
|
||||
.contains("name: ruleCode")
|
||||
.doesNotContain("#/components/parameters/Page")
|
||||
.doesNotContain("#/components/parameters/Size")
|
||||
.doesNotContain("name: customerName")
|
||||
.doesNotContain("name: chargeMode")
|
||||
.doesNotContain("name: status");
|
||||
assertThat(openapi)
|
||||
.contains("OptionalTenantId: {name: tenantId, in: query, required: false")
|
||||
.contains("schema: {type: string, pattern: '^[1-9][0-9]*$'}")
|
||||
.doesNotContain("format: int64");
|
||||
}
|
||||
|
||||
private static Method method(String name) {
|
||||
return java.util.Arrays.stream(PlatformFbaMasterDataController.class.getDeclaredMethods())
|
||||
.filter(candidate -> candidate.getName().equals(name))
|
||||
.findFirst().orElseThrow();
|
||||
}
|
||||
}
|
||||
|
||||
+21
-1
@@ -5,6 +5,7 @@ import com.baiyue.firstleg.dto.InboundOrderQuery;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -15,21 +16,40 @@ class InboundOrderControllerContractTest {
|
||||
@Test
|
||||
void shouldDeclareReadOnlyPermissions() throws Exception {
|
||||
assertAuthority("page", "hasAuthority('platform:firstleg:inbound:list')",
|
||||
AuthenticatedUser.class, long.class, InboundOrderQuery.class);
|
||||
AuthenticatedUser.class, Long.class, InboundOrderQuery.class);
|
||||
assertAuthority("detail", "hasAuthority('platform:firstleg:inbound:view')",
|
||||
AuthenticatedUser.class, long.class, long.class);
|
||||
|
||||
Method page = PlatformInboundOrderController.class.getDeclaredMethod(
|
||||
"page", AuthenticatedUser.class, Long.class, InboundOrderQuery.class);
|
||||
assertThat(page.getParameters()[1].getAnnotation(RequestParam.class).required()).isFalse();
|
||||
Method detail = PlatformInboundOrderController.class.getDeclaredMethod(
|
||||
"detail", AuthenticatedUser.class, long.class, long.class);
|
||||
assertThat(detail.getParameters()[1].getAnnotation(RequestParam.class).required()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDocumentOnlyV2TabsAndStringLongIds() throws Exception {
|
||||
String openapi = new ClassPathResource("openapi/firstleg-v1.yaml")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
String inboundList = openapi.substring(
|
||||
openapi.indexOf("/api/platform/inbounds:"),
|
||||
openapi.indexOf("/api/platform/inbounds/{inboundId}:"));
|
||||
String taskList = openapi.substring(
|
||||
openapi.indexOf("/api/platform/v1/first-leg/tasks:"),
|
||||
openapi.indexOf("/api/platform/v1/first-leg/tasks/{taskId}:"));
|
||||
assertThat(openapi)
|
||||
.contains("/api/platform/inbounds:")
|
||||
.contains("/api/platform/inbounds/{inboundId}:")
|
||||
.contains("required: false")
|
||||
.contains("InboundOrderSummary:")
|
||||
.contains("required: [id, tenantId]")
|
||||
.contains("tenantId: {type: string, pattern: '^[1-9][0-9]*$', description:")
|
||||
.contains("[WAIT_MEASURE, WAIT_REVIEW, WAIT_FEE_SEND, INBOUNDED]")
|
||||
.contains("platform:firstleg:inbound:list")
|
||||
.doesNotContain("WAIT_CUSTOMER_CONFIRM");
|
||||
assertThat(inboundList).contains("#/components/schemas/InboundOrderPageEnvelope");
|
||||
assertThat(taskList).doesNotContain("InboundOrderPageEnvelope");
|
||||
}
|
||||
|
||||
private static void assertAuthority(String methodName, String value, Class<?>... types)
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baiyue.firstleg.entity.CustomerChargeRuleEntity;
|
||||
import com.baiyue.firstleg.mapper.CarrierFbaRateMapper;
|
||||
import com.baiyue.firstleg.mapper.CustomerChargeRuleMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundFeeItemMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundFeeMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundOutboxMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundPricingIdempotencyMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundPricingMapper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class FbaPricingRepositoryRuleListTest {
|
||||
@BeforeAll
|
||||
static void initializeEntityMetadata() {
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"),
|
||||
CustomerChargeRuleEntity.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformRuleListOmitsTenantConditionWhenTenantIsAbsent() {
|
||||
assertThat(queryValues(null, null)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformRuleListKeepsTenantAndRuleCodeFiltersWhenProvided() {
|
||||
assertThat(queryValues(10001L, "CR-1")).containsExactlyInAnyOrder(10001L, "CR-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void internalVersionCalculationKeepsTenantAndRuleCodeFilters() {
|
||||
CustomerChargeRuleMapper rules = mock(CustomerChargeRuleMapper.class);
|
||||
CustomerChargeRuleEntity current = new CustomerChargeRuleEntity();
|
||||
current.setRuleVersion(3);
|
||||
when(rules.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of(current));
|
||||
FbaPricingRepository repository = repository(rules);
|
||||
|
||||
int result = repository.nextRuleVersion(10001L, "CR-1");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<LambdaQueryWrapper<CustomerChargeRuleEntity>> wrapper =
|
||||
ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
||||
verify(rules).selectList(wrapper.capture());
|
||||
wrapper.getValue().getSqlSegment();
|
||||
assertThat(wrapper.getValue().getParamNameValuePairs().values())
|
||||
.containsExactlyInAnyOrder(10001L, "CR-1");
|
||||
assertThat(result).isEqualTo(4);
|
||||
}
|
||||
|
||||
private static Collection<Object> queryValues(Long tenantId, String ruleCode) {
|
||||
CustomerChargeRuleMapper rules = mock(CustomerChargeRuleMapper.class);
|
||||
when(rules.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of());
|
||||
FbaPricingRepository repository = repository(rules);
|
||||
|
||||
repository.listRules(tenantId, ruleCode);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<LambdaQueryWrapper<CustomerChargeRuleEntity>> wrapper =
|
||||
ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
||||
verify(rules).selectList(wrapper.capture());
|
||||
wrapper.getValue().getSqlSegment();
|
||||
return wrapper.getValue().getParamNameValuePairs().values();
|
||||
}
|
||||
|
||||
private static FbaPricingRepository repository(CustomerChargeRuleMapper rules) {
|
||||
return new FbaPricingRepository(rules, mock(CarrierFbaRateMapper.class),
|
||||
mock(InboundPricingMapper.class), mock(InboundFeeMapper.class), mock(InboundFeeItemMapper.class),
|
||||
mock(InboundPricingIdempotencyMapper.class), mock(InboundOutboxMapper.class));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.firstleg.dto.InboundOrderQuery;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.entity.InboundPalletAllocationEntity;
|
||||
import com.baiyue.firstleg.entity.InboundTransportSnapshotEntity;
|
||||
import com.baiyue.firstleg.mapper.InboundFileMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundInternalNoteMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundOrderMapper;
|
||||
@@ -73,8 +74,10 @@ class InboundOrderRepositoryTest {
|
||||
Page<InboundOrderEntity> page = new Page<>(1, 20);
|
||||
InboundOrderEntity first = new InboundOrderEntity();
|
||||
first.setId(1L);
|
||||
first.setTenantId(10001L);
|
||||
InboundOrderEntity second = new InboundOrderEntity();
|
||||
second.setId(2L);
|
||||
second.setTenantId(10001L);
|
||||
page.setRecords(java.util.List.of(first, second));
|
||||
when(orders.selectPage(any(Page.class), any(Wrapper.class))).thenReturn(page);
|
||||
when(pallets.selectList(any(Wrapper.class))).thenReturn(java.util.List.of(
|
||||
@@ -104,12 +107,85 @@ class InboundOrderRepositoryTest {
|
||||
.contains("ORDER BY inbound_id ASC,allocation_no ASC");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
void shouldQueryAndAggregateCrossTenantPageByInboundOwnership() {
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), "test"), InboundOrderEntity.class);
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), "test"),
|
||||
InboundTransportSnapshotEntity.class);
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), "test"),
|
||||
InboundPalletAllocationEntity.class);
|
||||
InboundOrderMapper orders = mock(InboundOrderMapper.class);
|
||||
InboundTransportSnapshotMapper transports = mock(InboundTransportSnapshotMapper.class);
|
||||
InboundPalletAllocationMapper pallets = mock(InboundPalletAllocationMapper.class);
|
||||
Page<InboundOrderEntity> page = new Page<>(1, 20);
|
||||
page.setRecords(java.util.List.of(order(1L, 10001L), order(2L, 10002L)));
|
||||
when(orders.selectPage(any(Page.class), any(Wrapper.class))).thenReturn(page);
|
||||
when(transports.selectList(any(Wrapper.class))).thenReturn(java.util.List.of(
|
||||
transport(1L, 10001L, "T1"),
|
||||
transport(2L, 10002L, "T2"),
|
||||
transport(1L, 10002L, "WRONG")));
|
||||
when(pallets.selectList(any(Wrapper.class))).thenReturn(java.util.List.of(
|
||||
allocation(1L, 10001L, 1, "A1"),
|
||||
allocation(2L, 10002L, 1, "B1"),
|
||||
allocation(1L, 10002L, 2, "WRONG")));
|
||||
InboundOrderRepository repository = new InboundOrderRepository(
|
||||
orders, transports, pallets, mock(InboundFileMapper.class),
|
||||
mock(InboundInternalNoteMapper.class), mock(InboundStatusHistoryMapper.class));
|
||||
|
||||
var result = repository.page(null, new InboundOrderQuery(
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, 1, 20));
|
||||
|
||||
assertThat(result.records()).extracting(record -> record.tenantId())
|
||||
.containsExactly(10001L, 10002L);
|
||||
assertThat(result.records()).extracting(record -> record.loadingStatus())
|
||||
.containsExactly("T1", "T2");
|
||||
assertThat(result.records()).extracting(record -> record.locationCodes())
|
||||
.containsExactly("A1", "B1");
|
||||
ArgumentCaptor<Wrapper<InboundOrderEntity>> orderCaptor = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(orders).selectPage(any(Page.class), orderCaptor.capture());
|
||||
assertThat(orderCaptor.getValue().getCustomSqlSegment()).doesNotContain("tenant_id");
|
||||
|
||||
ArgumentCaptor<Wrapper<InboundTransportSnapshotEntity>> transportCaptor =
|
||||
ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(transports).selectList(transportCaptor.capture());
|
||||
assertThat(transportCaptor.getValue().getCustomSqlSegment())
|
||||
.doesNotContain("tenant_id")
|
||||
.contains("inbound_id IN");
|
||||
}
|
||||
|
||||
private static InboundPalletAllocationEntity allocation(
|
||||
long inboundId, int allocationNo, String locationCode) {
|
||||
return allocation(inboundId, 10001L, allocationNo, locationCode);
|
||||
}
|
||||
|
||||
private static InboundPalletAllocationEntity allocation(
|
||||
long inboundId, long tenantId, int allocationNo, String locationCode) {
|
||||
InboundPalletAllocationEntity entity = new InboundPalletAllocationEntity();
|
||||
entity.setInboundId(inboundId);
|
||||
entity.setTenantId(tenantId);
|
||||
entity.setAllocationNo(allocationNo);
|
||||
entity.setLocationCode(locationCode);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static InboundOrderEntity order(long id, long tenantId) {
|
||||
InboundOrderEntity entity = new InboundOrderEntity();
|
||||
entity.setId(id);
|
||||
entity.setTenantId(tenantId);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static InboundTransportSnapshotEntity transport(
|
||||
long inboundId, long tenantId, String loadingStatus) {
|
||||
InboundTransportSnapshotEntity entity = new InboundTransportSnapshotEntity();
|
||||
entity.setInboundId(inboundId);
|
||||
entity.setTenantId(tenantId);
|
||||
entity.setLoadingStatus(loadingStatus);
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,27 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("FBA计费基础资料版本")
|
||||
class FbaMasterDataServiceTest {
|
||||
private static final LocalDateTime AUGUST = LocalDateTime.of(2026, 8, 1, 0, 0);
|
||||
|
||||
@Test
|
||||
@DisplayName("平台列表不传企业时委托仓储查询全部企业规则")
|
||||
void listsRulesAcrossTenantsWithoutTenantFilter() {
|
||||
FbaPricingRepository repository = mock(FbaPricingRepository.class);
|
||||
List<CustomerChargeRuleEntity> expected = List.of(activeRule(AUGUST, null));
|
||||
when(repository.listRules(null, "CR-1")).thenReturn(expected);
|
||||
|
||||
List<CustomerChargeRuleEntity> result =
|
||||
new FbaMasterDataService(repository).listRules(null, "CR-1");
|
||||
|
||||
assertThat(result).isSameAs(expected);
|
||||
verify(repository).listRules(null, "CR-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("半开有效期首尾相接不视为重叠")
|
||||
void acceptsAdjacentHalfOpenPeriods() {
|
||||
|
||||
@@ -21,6 +21,17 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class InboundOrderQueryServiceTest {
|
||||
@Test
|
||||
void shouldQueryAllPlatformTenantsWhenTenantFilterIsMissing() {
|
||||
InboundOrderRepository repository = mock(InboundOrderRepository.class);
|
||||
InboundOrderQuery query = query(null, null, null, null);
|
||||
PageResult<InboundOrderSummary> expected = new PageResult<>(List.of(), 1, 20, 0, 0);
|
||||
when(repository.page(null, query)).thenReturn(expected);
|
||||
|
||||
assertThat(new InboundOrderQueryService(repository).page(null, query)).isSameAs(expected);
|
||||
verify(repository).page(null, query);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepV2TabMappingWithoutCustomerConfirmation() {
|
||||
assertThat(InboundListTab.WAIT_MEASURE.processStatuses())
|
||||
|
||||
Reference in New Issue
Block a user