This commit is contained in:
2026-08-05 16:26:10 +08:00
parent 06f7f9d2f2
commit 5ba9716e12
155 changed files with 2883 additions and 551 deletions
@@ -0,0 +1,40 @@
package com.baiyue.warehouse.controller;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class MerchantControllerIdentityContractTest {
@Test
void merchantControllersRequirePositiveCustomerScope() throws Exception {
var valid = user("MERCHANT", 10001L);
for (Class<?> controller : Set.of(MerchantWarehouseController.class, MerchantPartnerController.class)) {
Method guard = controller.getDeclaredMethod("requireMerchant", AuthenticatedUser.class);
guard.setAccessible(true);
guard.invoke(null, valid);
for (AuthenticatedUser invalid : new AuthenticatedUser[] {
null, user("PLATFORM", 0L), user("MERCHANT", 0L), user("MERCHANT", -1L)}) {
assertThatThrownBy(() -> guard.invoke(null, invalid))
.hasCauseInstanceOf(BizException.class);
}
}
}
@Test
void merchantWarehouseDefaultsToPlatformResourceScope() throws Exception {
var value = MerchantWarehouseController.class.getDeclaredField("publicTenantId")
.getAnnotation(org.springframework.beans.factory.annotation.Value.class);
assertThat(value.value()).isEqualTo("${baiyue.warehouse.public-tenant-id:0}");
}
private static AuthenticatedUser user(String appCode, Long tenantId) {
return new AuthenticatedUser(1L, tenantId, tenantId, 1L,
"user", appCode, "WEB", Set.of());
}
}
@@ -3,6 +3,8 @@ package com.baiyue.warehouse.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Validation;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
@@ -15,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import java.lang.reflect.Method;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class PlatformMasterDataControllerContractTest {
@Test
@@ -77,6 +80,36 @@ class PlatformMasterDataControllerContractTest {
assertMethod(PlatformExpressCompanyController.class, "importCompanies", PostMapping.class, "platform:express:import");
}
@Test
void platformResourcesUseScopeZeroAndRejectCustomerScope() throws Exception {
for (Class<?> controller : java.util.List.of(
PlatformWarehouseController.class, PlatformPartnerController.class, PlatformExpressCompanyController.class,
PlatformPalletController.class, PlatformLocationController.class)) {
var scope = java.util.Arrays.stream(controller.getDeclaredMethods())
.filter(method -> method.getName().equals("requireTenant")
|| method.getName().equals("requirePlatformScope"))
.findFirst().orElseThrow();
scope.setAccessible(true);
scope.invoke(null, 0L);
assertThatThrownBy(() -> scope.invoke(null, 10001L))
.hasCauseInstanceOf(BizException.class);
}
}
@Test
void platformIdentityMustCarryTenantZero() throws Exception {
var malformed = new AuthenticatedUser(1L, 10001L, 10001L, 1L,
"admin", "PLATFORM", "WEB", java.util.Set.of());
for (Class<?> controller : java.util.List.of(
PlatformWarehouseController.class, PlatformPartnerController.class, PlatformExpressCompanyController.class,
PlatformPalletController.class, PlatformLocationController.class)) {
var guard = controller.getDeclaredMethod("requirePlatform", AuthenticatedUser.class);
guard.setAccessible(true);
assertThatThrownBy(() -> guard.invoke(null, malformed))
.hasCauseInstanceOf(BizException.class);
}
}
private static void assertContract(Class<?> controller, String authority, String path) throws Exception {
RequestMapping mapping = controller.getAnnotation(RequestMapping.class);
assertThat(mapping.value()).containsExactly(path);
@@ -13,9 +13,9 @@ import java.lang.reflect.Method;
import static org.assertj.core.api.Assertions.assertThat;
class PlatformPartnerAllTenantQueryContractTest {
class PlatformPartnerPublicScopeContractTest {
@Test
void keepsTenantRequiredOutsidePaging() {
void keepsOnlyCompatibilityTenantParameterOutsidePaging() {
Method page = method(PlatformPartnerController.class, "page");
assertThat(page.getParameterTypes()[1]).isEqualTo(Long.class);
assertThat(page.getParameters()[1].getAnnotation(RequestParam.class).required()).isFalse();
@@ -27,6 +27,15 @@ class PlatformPartnerAllTenantQueryContractTest {
}
}
@Test
void exposesPlatformScopeGuardForSharedMasterData() throws Exception {
Method guard = method(PlatformPartnerController.class, "requirePlatformScope");
guard.setAccessible(true);
guard.invoke(null, 0L);
org.assertj.core.api.Assertions.assertThatThrownBy(() -> guard.invoke(null, 10001L))
.hasCauseInstanceOf(com.baiyue.common.core.exception.BizException.class);
}
@Test
void keepsTenantScopeForOtherMethods() throws Exception {
assertThat(PlatformPartnerQueryService.class.getMethod("page", Long.class, PartnerPageQuery.class))
@@ -21,4 +21,21 @@ class PartnerMapperContractTest {
.contains("<otherwise>")
.contains("pt.partner_type = #{type}");
}
@Test
void partnerTypesAreScopedByTenantForReadAndDelete() throws Exception {
Select select = PartnerMapper.class.getDeclaredMethod("selectTypes",
java.util.Collection.class, java.util.Collection.class).getAnnotation(Select.class);
assertThat(String.join("\n", select.value()))
.contains("tenant_id AS tenantId")
.contains("tenant_id IN")
.contains("partner_id IN");
org.apache.ibatis.annotations.Delete delete = PartnerMapper.class
.getDeclaredMethod("deleteTypes", long.class, long.class)
.getAnnotation(org.apache.ibatis.annotations.Delete.class);
assertThat(String.join("\n", delete.value()))
.contains("tenant_id = #{tenantId}")
.contains("partner_id = #{partnerId}");
}
}
@@ -26,6 +26,6 @@ class OverseasStorageFeeSqlContractTest {
.contains("platform:overseas-storage-fee:edit")
.contains("platform:overseas-storage-fee:delete")
.contains("CALL unified_assert_overseas_storage_fee()")
.contains("海外仓阶梯费用表、索引和权限校验通过");
.contains("统一增量全部执行完成");
}
}
@@ -2,6 +2,7 @@ package com.baiyue.warehouse.repository;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baiyue.warehouse.entity.PartnerEntity;
import com.baiyue.warehouse.entity.PartnerTypeEntity;
import com.baiyue.warehouse.mapper.PartnerMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -82,4 +83,40 @@ class PartnerRepositoryTest {
eq("ACTIVE"), isNull());
verify(mapper, never()).selectPartnerIdsByType(anyLong(), anyString());
}
@Test
void hydratesSamePartnerIdUsingEachCustomersTypes() {
PartnerEntity customerA = new PartnerEntity();
customerA.setId(7L); customerA.setTenantId(10001L); customerA.setPartnerType("CUSTOMER");
PartnerEntity customerB = new PartnerEntity();
customerB.setId(7L); customerB.setTenantId(10002L); customerB.setPartnerType("SUPPLIER");
Page<PartnerEntity> source = new Page<>(1, 20);
source.setRecords(List.of(customerA, customerB));
source.setTotal(2);
when(mapper.selectPage(any(Page.class), any())).thenReturn(source);
PartnerTypeEntity typeA = new PartnerTypeEntity();
typeA.setTenantId(10001L); typeA.setPartnerId(7L); typeA.setPartnerType("CUSTOMER");
PartnerTypeEntity typeB = new PartnerTypeEntity();
typeB.setTenantId(10002L); typeB.setPartnerId(7L); typeB.setPartnerType("SUPPLIER");
when(mapper.selectTypes(List.of(10001L, 10002L), List.of(7L, 7L)))
.thenReturn(List.of(typeA, typeB));
repository.page(null, new PartnerPageQuery(1, 20, null, null, null));
assertThat(customerA.getTypes()).containsExactly("CUSTOMER");
assertThat(customerB.getTypes()).containsExactly("SUPPLIER");
}
@Test
void replacesOnlyTheCurrentCustomersTypes() {
PartnerEntity entity = new PartnerEntity();
entity.setId(7L); entity.setTenantId(10001L); entity.setDataScopeId(10001L);
entity.setTypes(List.of("CUSTOMER"));
repository.save(entity);
verify(mapper).deleteTypes(10001L, 7L);
verify(mapper).insertTypes(7L, 10001L, 10001L, List.of("CUSTOMER"));
}
}
@@ -30,13 +30,13 @@ class ExpressCompanyImportServiceTest {
{"SF", "顺丰速运", "95338", "https://www.sf-express.com", "https://example.com/{trackingNo}", "ACTIVE", "国内件"}
});
var result = service.importFile(10001L, "express.xlsx", workbook);
var result = service.importFile(0L, "express.xlsx", workbook);
assertThat(result.success()).isTrue();
assertThat(result.importedRows()).isEqualTo(1);
var captor = org.mockito.ArgumentCaptor.forClass(ExpressCompanyEntity.class);
verify(repository).insert(captor.capture());
assertThat(captor.getValue().getTenantId()).isEqualTo(10001L);
assertThat(captor.getValue().getTenantId()).isZero();
assertThat(captor.getValue().getCompanyCode()).isEqualTo("SF");
}
@@ -48,7 +48,7 @@ class ExpressCompanyImportServiceTest {
{"SF2", "顺丰速运", "", "", "", "ACTIVE", ""}
});
var result = service.importFile(10001L, "express.xlsx", workbook);
var result = service.importFile(0L, "express.xlsx", workbook);
assertThat(result.success()).isFalse();
assertThat(result.importedRows()).isZero();
@@ -33,13 +33,14 @@ class PartnerImportServiceTest {
"CN", "广东省", "深圳市", "南山区", "测试路 1 号", "518000", "ACTIVE", "测试备注"}
});
PartnerImportResult result = service.importFile(10001L, "partners.xlsx", file);
PartnerImportResult result = service.importFile(0L, "partners.xlsx", file);
assertThat(result.success()).isTrue();
assertThat(result.importedRows()).isEqualTo(1);
var captor = org.mockito.ArgumentCaptor.forClass(PartnerEntity.class);
verify(repository).insert(captor.capture());
assertThat(captor.getValue().getDataScopeId()).isEqualTo(10001L);
assertThat(captor.getValue().getTenantId()).isZero();
assertThat(captor.getValue().getDataScopeId()).isZero();
assertThat(captor.getValue().getTypes()).containsExactly("CUSTOMER", "SHIPPER");
assertThat(captor.getValue().getRemark()).isEqualTo("测试备注");
}
@@ -52,7 +53,7 @@ class PartnerImportServiceTest {
{"CUS-100", "测试单位二", "UNKNOWN", "李四", "13900139000", "bad-email", "C", "", "", "", "", "", "ENABLED", ""}
});
PartnerImportResult result = service.importFile(10001L, "partners.xlsx", file);
PartnerImportResult result = service.importFile(0L, "partners.xlsx", file);
assertThat(result.success()).isFalse();
assertThat(result.importedRows()).isZero();
@@ -19,11 +19,11 @@ class PartnerQueryServiceTest {
@MockBean private PartnerRepository repository;
@Resource private PartnerQueryService service;
@Test void unsupportedCandidateTypeReturnsBadRequest() {
assertThatThrownBy(() -> service.candidates(10001L, null, "CUSTOMER"))
assertThatThrownBy(() -> service.candidates(0L, null, "CUSTOMER"))
.isInstanceOf(BizException.class).hasMessageContaining("SHIPPER");
}
@Test void missingOrCrossTenantPartnerReturnsNotFound() {
assertThatThrownBy(() -> service.active(10001L, 7L))
@Test void missingPublicPartnerReturnsNotFound() {
assertThatThrownBy(() -> service.active(0L, 7L))
.isInstanceOf(BizException.class)
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.NOT_FOUND);
@@ -31,10 +31,10 @@ class PartnerQueryServiceTest {
@Test void shouldHandleCase01() {
PartnerEntity partner = new PartnerEntity();
partner.setId(7L); partner.setTenantId(10001L); partner.setStatus("INACTIVE");
when(repository.findOwned(10001L, 7L)).thenReturn(partner);
partner.setId(7L); partner.setTenantId(0L); partner.setStatus("INACTIVE");
when(repository.findOwned(0L, 7L)).thenReturn(partner);
assertThatThrownBy(() -> service.active(10001L, 7L))
assertThatThrownBy(() -> service.active(0L, 7L))
.isInstanceOf(BizException.class)
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.CONFLICT);
@@ -42,9 +42,16 @@ class PartnerQueryServiceTest {
@Test void shouldHandleCase02() {
PartnerEntity partner = new PartnerEntity();
partner.setId(7L); partner.setTenantId(10001L); partner.setStatus("ACTIVE");
when(repository.findOwned(10001L, 7L)).thenReturn(partner);
partner.setId(7L); partner.setTenantId(0L); partner.setStatus("ACTIVE");
when(repository.findOwned(0L, 7L)).thenReturn(partner);
assertThat(service.active(10001L, 7L)).isSameAs(partner);
assertThat(service.active(0L, 7L)).isSameAs(partner);
}
@Test void customerScopeIsRejectedForSharedPartner() {
assertThatThrownBy(() -> service.active(10001L, 7L))
.isInstanceOf(BizException.class)
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.BAD_REQUEST);
}
}
@@ -35,17 +35,17 @@ class PlatformMasterDataManagementServiceTest {
PartnerRepository repository = partnerRepository;
PlatformPartnerQueryService service = partnerService;
assertThatThrownBy(() -> service.detail(10002L, 1L)).isInstanceOf(BizException.class);
verify(repository).findOwned(10002L, 1L);
assertThatThrownBy(() -> service.detail(0L, 1L)).isInstanceOf(BizException.class);
verify(repository).findOwned(0L, 1L);
}
@Test
void shouldHandleCase02() {
PartnerRepository repository = partnerRepository;
when(repository.existsCode(10001L, "CUS-001", null)).thenReturn(true);
when(repository.existsCode(0L, "CUS-001", null)).thenReturn(true);
PlatformPartnerQueryService service = partnerService;
assertThatThrownBy(() -> service.create(10001L, partnerRequest()))
assertThatThrownBy(() -> service.create(0L, partnerRequest()))
.isInstanceOf(BizException.class).hasMessageContaining("编码");
verify(repository, never()).insert(any());
}
@@ -56,7 +56,7 @@ class PlatformMasterDataManagementServiceTest {
doThrow(new DataIntegrityViolationException("duplicate")).when(repository).insert(any());
PlatformPartnerQueryService service = partnerService;
assertThatThrownBy(() -> service.create(10001L, partnerRequest()))
assertThatThrownBy(() -> service.create(0L, partnerRequest()))
.isInstanceOf(BizException.class).hasMessageContaining("编码");
}
@@ -70,12 +70,12 @@ class PlatformMasterDataManagementServiceTest {
}).when(repository).insert(any());
PlatformPartnerQueryService service = partnerService;
assertThat(service.create(10001L, partnerRequest())).isEqualTo(18L);
assertThat(service.create(0L, partnerRequest())).isEqualTo(18L);
org.mockito.ArgumentCaptor<PartnerEntity> entity = org.mockito.ArgumentCaptor.forClass(PartnerEntity.class);
verify(repository).insert(entity.capture());
assertThat(entity.getValue().getTenantId()).isEqualTo(10001L);
assertThat(entity.getValue().getDataScopeId()).isEqualTo(10001L);
assertThat(entity.getValue().getTenantId()).isZero();
assertThat(entity.getValue().getDataScopeId()).isZero();
assertThat(entity.getValue().getPartnerCode()).isEqualTo("CUS-001");
assertThat(entity.getValue().getPartnerName()).isEqualTo("示例客户");
assertThat(entity.getValue().getTypes()).containsExactly("CUSTOMER", "SHIPPER");
@@ -87,13 +87,13 @@ class PlatformMasterDataManagementServiceTest {
PartnerRepository repository = partnerRepository;
PartnerEntity entity = new PartnerEntity();
entity.setId(19L);
entity.setTenantId(10001L);
when(repository.findOwned(10001L, 19L)).thenReturn(entity);
entity.setTenantId(0L);
when(repository.findOwned(0L, 19L)).thenReturn(entity);
PlatformPartnerQueryService service = partnerService;
service.update(10001L, 19L, partnerRequest());
service.update(0L, 19L, partnerRequest());
assertThat(entity.getTenantId()).isEqualTo(10001L);
assertThat(entity.getTenantId()).isZero();
assertThat(entity.getPartnerName()).isEqualTo("示例客户");
verify(repository).save(entity);
}
@@ -103,12 +103,12 @@ class PlatformMasterDataManagementServiceTest {
PartnerRepository repository = partnerRepository;
PartnerEntity entity = new PartnerEntity();
entity.setId(8L);
entity.setTenantId(10001L);
entity.setTenantId(0L);
entity.setStatus("ACTIVE");
when(repository.findOwned(10001L, 8L)).thenReturn(entity);
when(repository.findOwned(0L, 8L)).thenReturn(entity);
PlatformPartnerQueryService service = partnerService;
service.changeStatus(10001L, 8L, "INACTIVE");
service.changeStatus(0L, 8L, "INACTIVE");
assertThat(entity.getStatus()).isEqualTo("INACTIVE");
verify(repository).save(entity);
@@ -119,29 +119,29 @@ class PlatformMasterDataManagementServiceTest {
ExpressCompanyRepository repository = expressRepository;
ExpressCompanyQueryService service = expressService;
assertThatThrownBy(() -> service.detail(10002L, 1L)).isInstanceOf(BizException.class);
verify(repository).findOwned(10002L, 1L);
assertThatThrownBy(() -> service.detail(0L, 1L)).isInstanceOf(BizException.class);
verify(repository).findOwned(0L, 1L);
}
@Test
void shouldHandleCase08() {
ExpressCompanyRepository repository = expressRepository;
when(repository.existsCode(10001L, "SF", null)).thenReturn(true);
when(repository.existsCode(0L, "SF", null)).thenReturn(true);
ExpressCompanyQueryService service = expressService;
assertThatThrownBy(() -> service.create(10001L, expressRequest()))
assertThatThrownBy(() -> service.create(0L, expressRequest()))
.isInstanceOf(BizException.class).hasMessageContaining("编码");
verify(repository, never()).insert(any());
}
@Test
@DisplayName("快递公司名称在租户内必须唯一")
@DisplayName("快递公司名称在平台范围内必须唯一")
void shouldRequireUniqueExpressCompanyNameWithinTenant() {
ExpressCompanyRepository repository = expressRepository;
when(repository.existsName(10001L, "顺丰速运", null)).thenReturn(true);
when(repository.existsName(0L, "顺丰速运", null)).thenReturn(true);
ExpressCompanyQueryService service = expressService;
assertThatThrownBy(() -> service.create(10001L, expressRequest()))
assertThatThrownBy(() -> service.create(0L, expressRequest()))
.isInstanceOf(BizException.class).hasMessageContaining("名称");
verify(repository, never()).insert(any());
}
@@ -153,7 +153,7 @@ class PlatformMasterDataManagementServiceTest {
doThrow(new DataIntegrityViolationException("duplicate")).when(repository).insert(any());
ExpressCompanyQueryService service = expressService;
assertThatThrownBy(() -> service.create(10001L, expressRequest()))
assertThatThrownBy(() -> service.create(0L, expressRequest()))
.isInstanceOf(BizException.class).hasMessageContaining("编码");
}
@@ -167,12 +167,12 @@ class PlatformMasterDataManagementServiceTest {
}).when(repository).insert(any());
ExpressCompanyQueryService service = expressService;
assertThat(service.create(10001L, expressRequest())).isEqualTo(28L);
assertThat(service.create(0L, expressRequest())).isEqualTo(28L);
org.mockito.ArgumentCaptor<ExpressCompanyEntity> entity =
org.mockito.ArgumentCaptor.forClass(ExpressCompanyEntity.class);
verify(repository).insert(entity.capture());
assertThat(entity.getValue().getTenantId()).isEqualTo(10001L);
assertThat(entity.getValue().getTenantId()).isZero();
assertThat(entity.getValue().getCompanyCode()).isEqualTo("SF");
assertThat(entity.getValue().getCompanyName()).isEqualTo("顺丰速运");
}
@@ -182,13 +182,13 @@ class PlatformMasterDataManagementServiceTest {
ExpressCompanyRepository repository = expressRepository;
ExpressCompanyEntity entity = new ExpressCompanyEntity();
entity.setId(29L);
entity.setTenantId(10001L);
when(repository.findOwned(10001L, 29L)).thenReturn(entity);
entity.setTenantId(0L);
when(repository.findOwned(0L, 29L)).thenReturn(entity);
ExpressCompanyQueryService service = expressService;
service.update(10001L, 29L, expressRequest());
service.update(0L, 29L, expressRequest());
assertThat(entity.getTenantId()).isEqualTo(10001L);
assertThat(entity.getTenantId()).isZero();
assertThat(entity.getCompanyName()).isEqualTo("顺丰速运");
verify(repository).save(entity);
}
@@ -198,12 +198,12 @@ class PlatformMasterDataManagementServiceTest {
ExpressCompanyRepository repository = expressRepository;
ExpressCompanyEntity entity = new ExpressCompanyEntity();
entity.setId(9L);
entity.setTenantId(10001L);
entity.setTenantId(0L);
entity.setStatus("ACTIVE");
when(repository.findOwned(10001L, 9L)).thenReturn(entity);
when(repository.findOwned(0L, 9L)).thenReturn(entity);
ExpressCompanyQueryService service = expressService;
service.changeStatus(10001L, 9L, "INACTIVE");
service.changeStatus(0L, 9L, "INACTIVE");
assertThat(entity.getStatus()).isEqualTo("INACTIVE");
verify(repository).save(entity);