This is an automated email from the ASF dual-hosted git repository.

mcgilman pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/NIFI-15258 by this push:
     new 8ff7ded679 NIFI-15606: Connector Mock War implementation. (#10907)
8ff7ded679 is described below

commit 8ff7ded679fb36d0ff7551494dab967a822bb3d4
Author: Bob Paulin <[email protected]>
AuthorDate: Tue Feb 17 13:02:20 2026 -0600

    NIFI-15606: Connector Mock War implementation. (#10907)
    
    * NIFI-15606: Connector Mock War implementation.
    
    * Load war files from Connector Nar
    * Mock Connector Web Context
    * Allow test runner to set http port
    
    * NIFI-15606: Connector Mock War implementation.
    
    * Code Review Feedback
---
 .../mock/connector/server/ConnectorTestRunner.java |   9 +
 .../nifi-connector-mock-server/pom.xml             |   9 +-
 .../server/MockNiFiConnectorWebContext.java        |  52 ++++++
 .../server/StandardConnectorMockServer.java        | 130 +++++++++++++-
 .../server/MockNiFiConnectorWebContextTest.java    |  80 +++++++++
 .../StandardConnectorMockServerJettyTest.java      | 186 +++++++++++++++++++++
 .../connector/StandardConnectorTestRunner.java     |  21 ++-
 7 files changed, 484 insertions(+), 3 deletions(-)

diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
index 25a5215db3..22b122402d 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
@@ -68,4 +68,13 @@ public interface ConnectorTestRunner extends Closeable {
     void waitForIdle(Duration minimumIdleTime, Duration maxWaitTime);
 
     List<ValidationResult> validate();
+
+    /**
+     * Returns the HTTP port on which the embedded Jetty server is listening, 
or -1 if no server is running.
+     *
+     * @return the HTTP port, or -1 if not applicable
+     */
+    default int getHttpPort() {
+        return -1;
+    }
 }
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/pom.xml 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/pom.xml
index 3b5704bf2a..bcfb88f3e5 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/pom.xml
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/pom.xml
@@ -60,7 +60,14 @@
             <artifactId>nifi-server-api</artifactId>
             <version>2.9.0-SNAPSHOT</version>
         </dependency>
-
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.ee11</groupId>
+            <artifactId>jetty-ee11-webapp</artifactId>
+        </dependency>
 
         <!-- Test Dependencies -->
         <dependency>
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockNiFiConnectorWebContext.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockNiFiConnectorWebContext.java
new file mode 100644
index 0000000000..9e8891ee52
--- /dev/null
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockNiFiConnectorWebContext.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.mock.connector.server;
+
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.ConnectorRepository;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.web.NiFiConnectorWebContext;
+
+/**
+ * Mock implementation of {@link NiFiConnectorWebContext} for the connector 
test runner.
+ * Provides direct access to the connector instance and its flow contexts 
without
+ * authorization proxying, since the mock server uses a permit-all authorizer.
+ */
+public class MockNiFiConnectorWebContext implements NiFiConnectorWebContext {
+
+    private final ConnectorRepository connectorRepository;
+
+    public MockNiFiConnectorWebContext(final ConnectorRepository 
connectorRepository) {
+        this.connectorRepository = connectorRepository;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> ConnectorWebContext<T> getConnectorWebContext(final String 
connectorId) throws IllegalArgumentException {
+        final ConnectorNode connectorNode = 
connectorRepository.getConnector(connectorId);
+        if (connectorNode == null) {
+            throw new IllegalArgumentException("Unable to find connector with 
id: " + connectorId);
+        }
+
+        final T connector = (T) connectorNode.getConnector();
+        final FlowContext workingFlowContext = 
connectorNode.getWorkingFlowContext();
+        final FlowContext activeFlowContext = 
connectorNode.getActiveFlowContext();
+
+        return new ConnectorWebContext<>(connector, workingFlowContext, 
activeFlowContext);
+    }
+}
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
index a4cbf8b5d9..f71ab45d51 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
@@ -17,6 +17,7 @@
 
 package org.apache.nifi.mock.connector.server;
 
+import jakarta.servlet.ServletContext;
 import org.apache.nifi.admin.service.AuditService;
 import org.apache.nifi.asset.Asset;
 import org.apache.nifi.asset.AssetManager;
@@ -44,6 +45,7 @@ import org.apache.nifi.components.state.StateManagerProvider;
 import org.apache.nifi.components.validation.DisabledServiceValidationResult;
 import org.apache.nifi.components.validation.ValidationState;
 import org.apache.nifi.connectable.FlowFileTransferCounts;
+import org.apache.nifi.controller.ControllerService;
 import org.apache.nifi.controller.DecommissionTask;
 import org.apache.nifi.controller.FlowController;
 import org.apache.nifi.controller.metrics.DefaultComponentMetricReporter;
@@ -56,19 +58,29 @@ import org.apache.nifi.diagnostics.DiagnosticsFactory;
 import org.apache.nifi.encrypt.PropertyEncryptor;
 import org.apache.nifi.engine.FlowEngine;
 import org.apache.nifi.events.VolatileBulletinRepository;
-import org.apache.nifi.controller.ControllerService;
 import 
org.apache.nifi.mock.connector.server.secrets.ConnectorTestRunnerSecretsManager;
 import org.apache.nifi.nar.ExtensionMapping;
 import org.apache.nifi.processor.Processor;
 import org.apache.nifi.reporting.BulletinRepository;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.validation.RuleViolationsManager;
+import org.apache.nifi.web.NiFiConnectorWebContext;
+import org.eclipse.jetty.ee.webapp.WebAppClassLoader;
+import org.eclipse.jetty.ee11.webapp.WebAppContext;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -76,9 +88,16 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.jar.JarFile;
+import java.util.stream.Stream;
 
 public class StandardConnectorMockServer implements ConnectorMockServer {
+    private static final Logger logger = 
LoggerFactory.getLogger(StandardConnectorMockServer.class);
+
     private static final String CONNECTOR_ID = "test-connector";
+    private static final String NAR_DEPENDENCIES_PATH = 
"NAR-INF/bundled-dependencies";
+    private static final String CONNECTOR_WAR_MANIFEST_PATH = 
"META-INF/nifi-connector";
+    private static final String WAR_EXTENSION = ".war";
 
     private Bundle systemBundle;
     private Set<Bundle> bundles;
@@ -90,6 +109,7 @@ public class StandardConnectorMockServer implements 
ConnectorMockServer {
     private FlowEngine flowEngine;
     private MockExtensionMapper mockExtensionMapper;
     private FlowFileTransferCounts initialFlowFileTransferCounts = new 
FlowFileTransferCounts(0L, 0L, 0L, 0L);
+    private Server jettyServer;
 
     @Override
     public void start() {
@@ -136,6 +156,8 @@ public class StandardConnectorMockServer implements 
ConnectorMockServer {
         ((MockConnectorRepository) 
connectorRepository).setMockExtensionMapper(mockExtensionMapper);
 
         flowEngine = new FlowEngine(4, "Connector Threads");
+
+        startJettyServer();
     }
 
     @Override
@@ -147,6 +169,14 @@ public class StandardConnectorMockServer implements 
ConnectorMockServer {
 
     @Override
     public void stop() {
+        if (jettyServer != null) {
+            try {
+                jettyServer.stop();
+                logger.info("Jetty server stopped");
+            } catch (final Exception e) {
+                logger.warn("Failed to stop Jetty server", e);
+            }
+        }
         if (flowEngine != null) {
             flowEngine.shutdown();
         }
@@ -401,8 +431,106 @@ public class StandardConnectorMockServer implements 
ConnectorMockServer {
         extensionManager.addControllerService(mockControllerServiceClass);
     }
 
+    @Override
+    public int getHttpPort() {
+        if (jettyServer == null) {
+            return -1;
+        }
+
+        final ServerConnector connector = (ServerConnector) 
jettyServer.getConnectors()[0];
+        return connector.getLocalPort();
+    }
+
     @Override
     public void close() {
         stop();
     }
+
+    private void startJettyServer() {
+        final String httpPortValue = 
nifiProperties.getProperty(NiFiProperties.WEB_HTTP_PORT);
+        if (httpPortValue == null || httpPortValue.isBlank()) {
+            logger.debug("No HTTP port configured; skipping Jetty server 
startup");
+            return;
+        }
+
+        final int httpPort = Integer.parseInt(httpPortValue);
+        final Map<File, Bundle> wars = findWars(bundles);
+        if (wars.isEmpty()) {
+            logger.debug("No WAR files found in NAR bundles; skipping Jetty 
server startup");
+            return;
+        }
+
+        jettyServer = new Server();
+
+        final ServerConnector serverConnector = new 
ServerConnector(jettyServer);
+        serverConnector.setPort(httpPort);
+        jettyServer.addConnector(serverConnector);
+
+        final List<WebAppContext> webAppContexts = new ArrayList<>();
+        final ContextHandlerCollection handlers = new 
ContextHandlerCollection();
+        for (final Map.Entry<File, Bundle> entry : wars.entrySet()) {
+            final File warFile = entry.getKey();
+            final Bundle bundle = entry.getValue();
+
+            final String warName = warFile.getName();
+            final String contextPath = "/" + warName.substring(0, 
warName.length() - WAR_EXTENSION.length());
+
+            final WebAppContext webAppContext = new 
WebAppContext(warFile.getPath(), contextPath);
+            webAppContext.setClassLoader(new 
WebAppClassLoader(bundle.getClassLoader(), webAppContext));
+
+            handlers.addHandler(webAppContext);
+            webAppContexts.add(webAppContext);
+            logger.info("Deploying WAR [{}] at context path [{}]", 
warFile.getAbsolutePath(), contextPath);
+        }
+
+        jettyServer.setHandler(handlers);
+
+        try {
+            jettyServer.start();
+            logger.info("Jetty server started on port [{}]", getHttpPort());
+        } catch (final Exception e) {
+            throw new RuntimeException("Failed to start Jetty server", e);
+        }
+
+        performInjectionForConnectorUis(webAppContexts);
+    }
+
+    private void performInjectionForConnectorUis(final List<WebAppContext> 
webAppContexts) {
+        final NiFiConnectorWebContext connectorWebContext = new 
MockNiFiConnectorWebContext(connectorRepository);
+        for (final WebAppContext webAppContext : webAppContexts) {
+            final ServletContext servletContext = 
webAppContext.getServletHandler().getServletContext();
+            servletContext.setAttribute("nifi-connector-web-context", 
connectorWebContext);
+            logger.info("Injected NiFiConnectorWebContext into WAR context 
[{}]", webAppContext.getContextPath());
+        }
+    }
+
+    public Map<File, Bundle> findWars(final Set<Bundle> bundles) {
+        final Map<File, Bundle> wars = new HashMap<>();
+
+        bundles.forEach(bundle -> {
+            final BundleDetails details = bundle.getBundleDetails();
+            final Path bundledDependencies = new 
File(details.getWorkingDirectory(), NAR_DEPENDENCIES_PATH).toPath();
+            if (Files.isDirectory(bundledDependencies)) {
+                try (final Stream<Path> dependencies = 
Files.list(bundledDependencies)) {
+                    dependencies.filter(dependency -> 
dependency.getFileName().toString().endsWith(WAR_EXTENSION))
+                            .map(Path::toFile)
+                            .filter(this::isConnectorWar)
+                            .forEach(dependency -> wars.put(dependency, 
bundle));
+                } catch (final IOException e) {
+                    logger.warn("Failed to find WAR files in 
bundled-dependencies [{}]", bundledDependencies, e);
+                }
+            }
+        });
+
+        return wars;
+    }
+
+    private boolean isConnectorWar(final File warFile) {
+        try (final JarFile jarFile = new JarFile(warFile)) {
+            return jarFile.getJarEntry(CONNECTOR_WAR_MANIFEST_PATH) != null;
+        } catch (final IOException e) {
+            logger.warn("Unable to inspect WAR file [{}] for connector 
manifest", warFile, e);
+            return false;
+        }
+    }
 }
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/MockNiFiConnectorWebContextTest.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/MockNiFiConnectorWebContextTest.java
new file mode 100644
index 0000000000..659fe2e8fb
--- /dev/null
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/MockNiFiConnectorWebContextTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.mock.connector.server;
+
+import org.apache.nifi.components.connector.Connector;
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.ConnectorRepository;
+import org.apache.nifi.components.connector.FrameworkFlowContext;
+import org.apache.nifi.web.NiFiConnectorWebContext;
+import org.apache.nifi.web.NiFiConnectorWebContext.ConnectorWebContext;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class MockNiFiConnectorWebContextTest {
+
+    private static final String CONNECTOR_ID = "test-connector";
+
+    @Mock
+    private ConnectorRepository connectorRepository;
+
+    @Mock
+    private ConnectorNode connectorNode;
+
+    @Mock
+    private Connector connector;
+
+    @Mock
+    private FrameworkFlowContext workingFlowContext;
+
+    @Mock
+    private FrameworkFlowContext activeFlowContext;
+
+    @Test
+    void testGetConnectorWebContextReturnsConnectorAndFlowContexts() {
+        
when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
+        when(connectorNode.getConnector()).thenReturn(connector);
+        
when(connectorNode.getWorkingFlowContext()).thenReturn(workingFlowContext);
+        
when(connectorNode.getActiveFlowContext()).thenReturn(activeFlowContext);
+
+        final NiFiConnectorWebContext context = new 
MockNiFiConnectorWebContext(connectorRepository);
+        final ConnectorWebContext<Connector> result = 
context.getConnectorWebContext(CONNECTOR_ID);
+
+        assertNotNull(result);
+        assertEquals(connector, result.connector());
+        assertEquals(workingFlowContext, result.workingFlowContext());
+        assertEquals(activeFlowContext, result.activeFlowContext());
+    }
+
+    @Test
+    void testGetConnectorWebContextThrowsForUnknownConnector() {
+        when(connectorRepository.getConnector("unknown-id")).thenReturn(null);
+
+        final NiFiConnectorWebContext context = new 
MockNiFiConnectorWebContext(connectorRepository);
+
+        assertThrows(IllegalArgumentException.class, () -> 
context.getConnectorWebContext("unknown-id"));
+    }
+}
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServerJettyTest.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServerJettyTest.java
new file mode 100644
index 0000000000..980d8341c9
--- /dev/null
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServerJettyTest.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.mock.connector.server;
+
+import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.bundle.BundleDetails;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class StandardConnectorMockServerJettyTest {
+
+    private static final String NAR_DEPENDENCIES_PATH = 
"NAR-INF/bundled-dependencies";
+
+    @TempDir
+    private Path tempDir;
+
+    @Test
+    void testFindWarsDiscoversSingleWarFile() throws Exception {
+        final Path bundleWorkingDir = tempDir.resolve("test-bundle");
+        final Path depsDir = bundleWorkingDir.resolve(NAR_DEPENDENCIES_PATH);
+        Files.createDirectories(depsDir);
+
+        final Path warFile = depsDir.resolve("my-app.war");
+        createConnectorWar(warFile);
+
+        final Bundle bundle = createBundle(bundleWorkingDir);
+        final StandardConnectorMockServer server = new 
StandardConnectorMockServer();
+        final Map<File, Bundle> wars = server.findWars(Set.of(bundle));
+
+        assertEquals(1, wars.size());
+        assertTrue(wars.containsKey(warFile.toFile()));
+        assertEquals(bundle, wars.get(warFile.toFile()));
+    }
+
+    @Test
+    void testFindWarsIgnoresNonWarFiles() throws Exception {
+        final Path bundleWorkingDir = tempDir.resolve("test-bundle");
+        final Path depsDir = bundleWorkingDir.resolve(NAR_DEPENDENCIES_PATH);
+        Files.createDirectories(depsDir);
+
+        Files.createFile(depsDir.resolve("some-lib.jar"));
+        Files.createFile(depsDir.resolve("config.xml"));
+
+        final Bundle bundle = createBundle(bundleWorkingDir);
+        final StandardConnectorMockServer server = new 
StandardConnectorMockServer();
+        final Map<File, Bundle> wars = server.findWars(Set.of(bundle));
+
+        assertTrue(wars.isEmpty());
+    }
+
+    @Test
+    void testFindWarsIgnoresWarWithoutConnectorManifest() throws Exception {
+        final Path bundleWorkingDir = tempDir.resolve("test-bundle");
+        final Path depsDir = bundleWorkingDir.resolve(NAR_DEPENDENCIES_PATH);
+        Files.createDirectories(depsDir);
+
+        final Path warFile = depsDir.resolve("non-connector.war");
+        createWarWithoutConnectorManifest(warFile);
+
+        final Bundle bundle = createBundle(bundleWorkingDir);
+        final StandardConnectorMockServer server = new 
StandardConnectorMockServer();
+        final Map<File, Bundle> wars = server.findWars(Set.of(bundle));
+
+        assertTrue(wars.isEmpty());
+    }
+
+    @Test
+    void testFindWarsHandlesMissingDependenciesDirectory() throws Exception {
+        final Path bundleWorkingDir = tempDir.resolve("empty-bundle");
+        Files.createDirectories(bundleWorkingDir);
+
+        final Bundle bundle = createBundle(bundleWorkingDir);
+        final StandardConnectorMockServer server = new 
StandardConnectorMockServer();
+        final Map<File, Bundle> wars = server.findWars(Set.of(bundle));
+
+        assertTrue(wars.isEmpty());
+    }
+
+    @Test
+    void testFindWarsDiscoversMultipleWarFiles() throws Exception {
+        final Path bundleWorkingDir = tempDir.resolve("multi-war-bundle");
+        final Path depsDir = bundleWorkingDir.resolve(NAR_DEPENDENCIES_PATH);
+        Files.createDirectories(depsDir);
+
+        createConnectorWar(depsDir.resolve("app-one.war"));
+        createConnectorWar(depsDir.resolve("app-two.war"));
+        Files.createFile(depsDir.resolve("some-lib.jar"));
+
+        final Bundle bundle = createBundle(bundleWorkingDir);
+        final StandardConnectorMockServer server = new 
StandardConnectorMockServer();
+        final Map<File, Bundle> wars = server.findWars(Set.of(bundle));
+
+        assertEquals(2, wars.size());
+    }
+
+    @Test
+    void testFindWarsFromMultipleBundles() throws Exception {
+        final Path bundleDir1 = tempDir.resolve("bundle-1");
+        final Path depsDir1 = bundleDir1.resolve(NAR_DEPENDENCIES_PATH);
+        Files.createDirectories(depsDir1);
+        createConnectorWar(depsDir1.resolve("first-app.war"));
+
+        final Path bundleDir2 = tempDir.resolve("bundle-2");
+        final Path depsDir2 = bundleDir2.resolve(NAR_DEPENDENCIES_PATH);
+        Files.createDirectories(depsDir2);
+        createConnectorWar(depsDir2.resolve("second-app.war"));
+
+        final Bundle bundle1 = createBundle(bundleDir1, "test-bundle-1");
+        final Bundle bundle2 = createBundle(bundleDir2, "test-bundle-2");
+
+        final StandardConnectorMockServer server = new 
StandardConnectorMockServer();
+        final Map<File, Bundle> wars = server.findWars(Set.of(bundle1, 
bundle2));
+
+        assertEquals(2, wars.size());
+    }
+
+    @Test
+    void testGetHttpPortReturnsNegativeOneWhenNoServer() {
+        final StandardConnectorMockServer server = new 
StandardConnectorMockServer();
+        assertEquals(-1, server.getHttpPort());
+    }
+
+    @Test
+    void testFindWarsReturnsEmptyForEmptyBundleSet() throws Exception {
+        final StandardConnectorMockServer server = new 
StandardConnectorMockServer();
+        final Map<File, Bundle> wars = server.findWars(Set.of());
+        assertTrue(wars.isEmpty());
+    }
+
+    private static void createConnectorWar(final Path warPath) throws 
IOException {
+        try (final JarOutputStream jarOut = new JarOutputStream(new 
FileOutputStream(warPath.toFile()))) {
+            jarOut.putNextEntry(new JarEntry("META-INF/nifi-connector"));
+            jarOut.closeEntry();
+        }
+    }
+
+    private static void createWarWithoutConnectorManifest(final Path warPath) 
throws IOException {
+        try (final JarOutputStream jarOut = new JarOutputStream(new 
FileOutputStream(warPath.toFile()))) {
+            jarOut.putNextEntry(new JarEntry("WEB-INF/web.xml"));
+            jarOut.closeEntry();
+        }
+    }
+
+    private Bundle createBundle(final Path workingDir) {
+        return createBundle(workingDir, "test-bundle");
+    }
+
+    private Bundle createBundle(final Path workingDir, final String 
artifactId) {
+        final BundleDetails details = new BundleDetails.Builder()
+                .workingDir(workingDir.toFile())
+                .coordinate(new BundleCoordinate("org.test", artifactId, 
"1.0.0"))
+                .build();
+
+        return new Bundle(details, ClassLoader.getSystemClassLoader());
+    }
+
+}
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
index e04708fdf8..294fadec94 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
@@ -46,15 +46,18 @@ import java.time.Duration;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Set;
 
 public class StandardConnectorTestRunner implements ConnectorTestRunner, 
Closeable {
     private final File narLibraryDirectory;
+    private final int httpPort;
 
     private ConnectorMockServer mockServer;
 
     private StandardConnectorTestRunner(final Builder builder) {
         this.narLibraryDirectory = builder.narLibraryDirectory;
+        this.httpPort = builder.httpPort;
 
         try {
             bootstrapInstance();
@@ -102,9 +105,14 @@ public class StandardConnectorTestRunner implements 
ConnectorTestRunner, Closeab
 
         final Set<Bundle> narBundles = narClassLoaders.getBundles();
 
+        final Properties additionalProperties = new Properties();
+        if (httpPort >= 0) {
+            additionalProperties.setProperty(NiFiProperties.WEB_HTTP_PORT, 
String.valueOf(httpPort));
+        }
+
         final NiFiProperties properties;
         try (final InputStream propertiesIn = 
getClass().getClassLoader().getResourceAsStream("nifi.properties")) {
-            properties = 
NiFiProperties.createBasicNiFiProperties(propertiesIn);
+            properties = 
NiFiProperties.createBasicNiFiProperties(propertiesIn, additionalProperties);
         }
 
         nifiServer.initialize(properties, systemBundle, narBundles, 
extensionMapping);
@@ -208,10 +216,16 @@ public class StandardConnectorTestRunner implements 
ConnectorTestRunner, Closeab
         return mockServer.validate();
     }
 
+    @Override
+    public int getHttpPort() {
+        return mockServer.getHttpPort();
+    }
+
 
     public static class Builder {
         private String connectorClassName;
         private File narLibraryDirectory;
+        private int httpPort = -1;
         private final Map<String, Class<? extends Processor>> processorMocks = 
new HashMap<>();
         private final Map<String, Class<? extends ControllerService>> 
controllerServiceMocks = new HashMap<>();
 
@@ -225,6 +239,11 @@ public class StandardConnectorTestRunner implements 
ConnectorTestRunner, Closeab
             return this;
         }
 
+        public Builder httpPort(final int httpPort) {
+            this.httpPort = httpPort;
+            return this;
+        }
+
         public Builder mockProcessor(final String processorType, final Class<? 
extends Processor> mockProcessorClass) {
             processorMocks.put(processorType, mockProcessorClass);
             return this;

Reply via email to