优化
This commit is contained in:
+25
@@ -0,0 +1,25 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import java.lang.reflect.Method;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PlatformSupplierControllerContractTest {
|
||||
@Test void exposesPublicScopeAndSeparatedRoutes() {
|
||||
Method page = method("page");
|
||||
assertThat(page.getParameters()[1].getAnnotation(RequestParam.class).required()).isFalse();
|
||||
assertThat(PlatformSupplierController.class.getAnnotation(RequestMapping.class).value()).containsExactly("/api/platform/v1/suppliers");
|
||||
}
|
||||
@Test void freezesSupplierPermissions() {
|
||||
assertThat(method("page").getAnnotation(PreAuthorize.class).value()).isEqualTo("hasAuthority('platform:supplier:list')");
|
||||
assertThat(method("stats").getAnnotation(PreAuthorize.class).value()).isEqualTo("hasAuthority('platform:supplier:list')");
|
||||
assertThat(method("detail").getAnnotation(PreAuthorize.class).value()).isEqualTo("hasAuthority('platform:supplier:list')");
|
||||
assertThat(method("create").getAnnotation(PreAuthorize.class).value()).contains("platform:supplier:create");
|
||||
assertThat(method("update").getAnnotation(PreAuthorize.class).value()).contains("platform:supplier:edit");
|
||||
assertThat(method("status").getAnnotation(PreAuthorize.class).value()).contains("platform:supplier:status");
|
||||
}
|
||||
private static Method method(String n) { return java.util.Arrays.stream(PlatformSupplierController.class.getDeclaredMethods()).filter(m -> m.getName().equals(n)).findFirst().orElseThrow(); }
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.baiyue.warehouse.domesticcontainer.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PlatformDomesticContainerControllerContractTest {
|
||||
@Test
|
||||
void exposesOnlyDedicatedPlatformContract() {
|
||||
assertThat(PlatformDomesticContainerController.class.getAnnotation(RequestMapping.class).value())
|
||||
.containsExactly("/api/platform/v1/domestic-containers");
|
||||
assertMethod("inbounds", GetMapping.class, "platform:domestic-container:list");
|
||||
assertMethod("outbounds", GetMapping.class, "platform:domestic-container:list");
|
||||
assertMethod("inventories", GetMapping.class, "platform:domestic-container:list");
|
||||
assertMethod("detail", GetMapping.class, "platform:domestic-container:list");
|
||||
assertMethod("createInbound", PostMapping.class, "platform:domestic-container:inbound:create");
|
||||
assertMethod("createOutbound", PostMapping.class, "platform:domestic-container:outbound:create");
|
||||
}
|
||||
|
||||
private static void assertMethod(String name, Class<? extends Annotation> mapping, String permission) {
|
||||
Method method = Arrays.stream(PlatformDomesticContainerController.class.getDeclaredMethods())
|
||||
.filter(candidate -> candidate.getName().equals(name)).findFirst().orElseThrow();
|
||||
assertThat(method.getAnnotation(mapping)).isNotNull();
|
||||
assertThat(method.getAnnotation(PreAuthorize.class).value()).isEqualTo("hasAuthority('" + permission + "')");
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.baiyue.warehouse.domesticcontainer.repository;
|
||||
|
||||
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 DomesticContainerSqlContractTest {
|
||||
@Test
|
||||
void migrationKeepsDedicatedInventoryAndIdempotencyBoundaries() throws Exception {
|
||||
String sql = Files.readString(Path.of("src/main/resources/db/domestic-container-business.sql"));
|
||||
assertThat(sql).contains("wh_domestic_container_order", "wh_domestic_container_inventory",
|
||||
"wh_domestic_container_transaction", "wh_domestic_container_idempotency");
|
||||
assertThat(sql).contains("uk_dc_inventory_container", "chk_dc_inventory_balance", "uk_dc_idempotency");
|
||||
assertThat(sql).contains("CREATE TABLE IF NOT EXISTS", "version INT NOT NULL DEFAULT 0",
|
||||
"uk_dc_order_no", "uk_dc_transaction_order", "request_hash CHAR(64)",
|
||||
"UNIQUE KEY uk_dc_idempotency (tenant_id, operation_type, idempotency_key)",
|
||||
"chk_dc_order_status", "chk_dc_idempotency_status");
|
||||
assertThat(sql).doesNotContain("DROP TABLE", "TRUNCATE TABLE", "DELETE FROM");
|
||||
assertThat(sql).doesNotContain("fl_container", "fl_loading", "fl_pda", "pda_");
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package com.baiyue.warehouse.domesticcontainer.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.domesticcontainer.dto.DomesticContainerDtos;
|
||||
import com.baiyue.warehouse.domesticcontainer.entity.DomesticContainerIdempotencyEntity;
|
||||
import com.baiyue.warehouse.domesticcontainer.entity.DomesticContainerInventoryEntity;
|
||||
import com.baiyue.warehouse.domesticcontainer.repository.DomesticContainerRepository;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.repository.WarehouseRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@SpringJUnitConfig(DomesticContainerService.class)
|
||||
class DomesticContainerServiceTest {
|
||||
@MockBean DomesticContainerRepository repository;
|
||||
@MockBean WarehouseRepository warehouses;
|
||||
@Resource DomesticContainerService service;
|
||||
|
||||
@Test
|
||||
void completedInboundCreatesInventoryAndTransaction() {
|
||||
when(warehouses.findActiveOwned(0L, 10L)).thenReturn(warehouse());
|
||||
when(repository.claim(anyLong(), eq("INBOUND"), eq("in-1"), any())).thenReturn(1);
|
||||
when(repository.completeClaim(eq("INBOUND"), eq("in-1"), any(), anyLong(), any())).thenReturn(1);
|
||||
when(repository.lockInventory(10L, "MSCU1234567")).thenReturn(null);
|
||||
var result = service.createInbound(new DomesticContainerDtos.CreateInboundRequest(10L, "家居用品", "mscu1234567", 120,
|
||||
new BigDecimal("58.6"), LocalDateTime.of(2026,8,12,9,30), "COMPLETED", "整柜到仓", "in-1"));
|
||||
assertThat(result.replayed()).isFalse();
|
||||
verify(repository).insertInventory(any());
|
||||
verify(repository).insertTransaction(any());
|
||||
verify(repository).lockWarehouse(10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pendingInboundDoesNotChangeInventory() {
|
||||
when(warehouses.findActiveOwned(0L, 10L)).thenReturn(warehouse());
|
||||
when(repository.claim(anyLong(), eq("INBOUND"), eq("in-2"), any())).thenReturn(1);
|
||||
when(repository.completeClaim(eq("INBOUND"), eq("in-2"), any(), anyLong(), any())).thenReturn(1);
|
||||
service.createInbound(new DomesticContainerDtos.CreateInboundRequest(10L, "家居用品", "MSCU1234567", 120,
|
||||
new BigDecimal("58.6"), LocalDateTime.now(), "PENDING", null, "in-2"));
|
||||
verify(repository, never()).insertInventory(any());
|
||||
verify(repository, never()).insertTransaction(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void completedOutboundCreatesPartialOutboundBalance() {
|
||||
when(warehouses.findActiveOwned(0L, 10L)).thenReturn(warehouse());
|
||||
when(repository.claim(anyLong(), eq("OUTBOUND"), eq("out-1"), any())).thenReturn(1);
|
||||
when(repository.completeClaim(eq("OUTBOUND"), eq("out-1"), any(), anyLong(), any())).thenReturn(1);
|
||||
DomesticContainerInventoryEntity inventory = inventory(120, 0, 120, 3);
|
||||
when(repository.lockInventory(10L, "MSCU1234567")).thenReturn(inventory);
|
||||
when(repository.updateInventory(inventory)).thenReturn(1);
|
||||
service.createOutbound(new DomesticContainerDtos.CreateOutboundRequest(10L, "MSCU1234567", 30,
|
||||
LocalDateTime.now(), "COMPLETED", "客户提货", "out-1", 3));
|
||||
assertThat(inventory.getOutboundBoxes()).isEqualTo(30);
|
||||
assertThat(inventory.getAvailableBoxes()).isEqualTo(90);
|
||||
assertThat(inventory.getInventoryStatus()).isEqualTo("PARTIALLY_OUTBOUND");
|
||||
verify(repository).insertTransaction(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsOutboundBeyondAvailableInventory() {
|
||||
when(warehouses.findActiveOwned(0L, 10L)).thenReturn(warehouse());
|
||||
when(repository.claim(anyLong(), eq("OUTBOUND"), eq("out-2"), any())).thenReturn(1);
|
||||
when(repository.lockInventory(10L, "MSCU1234567")).thenReturn(inventory(120, 100, 20, 4));
|
||||
assertThatThrownBy(() -> service.createOutbound(new DomesticContainerDtos.CreateOutboundRequest(10L,
|
||||
"MSCU1234567", 21, LocalDateTime.now(), "COMPLETED", null, "out-2", 4))).isInstanceOf(BizException.class);
|
||||
verify(repository, never()).insertOrder(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaysCompletedIdempotentRequestWithoutWriting() {
|
||||
DomesticContainerIdempotencyEntity replay = new DomesticContainerIdempotencyEntity();
|
||||
replay.setRequestHash(hashForInbound()); replay.setOperationStatus("COMPLETED"); replay.setResourceId(99L); replay.setResourceNo("ZGRK1");
|
||||
when(repository.claim(anyLong(), eq("INBOUND"), eq("same"), any())).thenReturn(0);
|
||||
when(repository.findClaim("INBOUND", "same")).thenReturn(replay);
|
||||
var result = service.createInbound(inboundForReplay());
|
||||
assertThat(result).isEqualTo(new DomesticContainerDtos.CreateResult(99L, "ZGRK1", true));
|
||||
verifyNoInteractions(warehouses);
|
||||
}
|
||||
|
||||
private DomesticContainerDtos.CreateInboundRequest inboundForReplay() { return new DomesticContainerDtos.CreateInboundRequest(10L,"家居用品","MSCU1234567",120,new BigDecimal("58.6"),LocalDateTime.of(2026,8,12,9,30),"COMPLETED","整柜到仓","same"); }
|
||||
private String hashForInbound() {
|
||||
try { var digest=java.security.MessageDigest.getInstance("SHA-256"); return java.util.HexFormat.of().formatHex(digest.digest(inboundForReplay().toString().getBytes(java.nio.charset.StandardCharsets.UTF_8))); } catch(Exception e) { throw new RuntimeException(e); }
|
||||
}
|
||||
private static WarehouseEntity warehouse() { WarehouseEntity w=new WarehouseEntity(); w.setId(10L); w.setTenantId(0L); w.setWarehouseName("宁波国内仓"); w.setWarehouseType("DOMESTIC"); w.setStatus("ACTIVE"); return w; }
|
||||
private static DomesticContainerInventoryEntity inventory(int in,int out,int available,int version) { DomesticContainerInventoryEntity i=new DomesticContainerInventoryEntity(); i.setId(20L); i.setWarehouseId(10L); i.setWarehouseName("宁波国内仓"); i.setContainerNo("MSCU1234567"); i.setCargoName("家居用品"); i.setInboundBoxes(in); i.setOutboundBoxes(out); i.setAvailableBoxes(available); i.setInboundVolumeCbm(new BigDecimal("58.6")); i.setInventoryStatus("IN_STOCK"); i.setVersion(version); return i; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.baiyue.warehouse.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SupplierSqlContractTest {
|
||||
@Test void freezesPublicSupplierSchemaAndBookingScope() throws IOException {
|
||||
try (var stream = getClass().getResourceAsStream("/db/warehouse-supplier.sql")) {
|
||||
assertThat(stream).isNotNull();
|
||||
String sql = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
assertThat(sql).contains("CREATE TABLE IF NOT EXISTS wh_supplier", "tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0",
|
||||
"category='BOOKING' AND scope IN ('DOMESTIC','INTERNATIONAL')", "license_file_id VARCHAR(128)",
|
||||
"MONTHLY_30','MONTHLY_45','MONTHLY_60','MONTHLY_90','PER_TRANSACTION','IMMEDIATE",
|
||||
"created_by BIGINT UNSIGNED", "updated_by BIGINT UNSIGNED",
|
||||
"supplier_add_column_if_missing", "uk_wh_supplier_name", "idx_wh_supplier_filter",
|
||||
"assert_supplier_schema");
|
||||
}
|
||||
}
|
||||
|
||||
@Test void unifiedMigrationContainsSupplierSchemaAndPermissionContract() throws IOException {
|
||||
String sql = Files.readString(resolveUnifiedSql(), StandardCharsets.UTF_8);
|
||||
assertThat(sql).contains("CREATE TABLE IF NOT EXISTS wh_supplier", "uk_wh_supplier_name",
|
||||
"idx_wh_supplier_filter", "platform:supplier:list", "platform:supplier:create",
|
||||
"platform:supplier:edit", "platform:supplier:status", "assert_platform_supplier_permissions");
|
||||
}
|
||||
|
||||
private static Path resolveUnifiedSql() {
|
||||
Path direct = Path.of("deploy", "sql", "头程数据库统一增量-MySQL8.sql");
|
||||
return Files.exists(direct) ? direct : Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.SupplierDtos.SaveRequest;
|
||||
import com.baiyue.warehouse.entity.SupplierEntity;
|
||||
import com.baiyue.warehouse.repository.SupplierRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SupplierServiceTest {
|
||||
@Mock private SupplierRepository repository;
|
||||
@InjectMocks private SupplierService service;
|
||||
private SaveRequest booking;
|
||||
|
||||
@BeforeEach void setUp() {
|
||||
booking = new SaveRequest("测试供应商", "BOOKING", "DOMESTIC", "宁波", "法人", "联系人",
|
||||
"13800000000", "supplier@example.com", "地址", "91330000TEST", "192000000000000001",
|
||||
"ACTIVE", "MONTHLY_30", null);
|
||||
}
|
||||
|
||||
@Test void createsPlatformPublicBookingSupplier() {
|
||||
when(repository.existsName(0L, "测试供应商", null)).thenReturn(false);
|
||||
service.create(booking);
|
||||
verify(repository).insert(any(SupplierEntity.class));
|
||||
}
|
||||
|
||||
@Test void rejectsScopeForNonBookingSupplier() {
|
||||
SaveRequest customs = new SaveRequest("报关供应商", "CUSTOMS", "DOMESTIC", "宁波", "法人", "联系人",
|
||||
"13800000000", null, null, null, null, "ACTIVE", "PER_TRANSACTION", null);
|
||||
assertThatThrownBy(() -> service.create(customs)).isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("仅适用于订舱供应商");
|
||||
}
|
||||
|
||||
@Test void requiresScopeForBookingSupplier() {
|
||||
SaveRequest missingScope = new SaveRequest("订舱供应商", "BOOKING", null, "宁波", "法人", "联系人",
|
||||
"13800000000", null, null, null, null, "ACTIVE", "PER_TRANSACTION", null);
|
||||
assertThatThrownBy(() -> service.create(missingScope)).isInstanceOf(BizException.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user