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();
