package com.baiyue.lastleg.controller; import com.baiyue.lastleg.delivery.controller.DeliveryTransferController; import com.baiyue.lastleg.dispatch.controller.DispatchController; import com.baiyue.lastleg.dispatch.controller.DispatchQueryController; import com.baiyue.lastleg.dispatch.controller.SmartDispatchController; import com.baiyue.lastleg.dispatch.pda.controller.DispatchPdaController; import com.baiyue.lastleg.inboundplan.pda.controller.ReceivingContainerController; import com.baiyue.lastleg.inventory.command.controller.InventoryCommandController; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.DeleteMapping; import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; import java.nio.file.Files; import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class Phase4BrowserApiBoundaryTest { @Test void onlyStableContainerReceivingUsesThePdaInboundAccessDomain() throws Exception { assertTrue(basePath(ReceivingContainerController.class).startsWith("/api/pda/v1/")); assertFalse(basePath(ReceivingContainerController.class).contains("/api/platform/")); Path controllers = Path.of("src/main/java/com/baiyue/lastleg/inboundplan/pda/controller"); String source = Files.walk(controllers).filter(path -> path.toString().endsWith(".java")) .map(path -> { try { return Files.readString(path); } catch (Exception exception) { throw new IllegalStateException(exception); } }).collect(Collectors.joining("\n")); assertFalse(source.contains("/api/pda/v1/last-leg/inbound-plans/{planNo}")); assertFalse(source.contains("@RequestParam(required = false) String customerId")); } @Test void excludedOutboundControllersAreDisabledUnlessExplicitlyEnabledLater() { for (Class type : new Class[] { DispatchController.class, DeliveryTransferController.class, DispatchPdaController.class }) { ConditionalOnProperty condition = type.getAnnotation(ConditionalOnProperty.class); assertTrue(condition != null, type.getSimpleName()); assertTrue(Arrays.asList(condition.name()).contains("lastleg.future-outbound-api.enabled")); assertFalse(condition.matchIfMissing()); } } @Test void dispatchQueryIsIndependentlyEnabledWithoutExposingOutboundCommands() { ConditionalOnProperty condition = DispatchQueryController.class.getAnnotation(ConditionalOnProperty.class); assertTrue(condition != null); assertTrue(Arrays.asList(condition.name()).contains("lastleg.dispatch-query-api.enabled")); assertFalse(condition.matchIfMissing()); assertTrue(Arrays.stream(DispatchQueryController.class.getDeclaredMethods()) .filter(method -> method.isAnnotationPresent(GetMapping.class)).count() == 2); assertFalse(Arrays.stream(DispatchQueryController.class.getDeclaredMethods()) .flatMap(method -> Arrays.stream(method.getAnnotations())) .anyMatch(annotation -> Set.of("PostMapping", "PutMapping", "DeleteMapping") .contains(annotation.annotationType().getSimpleName()))); } @Test void dispatchQueryIsEnabledByDefaultWithoutEnablingOutboundCommands() throws Exception { String yaml = Files.readString(Path.of("src/main/resources/application.yml")); assertTrue(yaml.contains("enabled: ${LASTLEG_DISPATCH_QUERY_API_ENABLED:true}")); assertTrue(yaml.contains("enabled: ${LASTLEG_SMART_DISPATCH_API_ENABLED:true}")); assertFalse(yaml.contains("future-outbound-api:")); } @Test void smartDispatchOnlyExposesCandidatePreArrangeAndDraftLifecycleEndpoints() { ConditionalOnProperty condition = SmartDispatchController.class.getAnnotation(ConditionalOnProperty.class); assertTrue(condition != null); assertTrue(Arrays.asList(condition.name()).contains("lastleg.smart-dispatch-api.enabled")); assertFalse(condition.matchIfMissing()); assertTrue(Arrays.stream(SmartDispatchController.class.getDeclaredMethods()) .filter(method -> method.isAnnotationPresent(GetMapping.class)).count() == 2); assertTrue(Arrays.stream(SmartDispatchController.class.getDeclaredMethods()) .filter(method -> method.isAnnotationPresent(PostMapping.class)).count() == 6); assertTrue(Arrays.stream(SmartDispatchController.class.getDeclaredMethods()) .filter(method -> method.isAnnotationPresent(DeleteMapping.class)).count() == 1); assertTrue(Arrays.stream(SmartDispatchController.class.getDeclaredMethods()) .filter(method -> method.isAnnotationPresent(DeleteMapping.class)) .allMatch(method -> Arrays.asList(method.getAnnotation(DeleteMapping.class).value()) .contains("/drafts/{draftToken}/containers/{containerId}"))); assertFalse(Arrays.stream(SmartDispatchController.class.getDeclaredMethods()) .flatMap(method -> Arrays.stream(method.getAnnotations())) .anyMatch(annotation -> Set.of("PutMapping", "PatchMapping") .contains(annotation.annotationType().getSimpleName()))); } @Test void platformInventoryCommandsDoNotMapOutboundAllocationPickingOrShipping() { Set paths = Arrays.stream(InventoryCommandController.class.getDeclaredMethods()) .flatMap(method -> Arrays.stream(method.getAnnotations())) .filter(annotation -> annotation.annotationType().getName().startsWith("org.springframework.web.bind.annotation")) .flatMap(annotation -> { try { return Arrays.stream((String[]) annotation.annotationType().getMethod("value").invoke(annotation)); } catch (ReflectiveOperationException ignored) { return java.util.stream.Stream.empty(); } }).collect(Collectors.toSet()); assertFalse(paths.contains("/allocate")); assertFalse(paths.contains("/unallocate")); assertFalse(paths.contains("/pick")); assertFalse(paths.contains("/ship")); } private static String basePath(Class type) { return type.getAnnotation(RequestMapping.class).value()[0]; } }