This is an automated email from the ASF dual-hosted git repository.
kdoran 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 150bdf5e534 NIFI-15604: Expose VersionedExternalFlow that represents
the Active/Working flow from ConnectorTestRunner (#10901)
150bdf5e534 is described below
commit 150bdf5e5349cb9416a4d737d4ee8200796295f4
Author: Mark Payne <[email protected]>
AuthorDate: Tue Mar 10 10:53:45 2026 -0400
NIFI-15604: Expose VersionedExternalFlow that represents the Active/Working
flow from ConnectorTestRunner (#10901)
Signed-off-by: Kevin Doran <[email protected]>
---
.cursor/rules/testing-standards.mdc | 12 +++
.../mock/connector/server/ConnectorTestRunner.java | 20 +++++
.../server/StandardConnectorMockServer.java | 87 ++++++++++++++++++++++
.../mock/connectors/tests/CreateConnectorIT.java | 31 ++++++++
.../connector/StandardConnectorTestRunner.java | 12 +++
5 files changed, 162 insertions(+)
diff --git a/.cursor/rules/testing-standards.mdc
b/.cursor/rules/testing-standards.mdc
index 09a543a967d..6cb98f4853c 100644
--- a/.cursor/rules/testing-standards.mdc
+++ b/.cursor/rules/testing-standards.mdc
@@ -47,6 +47,18 @@ creating or manipulating automated tests.
complexity. Just call the method, and if it throws an Exception, the test
will fail. It
is assumed by default that each line does not throw an Exception.
+8. Avoid providing messages in assert statements when they do not offer
benefits over the default message.
+ For example, rather than:
+```
+ assertEquals(3, processors.size(), "Expected 3 processors in the initial
flow");
+```
+ Just use:
+```
+ assertEquals(3, processors.size());
+```
+Because the expectation is already clear from assertion, and this message is
actually harmful because it hides the size
+of the `processors` Collection.
+
## General Testing Philosophy
- Unit tests should be used to verify any sufficiently complex method in a
class. We should
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 cbe24db2aac..669f7be22d2 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
@@ -24,6 +24,7 @@ import
org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowUpdateException;
import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StepConfiguration;
+import org.apache.nifi.flow.VersionedExternalFlow;
import java.io.Closeable;
import java.io.File;
@@ -80,4 +81,23 @@ public interface ConnectorTestRunner extends Closeable {
}
List<DescribedValue> fetchAllowableValues(String stepName, String
propertyName);
+
+ /*
+ * Returns a {@link VersionedExternalFlow} representing the current state
of the Active Flow Context.
+ * The Active Flow Context is the flow that is currently running (or most
recently ran) in the Connector.
+ * This is useful for making assertions about how the flow is configured
after updates have been applied.
+ *
+ * @return the VersionedExternalFlow for the Active Flow Context
+ */
+ VersionedExternalFlow getActiveFlowSnapshot();
+
+ /**
+ * Returns a {@link VersionedExternalFlow} representing the current state
of the Working Flow Context.
+ * The Working Flow Context is the flow that reflects configuration
changes that have been made
+ * but not yet applied. This is useful for making assertions about how the
flow will be configured
+ * once the update is applied.
+ *
+ * @return the VersionedExternalFlow for the Working Flow Context
+ */
+ VersionedExternalFlow getWorkingFlowSnapshot();
}
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 1049a6a511e..d491d0e6c19 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
@@ -36,6 +36,7 @@ import
org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.FrameworkFlowContext;
import org.apache.nifi.components.connector.GhostConnector;
import org.apache.nifi.components.connector.SecretReference;
import
org.apache.nifi.components.connector.StandaloneConnectorRequestReplicator;
@@ -59,9 +60,20 @@ 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.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedParameter;
+import org.apache.nifi.flow.VersionedParameterContext;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.groups.ProcessGroup;
import
org.apache.nifi.mock.connector.server.secrets.ConnectorTestRunnerSecretsManager;
import org.apache.nifi.nar.ExtensionMapping;
+import org.apache.nifi.parameter.Parameter;
+import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.processor.Processor;
+import org.apache.nifi.registry.flow.mapping.ComponentIdLookup;
+import org.apache.nifi.registry.flow.mapping.FlowMappingOptions;
+import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
+import org.apache.nifi.registry.flow.mapping.VersionedComponentStateLookup;
import org.apache.nifi.reporting.BulletinRepository;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.validation.RuleViolationsManager;
@@ -84,6 +96,7 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -425,6 +438,80 @@ public class StandardConnectorMockServer implements
ConnectorMockServer {
.toList();
}
+ @Override
+ public VersionedExternalFlow getActiveFlowSnapshot() {
+ final FrameworkFlowContext activeFlowContext =
connectorNode.getActiveFlowContext();
+ if (activeFlowContext == null) {
+ throw new IllegalStateException("Active Flow Context is not
available. The Connector may not have been initialized or may not have an
initial flow.");
+ }
+ return createFlowSnapshot(activeFlowContext);
+ }
+
+ @Override
+ public VersionedExternalFlow getWorkingFlowSnapshot() {
+ final FrameworkFlowContext workingFlowContext =
connectorNode.getWorkingFlowContext();
+ if (workingFlowContext == null) {
+ throw new IllegalStateException("Working Flow Context is not
available. No configuration changes may have been made since the last update
was applied.");
+ }
+ return createFlowSnapshot(workingFlowContext);
+ }
+
+ private VersionedExternalFlow createFlowSnapshot(final
FrameworkFlowContext flowContext) {
+ final ProcessGroup processGroup = flowContext.getManagedProcessGroup();
+
+ final FlowMappingOptions flowMappingOptions = new
FlowMappingOptions.Builder()
+ .mapSensitiveConfiguration(false)
+ .mapPropertyDescriptors(true)
+ .stateLookup(VersionedComponentStateLookup.ENABLED_OR_DISABLED)
+ .sensitiveValueEncryptor(value -> value)
+ .componentIdLookup(ComponentIdLookup.VERSIONED_OR_GENERATE)
+ .mapInstanceIdentifiers(true)
+ .mapControllerServiceReferencesToVersionedId(true)
+ .mapFlowRegistryClientId(true)
+ .mapAssetReferences(true)
+ .build();
+
+ final VersionedComponentFlowMapper flowMapper = new
VersionedComponentFlowMapper(extensionManager, flowMappingOptions);
+ final VersionedProcessGroup versionedGroup =
flowMapper.mapProcessGroup(
+ processGroup, flowController.getControllerServiceProvider(),
flowController.getFlowManager(), true);
+
+ final VersionedExternalFlow externalFlow = new VersionedExternalFlow();
+ externalFlow.setFlowContents(versionedGroup);
+
+ final ParameterContext parameterContext =
processGroup.getParameterContext();
+ if (parameterContext != null) {
+ final Map<String, VersionedParameterContext> parameterContexts =
new HashMap<>();
+ final VersionedParameterContext versionedParameterContext =
createVersionedParameterContext(parameterContext);
+ parameterContexts.put(versionedParameterContext.getName(),
versionedParameterContext);
+ externalFlow.setParameterContexts(parameterContexts);
+ }
+
+ return externalFlow;
+ }
+
+ private VersionedParameterContext createVersionedParameterContext(final
ParameterContext parameterContext) {
+ final VersionedParameterContext versionedParameterContext = new
VersionedParameterContext();
+ versionedParameterContext.setName(parameterContext.getName());
+
versionedParameterContext.setDescription(parameterContext.getDescription());
+
versionedParameterContext.setIdentifier(parameterContext.getIdentifier());
+
+ final Set<VersionedParameter> versionedParameters = new
LinkedHashSet<>();
+ for (final Parameter parameter :
parameterContext.getParameters().values()) {
+ final VersionedParameter versionedParameter = new
VersionedParameter();
+ versionedParameter.setName(parameter.getDescriptor().getName());
+
versionedParameter.setDescription(parameter.getDescriptor().getDescription());
+
versionedParameter.setSensitive(parameter.getDescriptor().isSensitive());
+
versionedParameter.setProvided(parameter.getDescriptor().isSensitive());
+ if (!parameter.getDescriptor().isSensitive()) {
+ versionedParameter.setValue(parameter.getValue());
+ }
+ versionedParameters.add(versionedParameter);
+ }
+ versionedParameterContext.setParameters(versionedParameters);
+
+ return versionedParameterContext;
+ }
+
@Override
public void mockProcessor(final String processorType, final Class<?
extends Processor> mockProcessorClass) {
mockExtensionMapper.mockProcessor(processorType,
mockProcessorClass.getName());
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 82e1a8e8cd5..18884a8d830 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
@@ -18,6 +18,10 @@
package org.apache.nifi.mock.connectors.tests;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.flow.VersionedControllerService;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.flow.VersionedProcessor;
import org.apache.nifi.mock.connector.StandardConnectorTestRunner;
import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
import org.junit.jupiter.api.Test;
@@ -25,6 +29,8 @@ import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.util.List;
+import java.util.Optional;
+import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -38,6 +44,22 @@ public class CreateConnectorIT {
.narLibraryDirectory(new File("target/libDir"))
.build()) {
+ // Verify the active flow snapshot reflects the initial flow
loaded from Generate_and_Update.json
+ final VersionedExternalFlow activeFlow =
testRunner.getActiveFlowSnapshot();
+ final VersionedProcessGroup rootGroup =
activeFlow.getFlowContents();
+
+ final Set<VersionedProcessor> processors =
rootGroup.getProcessors();
+ assertEquals(3, processors.size());
+ assertTrue(findProcessorByType(processors,
"org.apache.nifi.processors.standard.GenerateFlowFile").isPresent());
+ assertTrue(findProcessorByType(processors,
"org.apache.nifi.processors.standard.LookupAttribute").isPresent());
+ assertTrue(findProcessorByType(processors,
"org.apache.nifi.processors.attributes.UpdateAttribute").isPresent());
+
+ assertEquals(2, rootGroup.getConnections().size());
+
+ final Set<VersionedControllerService> controllerServices =
rootGroup.getControllerServices();
+ assertEquals(1, controllerServices.size());
+ assertEquals("org.apache.nifi.lookup.SimpleKeyValueLookupService",
controllerServices.iterator().next().getType());
+
testRunner.startConnector();
testRunner.stopConnector();
}
@@ -58,4 +80,13 @@ public class CreateConnectorIT {
assertTrue(message.contains("com.example.nonexistent.MissingProcessor"),
"Expected exception message to contain missing processor type but was: " +
message);
}
}
+
+ private Optional<VersionedProcessor> findProcessorByType(final
Set<VersionedProcessor> processors, final String type) {
+ for (final VersionedProcessor processor : processors) {
+ if (type.equals(processor.getType())) {
+ return Optional.of(processor);
+ }
+ }
+ return Optional.empty();
+ }
}
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 7fbfffb49e4..7153f8d868b 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
@@ -27,6 +27,7 @@ import
org.apache.nifi.components.connector.FlowUpdateException;
import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.flow.VersionedExternalFlow;
import org.apache.nifi.mock.connector.server.ConnectorConfigVerificationResult;
import org.apache.nifi.mock.connector.server.ConnectorMockServer;
import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
@@ -222,10 +223,21 @@ public class StandardConnectorTestRunner implements
ConnectorTestRunner, Closeab
return mockServer.getHttpPort();
}
+ @Override
public List<DescribedValue> fetchAllowableValues(final String stepName,
final String propertyName) {
return mockServer.fetchAllowableValues(stepName, propertyName);
}
+ @Override
+ public VersionedExternalFlow getActiveFlowSnapshot() {
+ return mockServer.getActiveFlowSnapshot();
+ }
+
+ @Override
+ public VersionedExternalFlow getWorkingFlowSnapshot() {
+ return mockServer.getWorkingFlowSnapshot();
+ }
+
public static class Builder {
private String connectorClassName;