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;

Reply via email to