feat: complete overseas customer and PDA workflows
This commit is contained in:
+101
@@ -0,0 +1,101 @@
|
||||
package com.baiyue.lastleg.merchant;
|
||||
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.lastleg.inboundplan.controller.MerchantInboundPlanController;
|
||||
import com.baiyue.lastleg.inboundplan.dto.InboundPlanRequests;
|
||||
import com.baiyue.lastleg.inboundplan.service.InboundPlanService;
|
||||
import com.baiyue.lastleg.looseboard.controller.MerchantLocalLooseboardController;
|
||||
import com.baiyue.lastleg.looseboard.dto.LocalLooseboardRequests;
|
||||
import com.baiyue.lastleg.looseboard.service.LocalLooseboardService;
|
||||
import com.baiyue.lastleg.procurement.controller.MerchantOverseasProcurementController;
|
||||
import com.baiyue.lastleg.security.OverseasPermissions;
|
||||
import com.baiyue.lastleg.trucking.controller.MerchantUsTruckOrderController;
|
||||
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.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MerchantOverseasCustomerApiContractTest {
|
||||
@Mock LocalLooseboardService looseboardService;
|
||||
@Mock InboundPlanService inboundPlanService;
|
||||
private MerchantLocalLooseboardController looseboard;
|
||||
private MerchantInboundPlanController inbound;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
looseboard = new MerchantLocalLooseboardController();
|
||||
inbound = new MerchantInboundPlanController();
|
||||
ReflectionTestUtils.setField(looseboard, "service", looseboardService);
|
||||
ReflectionTestUtils.setField(inbound, "service", inboundPlanService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantControllersUseFrozenPathsAndExistingClientPermissionCodes() {
|
||||
assertController(MerchantOverseasProcurementController.class,
|
||||
"/api/merchant/v1/last-leg/overseas-procurements", OverseasPermissions.MERCHANT_PROCUREMENT);
|
||||
assertController(MerchantUsTruckOrderController.class,
|
||||
"/api/merchant/v1/last-leg/us-truck-orders", OverseasPermissions.MERCHANT_TRUCKING);
|
||||
assertController(MerchantLocalLooseboardController.class,
|
||||
"/api/merchant/v1/last-leg/local-looseboard-orders", OverseasPermissions.MERCHANT_LOOSEBOARD);
|
||||
assertController(MerchantInboundPlanController.class,
|
||||
"/api/merchant/v1/last-leg/inbound-plans", OverseasPermissions.MERCHANT_INBOUND_LIST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void looseboardAndInboundQueriesUseAuthenticatedCustomerScope() {
|
||||
AuthenticatedUser merchant = user(101L, "MERCHANT");
|
||||
LocalLooseboardRequests.MerchantQuery looseboardQuery = new LocalLooseboardRequests.MerchantQuery(1, 20,
|
||||
null, null, null, null, null, null);
|
||||
InboundPlanRequests.Query inboundQuery = new InboundPlanRequests.Query(1, 20, null, null, null,
|
||||
null, null, null, null, null, null, null, null);
|
||||
|
||||
looseboard.page(merchant, looseboardQuery);
|
||||
inbound.page(merchant, inboundQuery);
|
||||
|
||||
verify(looseboardService).page("101", looseboardQuery);
|
||||
verify(inboundPlanService).page("101", inboundQuery);
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantQueryDtosDoNotAcceptCustomerScope() {
|
||||
for (Class<?> type : Set.of(LocalLooseboardRequests.MerchantQuery.class,
|
||||
com.baiyue.lastleg.procurement.dto.OverseasProcurementRequests.MerchantQuery.class,
|
||||
com.baiyue.lastleg.trucking.dto.UsTruckOrderRequests.MerchantQuery.class,
|
||||
InboundPlanRequests.Query.class)) {
|
||||
assertThat(Arrays.stream(type.getRecordComponents()).map(component -> component.getName()))
|
||||
.doesNotContain("customerId", "warehouseId", "operatorId");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantEndpointsRejectPlatformIdentityBeforeCallingServices() {
|
||||
AuthenticatedUser platform = user(0L, "PLATFORM");
|
||||
assertThatThrownBy(() -> looseboard.detail(platform, "LB-1"))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("403");
|
||||
assertThatThrownBy(() -> inbound.detail(platform, "IP-1"))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("403");
|
||||
}
|
||||
|
||||
private static void assertController(Class<?> type, String path, String permission) {
|
||||
assertThat(type.getAnnotation(RequestMapping.class).value()).containsExactly(path);
|
||||
assertThat(type.getAnnotation(PreAuthorize.class).value())
|
||||
.isEqualTo("hasAuthority('" + permission + "')");
|
||||
}
|
||||
|
||||
private static AuthenticatedUser user(long customerId, String appCode) {
|
||||
return new AuthenticatedUser(7L, customerId, customerId, 8L, "operator", appCode, "WEB", Set.of());
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.baiyue.lastleg.merchant;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class MerchantOverseasOpenApiContractTest {
|
||||
@Test
|
||||
void documentsFrozenMerchantPathsPermissionsResponsesAndConflictSemantics() throws Exception {
|
||||
String yaml;
|
||||
try (var input = getClass().getResourceAsStream("/openapi/overseas-business-v1.yaml")) {
|
||||
assertThat(input).isNotNull();
|
||||
yaml = new String(input.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
for (String path : new String[] {
|
||||
"/api/merchant/v1/last-leg/overseas-procurements:",
|
||||
"/api/merchant/v1/last-leg/overseas-procurements/{orderNo}:",
|
||||
"/api/merchant/v1/last-leg/us-truck-orders:",
|
||||
"/api/merchant/v1/last-leg/us-truck-orders/{orderNo}:",
|
||||
"/api/merchant/v1/last-leg/local-looseboard-orders:",
|
||||
"/api/merchant/v1/last-leg/local-looseboard-orders/{orderNo}:",
|
||||
"/api/merchant/v1/last-leg/inbound-plans:",
|
||||
"/api/merchant/v1/last-leg/inbound-plans/{planNo}:"}) {
|
||||
assertThat(yaml).contains(path);
|
||||
assertThat(pathBlock(yaml, path)).doesNotContain("name: customerId");
|
||||
}
|
||||
assertThat(yaml).contains("merchant:cross-border:procurement")
|
||||
.contains("merchant:cross-border:domestic-trucking")
|
||||
.contains("merchant:cross-border:pallet-shipping")
|
||||
.contains("merchant:warehouse:inbound:list")
|
||||
.contains("#/components/schemas/ProcurementDetailResponse")
|
||||
.contains("#/components/schemas/TruckDetailResponse")
|
||||
.contains("#/components/schemas/LooseboardDetailResponse")
|
||||
.contains("#/components/schemas/InboundPlanDetailResponse")
|
||||
.contains("PENDING_REVIEW").contains("PENDING_ACCEPTANCE")
|
||||
.contains("version 必填").contains("幂等冲突");
|
||||
}
|
||||
|
||||
private static String pathBlock(String yaml, String path) {
|
||||
int start = yaml.indexOf(" " + path);
|
||||
int end = yaml.indexOf("\n /api/", start + path.length());
|
||||
return yaml.substring(start, end < 0 ? yaml.length() : end);
|
||||
}
|
||||
}
|
||||
+9
-2
@@ -50,6 +50,10 @@ class PdaOperationsOpenApiContractTest {
|
||||
"/api/pda/v1/last-leg/stocktakes/{taskNo}/counts:",
|
||||
"/api/pda/v1/last-leg/stocktakes/{taskNo}/submit:",
|
||||
"/api/pda/v1/last-leg/inventory-moves:",
|
||||
"/api/pda/v1/last-leg/inventory/resolve:",
|
||||
"/api/pda/v1/last-leg/locations/resolve:",
|
||||
"/api/pda/v1/last-leg/pallets/resolve:",
|
||||
"/api/pda/v1/last-leg/operations:",
|
||||
"/api/pda/v1/last-leg/pallet-operations/pallets/resolve:",
|
||||
"/api/pda/v1/last-leg/pallet-operations/assemble:",
|
||||
"/api/pda/v1/last-leg/pallet-operations/disassemble:",
|
||||
@@ -74,12 +78,15 @@ class PdaOperationsOpenApiContractTest {
|
||||
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}/pick:",
|
||||
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}/complete:");
|
||||
for (String path : paths) assertTrue(yaml.contains(path), path);
|
||||
assertEquals(28, paths.size());
|
||||
assertEquals(32, paths.size());
|
||||
assertTrue(yaml.contains("enum: [LOCATION, PALLET]"));
|
||||
assertTrue(yaml.contains("enum: [APPROVE, REWORK]"));
|
||||
assertTrue(yaml.contains("enum: [DAMAGED, MISSING_LABEL, LABEL_MISMATCH, UNREADABLE, OTHER]"));
|
||||
for (String field : SERVER_OWNED) assertFalse(yaml.contains(field + ":"), field);
|
||||
assertFalse(yaml.contains("/api/pda/v1/last-leg/delivery"));
|
||||
for (String response : List.of("StocktakeSummaryPageResponse", "StocktakeDetailResponse",
|
||||
"StocktakeItemResponse", "InventoryMoveResponse", "InventoryScanResponse", "LocationResponse",
|
||||
"PalletResponse", "OperationPageResponse", "RelabelCandidatesResponse", "RelabelDetailResponse",
|
||||
"RelabelPhotoResponse")) assertTrue(yaml.contains("#/components/schemas/" + response), response);
|
||||
}
|
||||
|
||||
private static void assertNoServerOwnedFields(Class<?> type) {
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ class PdaWarehouseContractTest {
|
||||
|
||||
@Test
|
||||
void openApiAndGatewayPublishOnlyStablePdaPaths() throws Exception {
|
||||
String openapi = Files.readString(Path.of("src/main/resources/openapi/overseas-business-v1.yaml"));
|
||||
String openapi = Files.readString(Path.of("src/main/resources/openapi/overseas-pda-operations-v1.yaml"));
|
||||
for (String path : Set.of("/api/pda/v1/last-leg/inventory/resolve:",
|
||||
"/api/pda/v1/last-leg/locations/resolve:", "/api/pda/v1/last-leg/pallets/resolve:",
|
||||
"/api/pda/v1/last-leg/operations:")) assertThat(openapi).contains(path);
|
||||
|
||||
+3
-3
@@ -33,9 +33,9 @@ class OverseasProcurementControllerTest {
|
||||
|
||||
@Test
|
||||
void merchantAlwaysUsesTenantFromTrustedIdentity() {
|
||||
var query = new OverseasProcurementRequests.Query(1, 20, null, null, null, null, null, null, null, null);
|
||||
var query = new OverseasProcurementRequests.MerchantQuery(1, 20, null, null, null, null, null, null, null);
|
||||
merchant.page(user(100L, "MERCHANT", Set.of()), query);
|
||||
verify(service).page("100", query);
|
||||
verify(service).page("100", query.scoped());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -48,7 +48,7 @@ class OverseasProcurementControllerTest {
|
||||
|
||||
@Test
|
||||
void merchantEndpointRejectsPlatformIdentity() {
|
||||
var query = new OverseasProcurementRequests.Query(1, 20, null, null, null, null, null, null, null, null);
|
||||
var query = new OverseasProcurementRequests.MerchantQuery(1, 20, null, null, null, null, null, null, null);
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class,
|
||||
() -> merchant.page(user(0L, "PLATFORM", Set.of()), query));
|
||||
assertEquals(403, error.getStatusCode().value());
|
||||
|
||||
+5
-3
@@ -31,8 +31,8 @@ class UsTruckOrderControllerTest {
|
||||
|
||||
@Test
|
||||
void merchantListAlwaysUsesTrustedTenant() {
|
||||
var query = query(); merchant.page(user(100L, "MERCHANT", Set.of()), query);
|
||||
verify(service).page("100", query);
|
||||
var query = merchantQuery(); merchant.page(user(100L, "MERCHANT", Set.of()), query);
|
||||
verify(service).page("100", query.scoped());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -92,7 +92,9 @@ class UsTruckOrderControllerTest {
|
||||
verify(service).update("100", "TK1", "7", request);
|
||||
}
|
||||
|
||||
private UsTruckOrderRequests.Query query() { return new UsTruckOrderRequests.Query(1, 20, null, null, null, null, null, null, null); }
|
||||
private UsTruckOrderRequests.MerchantQuery merchantQuery() {
|
||||
return new UsTruckOrderRequests.MerchantQuery(1, 20, null, null, null, null, null, null);
|
||||
}
|
||||
private AuthenticatedUser user(long tenant, String app, Set<String> permissions) {
|
||||
return new AuthenticatedUser(7L, tenant, tenant, 8L, "operator", app, "WEB", permissions);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user