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-api.git

commit 635a439e54cd0efb87dec43cbc8656a261952bd5
Author: Mark Payne <[email protected]>
AuthorDate: Tue Jan 6 12:05:35 2026 -0500

    NIFI-15428: If non-existent property value is set, connector should be 
invalid. (#43)
---
 .../components/connector/AbstractConnector.java    | 27 ++++++++++-
 .../connector/TestAbstractConnector.java           | 56 +++++++++++++++++++++-
 2 files changed, 81 insertions(+), 2 deletions(-)

diff --git 
a/src/main/java/org/apache/nifi/components/connector/AbstractConnector.java 
b/src/main/java/org/apache/nifi/components/connector/AbstractConnector.java
index 49d8059..8045bc6 100644
--- a/src/main/java/org/apache/nifi/components/connector/AbstractConnector.java
+++ b/src/main/java/org/apache/nifi/components/connector/AbstractConnector.java
@@ -156,7 +156,7 @@ public abstract class AbstractConnector implements 
Connector {
      * @param flowContext the FlowContext to use for drainage
      * @return a CompletableFuture that will be completed when drainage is 
complete
      */
-    protected CompletableFuture<Void> drainFlowFiles(final FlowContext 
flowContext) {
+    public CompletableFuture<Void> drainFlowFiles(final FlowContext 
flowContext) {
         final CompletableFuture<Void> result = new CompletableFuture<>();
         final QueueSize initialQueueSize = 
flowContext.getRootGroup().getQueueSize();
         if (initialQueueSize.getObjectCount() == 0) {
@@ -454,7 +454,32 @@ public abstract class AbstractConnector implements 
Connector {
         final String stepName = configurationStep.getName();
         final List<ValidationResult> results = new ArrayList<>();
 
+        // Build a set of all valid property names defined by this 
configuration step
         final List<ConnectorPropertyGroup> propertyGroups = 
configurationStep.getPropertyGroups();
+        final Set<String> validPropertyNames = new HashSet<>();
+        for (final ConnectorPropertyGroup propertyGroup : propertyGroups) {
+            final List<ConnectorPropertyDescriptor> descriptors = 
propertyGroup.getProperties();
+            for (final ConnectorPropertyDescriptor descriptor : descriptors) {
+                validPropertyNames.add(descriptor.getName());
+            }
+        }
+
+        // Check for any properties that have been set but are not defined by 
this configuration step
+        final Set<String> configuredPropertyNames = 
configurationContext.getPropertyNames(stepName);
+        for (final String configuredPropertyName : configuredPropertyNames) {
+            if (!validPropertyNames.contains(configuredPropertyName)) {
+                final String configuredValue = 
configurationContext.getProperty(stepName, configuredPropertyName).getValue();
+
+                final ValidationResult invalidResult = new 
ValidationResult.Builder()
+                    .valid(false)
+                    .input(configuredValue)
+                    .subject(configuredPropertyName)
+                    .explanation("Property '" + configuredPropertyName + "' is 
not defined by Connector for Configuration Step '" + stepName + "'")
+                    .build();
+                results.add(invalidResult);
+            }
+        }
+
         for (final ConnectorPropertyGroup propertyGroup : propertyGroups) {
             final List<ConnectorPropertyDescriptor> descriptors = 
propertyGroup.getProperties();
             final Map<String, ConnectorPropertyDescriptor> descriptorMap = 
descriptors.stream()
diff --git 
a/src/test/java/org/apache/nifi/components/connector/TestAbstractConnector.java 
b/src/test/java/org/apache/nifi/components/connector/TestAbstractConnector.java
index bb77976..20d0534 100644
--- 
a/src/test/java/org/apache/nifi/components/connector/TestAbstractConnector.java
+++ 
b/src/test/java/org/apache/nifi/components/connector/TestAbstractConnector.java
@@ -38,6 +38,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -838,10 +839,63 @@ public class TestAbstractConnector {
 
         final List<ValidationResult> results = connector.validate(flowContext, 
validationContext);
 
-        assertTrue(results.isEmpty(), "Step 2 should be skipped because 
Property With Default has a default value");
+        assertTrue(results.isEmpty(), "Step 2 should be skipped because 
Property 'With Default' has a default value");
         assertTrue(connector.isCustomValidateCalled());
     }
 
+    @Test
+    void testValidateWithUndefinedPropertiesConfigured() {
+        final ConnectorPropertyDescriptor validProperty = new 
ConnectorPropertyDescriptor.Builder()
+            .name("Valid Property")
+            .description("A valid property")
+            .required(true)
+            .build();
+
+        final ConnectorPropertyGroup propertyGroup = 
ConnectorPropertyGroup.builder()
+            .name("Test Group")
+            .addProperty(validProperty)
+            .build();
+
+        final ConfigurationStep configStep = new ConfigurationStep.Builder()
+            .name("Test Step")
+            .propertyGroups(List.of(propertyGroup))
+            .build();
+
+        connector.setConfigurationSteps(List.of(configStep));
+
+        // Mock the configuration context to return property names including 
multiple undefined properties
+        when(configurationContext.getPropertyNames("Test Step"))
+            .thenReturn(Set.of("Valid Property", "undefined.one", 
"undefined.two"));
+
+        final ConnectorPropertyValue validValue = 
mock(ConnectorPropertyValue.class);
+        when(validValue.getValue()).thenReturn("valid-value");
+        when(validValue.isSet()).thenReturn(true);
+        when(configurationContext.getProperty("Test Step", "Valid 
Property")).thenReturn(validValue);
+
+        final ConnectorPropertyValue undefinedValue = 
mock(ConnectorPropertyValue.class);
+        when(undefinedValue.getValue()).thenReturn("some-value");
+        when(undefinedValue.isSet()).thenReturn(true);
+        when(configurationContext.getProperty("Test Step", 
"undefined.one")).thenReturn(undefinedValue);
+        when(configurationContext.getProperty("Test Step", 
"undefined.two")).thenReturn(undefinedValue);
+
+        final List<ValidationResult> results = connector.validate(flowContext, 
validationContext);
+
+        assertEquals(2, results.size());
+
+        // Verify both undefined properties are reported as invalid
+        final List<String> invalidSubjects = results.stream()
+            .map(ValidationResult::getSubject)
+            .toList();
+        assertTrue(invalidSubjects.contains("undefined.one"));
+        assertTrue(invalidSubjects.contains("undefined.two"));
+
+        for (final ValidationResult result : results) {
+            assertFalse(result.isValid());
+        }
+
+        assertFalse(connector.isCustomValidateCalled());
+    }
+
     private static class TestableAbstractConnector extends AbstractConnector {
         private List<ConfigurationStep> configurationSteps = 
Collections.emptyList();
         private Collection<ValidationResult> customValidationResults = 
Collections.emptyList();

Reply via email to