This is an automated email from the ASF dual-hosted git repository.
markap14 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 556ec1ea46 NIFI-15581: Add support for ControllerService Mocks for
Connectors. (#10885)
556ec1ea46 is described below
commit 556ec1ea46e905cbfc3126e753b74be68786328f
Author: Bob Paulin <[email protected]>
AuthorDate: Thu Feb 12 11:07:31 2026 -0600
NIFI-15581: Add support for ControllerService Mocks for Connectors. (#10885)
---
.../mock/connector/server/ConnectorMockServer.java | 3 +
.../server/MockConnectorInitializationContext.java | 7 +
.../server/MockExtensionDiscoveringManager.java | 14 +
.../mock/connector/server/MockExtensionMapper.java | 17 +
.../server/StandardConnectorMockServer.java | 7 +
.../nifi-connector-mock-integration-tests/pom.xml | 12 +
.../mock/connectors/tests/CreateConnectorIT.java | 22 +-
.../connectors/tests/MockControllerServiceIT.java | 87 +++++
.../main/resources/flows/Generate_and_Update.json | 394 ++++++++++++++++++++-
.../connector/StandardConnectorTestRunner.java | 16 +-
10 files changed, 565 insertions(+), 14 deletions(-)
diff --git
a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorMockServer.java
b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorMockServer.java
index 7b24c973c9..ade061a9d4 100644
---
a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorMockServer.java
+++
b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorMockServer.java
@@ -18,6 +18,7 @@
package org.apache.nifi.mock.connector.server;
import org.apache.nifi.NiFiServer;
+import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.processor.Processor;
import java.io.File;
@@ -30,4 +31,6 @@ public interface ConnectorMockServer extends NiFiServer,
ConnectorTestRunner {
void mockProcessor(String processorType, Class<? extends Processor>
mockProcessorClass);
+ void mockControllerService(String controllerServiceType, Class<? extends
ControllerService> mockControllerServiceClass);
+
}
diff --git
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
index 6390fad284..02d276bc28 100644
---
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
+++
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
@@ -21,6 +21,7 @@ import
org.apache.nifi.components.connector.BundleCompatibility;
import org.apache.nifi.components.connector.FlowUpdateException;
import
org.apache.nifi.components.connector.StandardConnectorInitializationContext;
import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.flow.VersionedControllerService;
import org.apache.nifi.flow.VersionedExternalFlow;
import org.apache.nifi.flow.VersionedProcessGroup;
import org.apache.nifi.flow.VersionedProcessor;
@@ -48,6 +49,12 @@ public class MockConnectorInitializationContext extends
StandardConnectorInitial
}
}
+ if (group.getControllerServices() != null) {
+ for (final VersionedControllerService controllerService :
group.getControllerServices()) {
+ mockExtensionMapper.mapControllerService(controllerService);
+ }
+ }
+
if (group.getProcessGroups() != null) {
for (final VersionedProcessGroup childGroup :
group.getProcessGroups()) {
replaceMocks(childGroup);
diff --git
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionDiscoveringManager.java
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionDiscoveringManager.java
index 7af2240d2d..22b2ca043c 100644
---
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionDiscoveringManager.java
+++
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionDiscoveringManager.java
@@ -20,6 +20,7 @@ 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.apache.nifi.controller.ControllerService;
import org.apache.nifi.nar.InstanceClassLoader;
import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
import org.apache.nifi.processor.Processor;
@@ -46,6 +47,19 @@ public class MockExtensionDiscoveringManager extends
StandardExtensionDiscoverin
registerExtensionClass(Processor.class, mockProcessorClass.getName(),
mockBundle);
}
+ public synchronized void addControllerService(final Class<? extends
ControllerService> mockControllerServiceClass) {
+ final BundleDetails bundleDetails = new BundleDetails.Builder()
+ .workingDir(new File("target/work/extensions/mock-bundle"))
+ .coordinate(new BundleCoordinate("org.apache.nifi.mock",
mockControllerServiceClass.getName(), "1.0.0"))
+ .build();
+
+ final Bundle mockBundle = new Bundle(bundleDetails,
mockControllerServiceClass.getClassLoader());
+ discoverExtensions(Set.of(mockBundle));
+
+ mockComponentClassLoaders.put(mockControllerServiceClass.getName(),
mockControllerServiceClass.getClassLoader());
+ registerExtensionClass(ControllerService.class,
mockControllerServiceClass.getName(), mockBundle);
+ }
+
@Override
public InstanceClassLoader createInstanceClassLoader(final String
classType, final String instanceIdentifier, final Bundle bundle, final Set<URL>
additionalUrls,
final boolean register, final String classloaderIsolationKey) {
diff --git
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionMapper.java
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionMapper.java
index ed532cc253..edf81cdeaa 100644
---
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionMapper.java
+++
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionMapper.java
@@ -18,6 +18,7 @@
package org.apache.nifi.mock.connector.server;
import org.apache.nifi.flow.Bundle;
+import org.apache.nifi.flow.VersionedControllerService;
import org.apache.nifi.flow.VersionedProcessor;
import java.util.HashMap;
@@ -26,11 +27,16 @@ import java.util.Map;
public class MockExtensionMapper {
private final Map<String, String> processorMocks = new HashMap<>();
+ private final Map<String, String> controllerServiceMocks = new HashMap<>();
public void mockProcessor(final String processorType, final String
mockProcessorClassName) {
processorMocks.put(processorType, mockProcessorClassName);
}
+ public void mockControllerService(final String controllerServiceType,
final String mockControllerServiceClassName) {
+ controllerServiceMocks.put(controllerServiceType,
mockControllerServiceClassName);
+ }
+
public void mapProcessor(final VersionedProcessor processor) {
final String type = processor.getType();
final String implementationClassName = processorMocks.get(type);
@@ -41,4 +47,15 @@ public class MockExtensionMapper {
processor.setType(implementationClassName);
processor.setBundle(new Bundle("org.apache.nifi.mock",
implementationClassName, "1.0.0"));
}
+
+ public void mapControllerService(final VersionedControllerService
controllerService) {
+ final String type = controllerService.getType();
+ final String implementationClassName =
controllerServiceMocks.get(type);
+ if (implementationClassName == null) {
+ return;
+ }
+
+ controllerService.setType(implementationClassName);
+ controllerService.setBundle(new Bundle("org.apache.nifi.mock",
implementationClassName, "1.0.0"));
+ }
}
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 e788b446fa..2ff196f435 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
@@ -57,6 +57,7 @@ import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.events.VolatileBulletinRepository;
import
org.apache.nifi.mock.connector.server.secrets.ConnectorTestRunnerSecretsManager;
+import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.nar.ExtensionMapping;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.reporting.BulletinRepository;
@@ -394,6 +395,12 @@ public class StandardConnectorMockServer implements
ConnectorMockServer {
extensionManager.addProcessor(mockProcessorClass);
}
+ @Override
+ public void mockControllerService(final String controllerServiceType,
final Class<? extends ControllerService> mockControllerServiceClass) {
+ mockExtensionMapper.mockControllerService(controllerServiceType,
mockControllerServiceClass.getName());
+ extensionManager.addControllerService(mockControllerServiceClass);
+ }
+
@Override
public void close() {
stop();
diff --git
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/pom.xml
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/pom.xml
index 7b092337a4..a46a06baaa 100644
---
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/pom.xml
+++
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/pom.xml
@@ -43,6 +43,12 @@
<artifactId>nifi-connector-mock-api</artifactId>
<version>2.8.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-lookup-service-api</artifactId>
+ <version>2.8.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
<!-- Test Dependencies -->
<dependency>
@@ -90,6 +96,12 @@
<version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-lookup-services-nar</artifactId>
+ <version>2.8.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
</dependencies>
<build>
diff --git
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CreateConnectorIT.java
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CreateConnectorIT.java
index 950b945003..61e6aeef60 100644
---
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CreateConnectorIT.java
+++
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CreateConnectorIT.java
@@ -17,14 +17,16 @@
package org.apache.nifi.mock.connectors.tests;
+import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.mock.connector.StandardConnectorTestRunner;
import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
+import java.util.List;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class CreateConnectorIT {
@@ -42,16 +44,18 @@ public class CreateConnectorIT {
}
@Test
- public void testConnectorWithMissingBundleThrowsException() {
- final IllegalStateException exception =
assertThrows(IllegalStateException.class, () -> {
- new StandardConnectorTestRunner.Builder()
+ public void testConnectorWithMissingBundleFailsValidate() throws
IOException {
+
+ try (final ConnectorTestRunner testRunner = new
StandardConnectorTestRunner.Builder()
.connectorClassName("org.apache.nifi.mock.connectors.MissingBundleConnector")
.narLibraryDirectory(new File("target/libDir"))
- .build();
- });
+ .build()) {
- final String message = exception.getMessage();
-
assertTrue(message.contains("com.example.nonexistent:missing-nar:1.0.0"),
"Expected exception message to contain missing bundle coordinates but was: " +
message);
-
assertTrue(message.contains("com.example.nonexistent.MissingProcessor"),
"Expected exception message to contain missing processor type but was: " +
message);
+ final List<ValidationResult> results = testRunner.validate();
+ assertEquals(results.size(), 1);
+ final String message = results.getFirst().getExplanation();
+
assertTrue(message.contains("com.example.nonexistent:missing-nar:1.0.0"),
"Expected exception message to contain missing bundle coordinates but was: " +
message);
+
assertTrue(message.contains("com.example.nonexistent.MissingProcessor"),
"Expected exception message to contain missing processor type but was: " +
message);
+ }
}
}
diff --git
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockControllerServiceIT.java
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockControllerServiceIT.java
new file mode 100644
index 0000000000..1f044ccf21
--- /dev/null
+++
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockControllerServiceIT.java
@@ -0,0 +1,87 @@
+/*
+ * 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.connectors.tests;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.Validator;
+import org.apache.nifi.controller.AbstractControllerService;
+import org.apache.nifi.lookup.LookupFailureException;
+import org.apache.nifi.lookup.StringLookupService;
+import org.apache.nifi.mock.connector.StandardConnectorTestRunner;
+import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class MockControllerServiceIT {
+
+ @Test
+ @Timeout(10)
+ public void testMockControllerService() throws IOException {
+ try (final ConnectorTestRunner runner = new
StandardConnectorTestRunner.Builder()
+ .narLibraryDirectory(new File("target/libDir"))
+
.connectorClassName("org.apache.nifi.mock.connectors.GenerateAndLog")
+
.mockControllerService("org.apache.nifi.lookup.SimpleKeyValueLookupService",
MockStringLookupService.class)
+ .build()) {
+
+ runner.startConnector();
+
+ // Wait until MockStringLookupService.lookup is invoked at least
once.
+ // We use @Timeout on the test to avoid hanging indefinitely in
case of failure.
+ while (MockStringLookupService.lookupCounter.get() < 1) {
+ Thread.yield();
+ }
+
+ assertTrue(MockStringLookupService.lookupCounter.get() >= 1);
+
+ runner.stopConnector();
+ }
+ }
+
+ public static class MockStringLookupService extends
AbstractControllerService implements StringLookupService {
+ private static final AtomicLong lookupCounter = new AtomicLong(0L);
+
+ @Override
+ protected PropertyDescriptor
getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) {
+ return new PropertyDescriptor.Builder()
+ .name(propertyDescriptorName)
+ .addValidator(Validator.VALID)
+ .dynamic(true)
+ .build();
+ }
+
+ @Override
+ public Optional<String> lookup(final Map<String, Object> coordinates)
throws LookupFailureException {
+ lookupCounter.incrementAndGet();
+ return Optional.of("mock-value");
+ }
+
+ @Override
+ public Set<String> getRequiredKeys() {
+ return Set.of("key");
+ }
+ }
+}
diff --git
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/resources/flows/Generate_and_Update.json
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/resources/flows/Generate_and_Update.json
index 686247011e..d4f5fc798e 100644
---
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/resources/flows/Generate_and_Update.json
+++
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/resources/flows/Generate_and_Update.json
@@ -1 +1,393 @@
-{"flowContents":{"identifier":"cdf7f935-0045-3c88-bdad-fd4e34105756","instanceIdentifier":"3a87b58f-019a-1000-30d2-87dfdd9816aa","name":"Generate
and
Update","comments":"","position":{"x":-1124.0,"y":65.5},"processGroups":[],"remoteProcessGroups":[],"processors":[{"identifier":"4d4160b1-736c-3be4-bc3a-84fc6eb4ad2a","instanceIdentifier":"3a87db5f-019a-1000-75df-90e055f21fc9","name":"UpdateAttribute","comments":"","position":{"x":-358.0,"y":-106.5},"type":"org.apache.nifi.processors.attrib
[...]
\ No newline at end of file
+{
+ "flowContents": {
+ "identifier": "1800c04e-f9b9-3293-bfc7-b35f43e0706c",
+ "instanceIdentifier": "4f8ca484-019c-1000-955f-68c5defeb22b",
+ "name": "Generate_and_Update",
+ "comments": "",
+ "position": {
+ "x": -1254.0,
+ "y": -437.70139741897583
+ },
+ "processGroups": [],
+ "remoteProcessGroups": [],
+ "processors": [
+ {
+ "identifier": "4d4160b1-736c-3be4-bc3a-84fc6eb4ad2a",
+ "instanceIdentifier": "499f9781-71c5-367f-aa17-5441bff29de9",
+ "name": "UpdateAttribute",
+ "comments": "",
+ "position": {
+ "x": -360.0,
+ "y": 24.0
+ },
+ "type":
"org.apache.nifi.processors.attributes.UpdateAttribute",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-update-attribute-nar",
+ "version": "2026.1.20.21-SNAPSHOT"
+ },
+ "properties": {
+ "Delete Attributes Expression": null,
+ "Store State": "Do not store state",
+ "Cache Value Lookup Cache Size": "100",
+ "Stateful Variables Initial Value": null
+ },
+ "propertyDescriptors": {
+ "Delete Attributes Expression": {
+ "name": "Delete Attributes Expression",
+ "displayName": "Delete Attributes Expression",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Store State": {
+ "name": "Store State",
+ "displayName": "Store State",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Cache Value Lookup Cache Size": {
+ "name": "Cache Value Lookup Cache Size",
+ "displayName": "Cache Value Lookup Cache Size",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Stateful Variables Initial Value": {
+ "name": "Stateful Variables Initial Value",
+ "displayName": "Stateful Variables Initial Value",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "style": {},
+ "schedulingPeriod": "0 sec",
+ "schedulingStrategy": "TIMER_DRIVEN",
+ "executionNode": "ALL",
+ "penaltyDuration": "30 sec",
+ "yieldDuration": "1 sec",
+ "bulletinLevel": "WARN",
+ "runDurationMillis": 25,
+ "concurrentlySchedulableTaskCount": 1,
+ "autoTerminatedRelationships": [
+ "success"
+ ],
+ "scheduledState": "ENABLED",
+ "retryCount": 10,
+ "retriedRelationships": [],
+ "backoffMechanism": "PENALIZE_FLOWFILE",
+ "maxBackoffPeriod": "10 mins",
+ "componentType": "PROCESSOR",
+ "groupIdentifier": "1800c04e-f9b9-3293-bfc7-b35f43e0706c"
+ },
+ {
+ "identifier": "54ab8572-0e10-39a5-b553-931f9c253023",
+ "instanceIdentifier": "6d6b9cd3-6d31-330a-40f9-185959ad1c78",
+ "name": "GenerateFlowFile",
+ "comments": "",
+ "position": {
+ "x": -360.0,
+ "y": -371.5
+ },
+ "type": "org.apache.nifi.processors.standard.GenerateFlowFile",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-nar",
+ "version": "2026.1.20.21-SNAPSHOT"
+ },
+ "properties": {
+ "File Size": "0B",
+ "Batch Size": "1",
+ "Unique FlowFiles": "false",
+ "Mime Type": null,
+ "Custom Text": null,
+ "Character Set": "UTF-8",
+ "Data Format": "Text",
+ "key": "test.key"
+ },
+ "propertyDescriptors": {
+ "File Size": {
+ "name": "File Size",
+ "displayName": "File Size",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Batch Size": {
+ "name": "Batch Size",
+ "displayName": "Batch Size",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Unique FlowFiles": {
+ "name": "Unique FlowFiles",
+ "displayName": "Unique FlowFiles",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Mime Type": {
+ "name": "Mime Type",
+ "displayName": "Mime Type",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Custom Text": {
+ "name": "Custom Text",
+ "displayName": "Custom Text",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Character Set": {
+ "name": "Character Set",
+ "displayName": "Character Set",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Data Format": {
+ "name": "Data Format",
+ "displayName": "Data Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "key": {
+ "name": "key",
+ "displayName": "key",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": true
+ }
+ },
+ "style": {},
+ "schedulingPeriod": "0 sec",
+ "schedulingStrategy": "TIMER_DRIVEN",
+ "executionNode": "ALL",
+ "penaltyDuration": "30 sec",
+ "yieldDuration": "1 sec",
+ "bulletinLevel": "WARN",
+ "runDurationMillis": 0,
+ "concurrentlySchedulableTaskCount": 1,
+ "autoTerminatedRelationships": [],
+ "scheduledState": "ENABLED",
+ "retryCount": 10,
+ "retriedRelationships": [],
+ "backoffMechanism": "PENALIZE_FLOWFILE",
+ "maxBackoffPeriod": "10 mins",
+ "componentType": "PROCESSOR",
+ "groupIdentifier": "1800c04e-f9b9-3293-bfc7-b35f43e0706c"
+ },
+ {
+ "identifier": "34c1ac1b-1f21-3fd6-a734-726d5b142b7a",
+ "instanceIdentifier": "4f8d0670-019c-1000-1ac6-c81ef75a70d0",
+ "name": "LookupAttribute",
+ "comments": "",
+ "position": {
+ "x": -360.0,
+ "y": -168.0
+ },
+ "type": "org.apache.nifi.processors.standard.LookupAttribute",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-nar",
+ "version": "2026.1.20.21-SNAPSHOT"
+ },
+ "properties": {
+ "Lookup Service": "b013f870-aee8-3cc4-b022-ac385ded928d",
+ "test.attribute": "${key}",
+ "Include Empty Values": "true"
+ },
+ "propertyDescriptors": {
+ "Lookup Service": {
+ "name": "Lookup Service",
+ "displayName": "Lookup Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "test.attribute": {
+ "name": "test.attribute",
+ "displayName": "test.attribute",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": true
+ },
+ "Include Empty Values": {
+ "name": "Include Empty Values",
+ "displayName": "Include Empty Values",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "style": {},
+ "schedulingPeriod": "0 sec",
+ "schedulingStrategy": "TIMER_DRIVEN",
+ "executionNode": "ALL",
+ "penaltyDuration": "30 sec",
+ "yieldDuration": "1 sec",
+ "bulletinLevel": "WARN",
+ "runDurationMillis": 0,
+ "concurrentlySchedulableTaskCount": 1,
+ "autoTerminatedRelationships": [
+ "failure"
+ ],
+ "scheduledState": "ENABLED",
+ "retryCount": 10,
+ "retriedRelationships": [],
+ "backoffMechanism": "PENALIZE_FLOWFILE",
+ "maxBackoffPeriod": "10 mins",
+ "componentType": "PROCESSOR",
+ "groupIdentifier": "1800c04e-f9b9-3293-bfc7-b35f43e0706c"
+ }
+ ],
+ "inputPorts": [],
+ "outputPorts": [],
+ "connections": [
+ {
+ "identifier": "893ad2c9-4a07-3447-b772-6b7149cfd6c1",
+ "instanceIdentifier": "5b9c96a4-7982-3746-2937-45511fb20c96",
+ "name": "",
+ "source": {
+ "id": "54ab8572-0e10-39a5-b553-931f9c253023",
+ "type": "PROCESSOR",
+ "groupId": "1800c04e-f9b9-3293-bfc7-b35f43e0706c",
+ "name": "GenerateFlowFile",
+ "comments": "",
+ "instanceIdentifier":
"6d6b9cd3-6d31-330a-40f9-185959ad1c78"
+ },
+ "destination": {
+ "id": "34c1ac1b-1f21-3fd6-a734-726d5b142b7a",
+ "type": "PROCESSOR",
+ "groupId": "1800c04e-f9b9-3293-bfc7-b35f43e0706c",
+ "name": "LookupAttribute",
+ "comments": "",
+ "instanceIdentifier":
"4f8d0670-019c-1000-1ac6-c81ef75a70d0"
+ },
+ "labelIndex": 0,
+ "zIndex": 1,
+ "selectedRelationships": [
+ "success"
+ ],
+ "backPressureObjectThreshold": 10000,
+ "backPressureDataSizeThreshold": "1 GB",
+ "flowFileExpiration": "0 sec",
+ "prioritizers": [],
+ "bends": [],
+ "loadBalanceStrategy": "DO_NOT_LOAD_BALANCE",
+ "partitioningAttribute": "",
+ "loadBalanceCompression": "DO_NOT_COMPRESS",
+ "componentType": "CONNECTION",
+ "groupIdentifier": "1800c04e-f9b9-3293-bfc7-b35f43e0706c"
+ },
+ {
+ "identifier": "f63459de-cf1f-3773-8f93-405518e085e4",
+ "instanceIdentifier": "4f93c5e2-019c-1000-2bea-06d8d611d5f6",
+ "name": "",
+ "source": {
+ "id": "34c1ac1b-1f21-3fd6-a734-726d5b142b7a",
+ "type": "PROCESSOR",
+ "groupId": "1800c04e-f9b9-3293-bfc7-b35f43e0706c",
+ "name": "LookupAttribute",
+ "comments": "",
+ "instanceIdentifier":
"4f8d0670-019c-1000-1ac6-c81ef75a70d0"
+ },
+ "destination": {
+ "id": "4d4160b1-736c-3be4-bc3a-84fc6eb4ad2a",
+ "type": "PROCESSOR",
+ "groupId": "1800c04e-f9b9-3293-bfc7-b35f43e0706c",
+ "name": "UpdateAttribute",
+ "comments": "",
+ "instanceIdentifier":
"499f9781-71c5-367f-aa17-5441bff29de9"
+ },
+ "labelIndex": 0,
+ "zIndex": 2,
+ "selectedRelationships": [
+ "matched",
+ "unmatched"
+ ],
+ "backPressureObjectThreshold": 10000,
+ "backPressureDataSizeThreshold": "1 GB",
+ "flowFileExpiration": "0 sec",
+ "prioritizers": [],
+ "bends": [],
+ "loadBalanceStrategy": "DO_NOT_LOAD_BALANCE",
+ "partitioningAttribute": "",
+ "loadBalanceCompression": "DO_NOT_COMPRESS",
+ "componentType": "CONNECTION",
+ "groupIdentifier": "1800c04e-f9b9-3293-bfc7-b35f43e0706c"
+ }
+ ],
+ "labels": [],
+ "funnels": [],
+ "controllerServices": [
+ {
+ "identifier": "b013f870-aee8-3cc4-b022-ac385ded928d",
+ "instanceIdentifier": "4f8d53d4-019c-1000-d376-abfaa091dc37",
+ "name": "SimpleKeyValueLookupService",
+ "comments": "",
+ "type": "org.apache.nifi.lookup.SimpleKeyValueLookupService",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-lookup-services-nar",
+ "version": "2026.1.20.21-SNAPSHOT"
+ },
+ "properties": {
+ "test.key": "Test Value"
+ },
+ "propertyDescriptors": {
+ "test.key": {
+ "name": "test.key",
+ "displayName": "test.key",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": true
+ }
+ },
+ "controllerServiceApis": [
+ {
+ "type": "org.apache.nifi.lookup.LookupService",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-services-api-nar",
+ "version": "2026.1.20.21-SNAPSHOT"
+ }
+ },
+ {
+ "type": "org.apache.nifi.lookup.StringLookupService",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-services-api-nar",
+ "version": "2026.1.20.21-SNAPSHOT"
+ }
+ }
+ ],
+ "scheduledState": "DISABLED",
+ "bulletinLevel": "WARN",
+ "componentType": "CONTROLLER_SERVICE",
+ "groupIdentifier": "1800c04e-f9b9-3293-bfc7-b35f43e0706c"
+ }
+ ],
+ "defaultFlowFileExpiration": "0 sec",
+ "defaultBackPressureObjectThreshold": 10000,
+ "defaultBackPressureDataSizeThreshold": "1 GB",
+ "scheduledState": "ENABLED",
+ "executionEngine": "INHERITED",
+ "maxConcurrentTasks": 1,
+ "statelessFlowTimeout": "1 min",
+ "flowFileConcurrency": "UNBOUNDED",
+ "flowFileOutboundPolicy": "STREAM_WHEN_AVAILABLE",
+ "componentType": "PROCESS_GROUP"
+ },
+ "externalControllerServices": {},
+ "parameterContexts": {},
+ "flowEncodingVersion": "1.0",
+ "parameterProviders": {},
+ "latest": false
+}
\ No newline at end of file
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 d2b7e79f0b..4cea7502b6 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
@@ -32,6 +32,7 @@ import org.apache.nifi.nar.ExtensionMapping;
import org.apache.nifi.nar.NarClassLoaders;
import org.apache.nifi.nar.NarUnpackMode;
import org.apache.nifi.nar.NarUnpacker;
+import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.nar.SystemBundle;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.util.NiFiProperties;
@@ -61,13 +62,14 @@ public class StandardConnectorTestRunner implements
ConnectorTestRunner, Closeab
throw new RuntimeException("Failed to bootstrap
ConnectorTestRunner", e);
}
- // It is important that we register the processor mocks before
instantiating the connector.
+ // It is important that we register the processor and controller
service mocks before instantiating the connector.
// Otherwise, the call to instantiateConnector will initialize the
Connector, which may update the flow.
- // If the flow is updated before the processor mocks are registered,
the Processors will be created without
- // using the mocks. Subsequent updates to the flow will not replace
the Processors already created because
- // these are not recognized as updates to the flow, since the
framework assumes that the type of a Processor
+ // If the flow is updated before the mocks are registered, the
components will be created without
+ // using the mocks. Subsequent updates to the flow will not replace
the components already created because
+ // these are not recognized as updates to the flow, since the
framework assumes that the type of a component
// with a given ID does not change.
builder.processorMocks.forEach(mockServer::mockProcessor);
+
builder.controllerServiceMocks.forEach(mockServer::mockControllerService);
mockServer.instantiateConnector(builder.connectorClassName);
}
@@ -211,6 +213,7 @@ public class StandardConnectorTestRunner implements
ConnectorTestRunner, Closeab
private String connectorClassName;
private File narLibraryDirectory;
private final Map<String, Class<? extends Processor>> processorMocks =
new HashMap<>();
+ private final Map<String, Class<? extends ControllerService>>
controllerServiceMocks = new HashMap<>();
public Builder connectorClassName(final String connectorClassName) {
this.connectorClassName = connectorClassName;
@@ -227,6 +230,11 @@ public class StandardConnectorTestRunner implements
ConnectorTestRunner, Closeab
return this;
}
+ public Builder mockControllerService(final String
controllerServiceType, final Class<? extends ControllerService>
mockControllerServiceClass) {
+ controllerServiceMocks.put(controllerServiceType,
mockControllerServiceClass);
+ return this;
+ }
+
public StandardConnectorTestRunner build() {
if (!narLibraryDirectory.exists() ||
!narLibraryDirectory.isDirectory()) {
throw new IllegalArgumentException("NAR file does not exist or
is not a directory: " + narLibraryDirectory.getAbsolutePath());