This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 7fa61fcfe2 Issue #8417 : Use the unit test database replacement in 
Database Lookup (#8520)
7fa61fcfe2 is described below

commit 7fa61fcfe2c6dc2dc8d64e9d8f703d0bf3264d80
Author: Matt Casters <[email protected]>
AuthorDate: Tue Sep 22 10:03:47 2026 +0200

    Issue #8417 : Use the unit test database replacement in Database Lookup 
(#8520)
---
 .../ROOT/pages/pipeline/pipeline-unit-testing.adoc |   2 +-
 plugins/misc/testing/pom.xml                       |  18 ++
 .../hop/testing/xp/PipelineMetaModifier.java       |  48 +++++-
 .../hop/testing/xp/PipelineMetaModifierTest.java   | 186 +++++++++++++++++++++
 .../transforms/addsequence/AddSequenceMeta.java    |   4 +-
 .../gettablenames/GetTableNamesMeta.java           |   4 +-
 .../transforms/jdbcmetadata/JdbcMetadataMeta.java  |   4 +-
 .../SynchronizeAfterMergeMeta.java                 |   4 +-
 8 files changed, 260 insertions(+), 10 deletions(-)

diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/pipeline-unit-testing.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/pipeline-unit-testing.adoc
index d60c787d58..5094c5ec5b 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/pipeline-unit-testing.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/pipeline-unit-testing.adoc
@@ -145,7 +145,7 @@ By default, you should see the active pipeline filename 
here.
 |Test pipeline filename (Optional)|the filename to use for this unit test.
 |Base test path (or use HOP_UNIT_TESTS_FOLDER)|the folder to store this unit 
test to.
 |Select this test automatically|default: false
-|Replace a database connection with another|specify a list of database 
connections for this pipeline that you'd like to swap out for a test-specific 
connection.
+|Replace a database connection with another|specify a list of database 
connections for this pipeline that you'd like to swap out for a test-specific 
connection. The swap rewrites those relational connection references on the 
in-memory test copy, including lookups, and does not change the project 
connection.
 |Variables|a list of variables to use in testing.
 |===
 
diff --git a/plugins/misc/testing/pom.xml b/plugins/misc/testing/pom.xml
index fc3b98b2f1..ff401fd372 100644
--- a/plugins/misc/testing/pom.xml
+++ b/plugins/misc/testing/pom.xml
@@ -47,6 +47,24 @@
             <artifactId>commons-csv</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.hop</groupId>
+            <artifactId>hop-databases-generic</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.hop</groupId>
+            <artifactId>hop-databases-h2</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.hop</groupId>
+            <artifactId>hop-transform-databaselookup</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git 
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/PipelineMetaModifier.java
 
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/PipelineMetaModifier.java
index fbfed30317..8b811642b0 100644
--- 
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/PipelineMetaModifier.java
+++ 
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/PipelineMetaModifier.java
@@ -20,7 +20,9 @@ package org.apache.hop.testing.xp;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import org.apache.hop.core.database.DatabaseMeta;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.logging.ILogChannel;
@@ -29,7 +31,9 @@ import org.apache.hop.core.plugins.TransformPluginType;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.IValueMeta;
 import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.metadata.api.HopMetadataPropertyType;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.metadata.util.HopMetadataPropertyWalker;
 import org.apache.hop.pipeline.PipelineHopMeta;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
@@ -76,8 +80,11 @@ public class PipelineMetaModifier {
     //
     copyPipelineMeta.setMetadataProvider(pipelineMeta.getMetadataProvider());
 
-    // Replace certain connections with another
+    // Connections are metadata loaded by name. Rewriting the name on the test 
copy is what
+    // transforms such as Database Lookup read at runtime. Do not call 
DatabaseMeta.replaceMeta():
+    // findDatabase() returns either a throwaway load or the shared metadata 
object.
     //
+    Map<String, String> connectionReplacements = new LinkedHashMap<>();
     for (PipelineUnitTestDatabaseReplacement dbReplacement : 
unitTest.getDatabaseReplacements()) {
       String sourceDatabaseName = 
variables.resolve(dbReplacement.getOriginalDatabaseName());
       String replacementDatabaseName =
@@ -99,16 +106,33 @@ public class PipelineMetaModifier {
                 + replacementDatabaseName
                 + "', can not be used to replace");
       }
+      connectionReplacements.put(sourceDatabaseName, replacementDatabaseName);
+    }
+
+    int replacedFields = 0;
+    if (!connectionReplacements.isEmpty()) {
+      for (TransformMeta transformMeta : copyPipelineMeta.getTransforms()) {
+        replacedFields +=
+            HopMetadataPropertyWalker.rewriteStrings(
+                transformMeta.getTransform(),
+                HopMetadataPropertyType.RDBMS_CONNECTION,
+                value -> replacementFor(variables, connectionReplacements, 
value));
+      }
+    }
 
-      if (log.isDetailed()) {
+    if (log.isDetailed()) {
+      for (Map.Entry<String, String> replacement : 
connectionReplacements.entrySet()) {
         log.logDetailed(
             "Replaced database connection '"
-                + sourceDatabaseName
+                + replacement.getKey()
                 + "' with connection '"
-                + replacementDatabaseName
+                + replacement.getValue()
                 + "'");
       }
-      sourceDatabaseMeta.replaceMeta(replacementDatabaseMeta);
+      if (!connectionReplacements.isEmpty()) {
+        log.logDetailed(
+            "Rewrote " + replacedFields + " relational connection reference(s) 
for this unit test");
+      }
     }
 
     // Replace all transforms with an Input Data Set marker with an Injector
@@ -160,6 +184,20 @@ public class PipelineMetaModifier {
     return copyPipelineMeta;
   }
 
+  /**
+   * Map a connection name to its replacement once. A later replacement of the 
replacement name does
+   * not chain. Variable expressions are resolved before matching, and a match 
is stored as the
+   * literal replacement name.
+   */
+  private static String replacementFor(
+      IVariables variables, Map<String, String> connectionReplacements, String 
value) {
+    if (value == null) {
+      return null;
+    }
+    String replacement = connectionReplacements.get(variables.resolve(value));
+    return replacement == null ? value : replacement;
+  }
+
   private void handleInputDataSet(
       ILogChannel log,
       PipelineUnitTestSetLocation inputLocation,
diff --git 
a/plugins/misc/testing/src/test/java/org/apache/hop/testing/xp/PipelineMetaModifierTest.java
 
b/plugins/misc/testing/src/test/java/org/apache/hop/testing/xp/PipelineMetaModifierTest.java
new file mode 100644
index 0000000000..db099ac6c6
--- /dev/null
+++ 
b/plugins/misc/testing/src/test/java/org/apache/hop/testing/xp/PipelineMetaModifierTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.hop.testing.xp;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.databases.generic.GenericDatabaseMeta;
+import org.apache.hop.metadata.api.IHopMetadataSerializer;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transforms.databaselookup.DatabaseLookupMeta;
+import org.apache.hop.testing.PipelineUnitTest;
+import org.apache.hop.testing.PipelineUnitTestDatabaseReplacement;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+class PipelineMetaModifierTest {
+
+  private static final String SQL_SERVER_DRIVER = 
"com.microsoft.sqlserver.jdbc.SQLServerDriver";
+  private static final String H2_DRIVER = "org.h2.Driver";
+
+  @BeforeAll
+  static void initHop() throws HopException {
+    HopEnvironment.init();
+  }
+
+  @Test
+  void replacesDatabaseLookupConnectionOnTheTestCopy() throws HopException {
+    IVariables variables = new Variables();
+    variables.setVariable("DB", "source-db");
+    MemoryMetadataProvider provider = new MemoryMetadataProvider();
+    DatabaseMeta source = genericConnection("source-db", "source.example", 
"source_db");
+    DatabaseMeta test = h2Connection("test-h2", "mem:lookup_data");
+    save(provider, source, test, genericConnection("other-db", 
"other.example", "other_db"));
+
+    PipelineMeta pipeline =
+        pipeline(
+            provider,
+            lookup("lookup", "source-db"),
+            lookup("other", "other-db"),
+            lookup("variable-lookup", "${DB}"));
+    PipelineUnitTest unitTest = new PipelineUnitTest();
+    unitTest
+        .getDatabaseReplacements()
+        .add(new PipelineUnitTestDatabaseReplacement("source-db", "test-h2"));
+
+    PipelineMeta copy =
+        new PipelineMetaModifier(variables, pipeline, unitTest)
+            .getTestPipeline(LogChannel.GENERAL, variables, provider);
+
+    assertEquals("test-h2", connection(copy, "lookup"));
+    DatabaseMeta resolved = copy.findDatabase(connection(copy, "lookup"), 
variables);
+    assertEquals("H2", resolved.getPluginId());
+    assertEquals("mem:lookup_data", resolved.getDatabaseName());
+    assertEquals(H2_DRIVER, resolved.getDriverClass(variables));
+
+    assertEquals("other-db", connection(copy, "other"));
+    assertEquals("test-h2", connection(copy, "variable-lookup"));
+    assertEquals("source-db", connection(pipeline, "lookup"));
+
+    DatabaseMeta sourceAfter = 
provider.getSerializer(DatabaseMeta.class).load("source-db");
+    assertEquals("source-db", sourceAfter.getName());
+    assertEquals("GENERIC", sourceAfter.getPluginId());
+    assertEquals(SQL_SERVER_DRIVER, sourceAfter.getDriverClass(variables));
+    assertEquals("source.example", sourceAfter.getHostname());
+  }
+
+  @Test
+  void replacementsDoNotChain() throws HopException {
+    IVariables variables = new Variables();
+    MemoryMetadataProvider provider = new MemoryMetadataProvider();
+    save(
+        provider,
+        genericConnection("source-db", "source.example", "source_db"),
+        h2Connection("test-h2", "mem:lookup_data"),
+        h2Connection("other-h2", "mem:other"));
+
+    PipelineMeta pipeline = pipeline(provider, lookup("lookup", "source-db"));
+    PipelineUnitTest unitTest = new PipelineUnitTest();
+    unitTest
+        .getDatabaseReplacements()
+        .add(new PipelineUnitTestDatabaseReplacement("source-db", "test-h2"));
+    unitTest
+        .getDatabaseReplacements()
+        .add(new PipelineUnitTestDatabaseReplacement("test-h2", "other-h2"));
+
+    PipelineMeta copy =
+        new PipelineMetaModifier(variables, pipeline, unitTest)
+            .getTestPipeline(LogChannel.GENERAL, variables, provider);
+
+    assertEquals("test-h2", connection(copy, "lookup"));
+  }
+
+  @Test
+  void missingReplacementDoesNotChangeTheSourceConnection() throws 
HopException {
+    IVariables variables = new Variables();
+    MemoryMetadataProvider provider = new MemoryMetadataProvider();
+    DatabaseMeta source = genericConnection("source-db", "source.example", 
"source_db");
+    save(provider, source);
+    PipelineMeta pipeline = pipeline(provider, lookup("lookup", "source-db"));
+    PipelineUnitTest unitTest = new PipelineUnitTest();
+    unitTest
+        .getDatabaseReplacements()
+        .add(new PipelineUnitTestDatabaseReplacement("source-db", "test-h2"));
+
+    HopException exception =
+        assertThrows(
+            HopException.class,
+            () ->
+                new PipelineMetaModifier(variables, pipeline, unitTest)
+                    .getTestPipeline(LogChannel.GENERAL, variables, provider));
+
+    assertTrue(exception.getMessage().contains("test-h2"));
+    assertEquals("source-db", source.getName());
+    assertEquals("GENERIC", source.getPluginId());
+    assertEquals(SQL_SERVER_DRIVER, source.getDriverClass(variables));
+    assertEquals("source-db", connection(pipeline, "lookup"));
+  }
+
+  private static PipelineMeta pipeline(MemoryMetadataProvider provider, 
TransformMeta... transforms)
+      throws HopException {
+    PipelineMeta pipelineMeta = new PipelineMeta();
+    pipelineMeta.setName("lookup-test");
+    pipelineMeta.setMetadataProvider(provider);
+    for (TransformMeta transform : transforms) {
+      pipelineMeta.addTransform(transform);
+    }
+    return pipelineMeta;
+  }
+
+  private static TransformMeta lookup(String name, String connection) {
+    DatabaseLookupMeta lookupMeta = new DatabaseLookupMeta();
+    lookupMeta.setConnection(connection);
+    return new TransformMeta("DBLookup", name, lookupMeta);
+  }
+
+  private static String connection(PipelineMeta pipelineMeta, String 
transformName) {
+    return ((DatabaseLookupMeta) 
pipelineMeta.findTransform(transformName).getTransform())
+        .getConnection();
+  }
+
+  private static void save(MemoryMetadataProvider provider, DatabaseMeta... 
connections)
+      throws HopException {
+    IHopMetadataSerializer<DatabaseMeta> serializer = 
provider.getSerializer(DatabaseMeta.class);
+    for (DatabaseMeta connection : connections) {
+      serializer.save(connection);
+    }
+  }
+
+  private static DatabaseMeta genericConnection(String name, String host, 
String database) {
+    DatabaseMeta databaseMeta =
+        new DatabaseMeta(name, "GENERIC", "Native", host, database, "1433", 
"sa", "secret");
+    databaseMeta
+        .getAttributes()
+        .put(GenericDatabaseMeta.ATTRIBUTE_CUSTOM_DRIVER_CLASS, 
SQL_SERVER_DRIVER);
+    return databaseMeta;
+  }
+
+  private static DatabaseMeta h2Connection(String name, String database) {
+    return new DatabaseMeta(name, "H2", "Native", "localhost", database, "", 
"sa", "");
+  }
+}
diff --git 
a/plugins/transforms/addsequence/src/main/java/org/apache/hop/pipeline/transforms/addsequence/AddSequenceMeta.java
 
b/plugins/transforms/addsequence/src/main/java/org/apache/hop/pipeline/transforms/addsequence/AddSequenceMeta.java
index 6cfe71782b..7038ea3873 100644
--- 
a/plugins/transforms/addsequence/src/main/java/org/apache/hop/pipeline/transforms/addsequence/AddSequenceMeta.java
+++ 
b/plugins/transforms/addsequence/src/main/java/org/apache/hop/pipeline/transforms/addsequence/AddSequenceMeta.java
@@ -34,6 +34,7 @@ import org.apache.hop.core.row.value.ValueMetaInteger;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.metadata.api.HopMetadataProperty;
+import org.apache.hop.metadata.api.HopMetadataPropertyType;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.BaseTransformMeta;
@@ -66,7 +67,8 @@ public class AddSequenceMeta extends 
BaseTransformMeta<AddSequence, AddSequenceD
 
   @HopMetadataProperty(
       key = "connection",
-      injectionKeyDescription = "AddSequenceMeta.Injection.Connection")
+      injectionKeyDescription = "AddSequenceMeta.Injection.Connection",
+      hopMetadataPropertyType = HopMetadataPropertyType.RDBMS_CONNECTION)
   private String connection;
 
   @HopMetadataProperty(
diff --git 
a/plugins/transforms/gettablenames/src/main/java/org/apache/hop/pipeline/transforms/gettablenames/GetTableNamesMeta.java
 
b/plugins/transforms/gettablenames/src/main/java/org/apache/hop/pipeline/transforms/gettablenames/GetTableNamesMeta.java
index bbd7989d3b..6163d1ed13 100644
--- 
a/plugins/transforms/gettablenames/src/main/java/org/apache/hop/pipeline/transforms/gettablenames/GetTableNamesMeta.java
+++ 
b/plugins/transforms/gettablenames/src/main/java/org/apache/hop/pipeline/transforms/gettablenames/GetTableNamesMeta.java
@@ -32,6 +32,7 @@ import org.apache.hop.core.util.Utils;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.metadata.api.HopMetadataProperty;
+import org.apache.hop.metadata.api.HopMetadataPropertyType;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.BaseTransformMeta;
@@ -55,7 +56,8 @@ public class GetTableNamesMeta extends 
BaseTransformMeta<GetTableNames, GetTable
   @HopMetadataProperty(
       key = "connection",
       injectionKey = "CONNECTIONNAME",
-      injectionKeyDescription = "GetTableNames.Injection.CONNECTION_NAME")
+      injectionKeyDescription = "GetTableNames.Injection.CONNECTION_NAME",
+      hopMetadataPropertyType = HopMetadataPropertyType.RDBMS_CONNECTION)
   private String connection;
 
   @HopMetadataProperty(
diff --git 
a/plugins/transforms/jdbc-metadata/src/main/java/org/apache/hop/pipeline/transforms/jdbcmetadata/JdbcMetadataMeta.java
 
b/plugins/transforms/jdbc-metadata/src/main/java/org/apache/hop/pipeline/transforms/jdbcmetadata/JdbcMetadataMeta.java
index e20c65088c..539d6c0441 100644
--- 
a/plugins/transforms/jdbc-metadata/src/main/java/org/apache/hop/pipeline/transforms/jdbcmetadata/JdbcMetadataMeta.java
+++ 
b/plugins/transforms/jdbc-metadata/src/main/java/org/apache/hop/pipeline/transforms/jdbcmetadata/JdbcMetadataMeta.java
@@ -34,6 +34,7 @@ import org.apache.hop.core.row.value.ValueMetaInteger;
 import org.apache.hop.core.row.value.ValueMetaString;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.metadata.api.HopMetadataProperty;
+import org.apache.hop.metadata.api.HopMetadataPropertyType;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
 import org.apache.hop.pipeline.transform.BaseTransformMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
@@ -381,7 +382,8 @@ public class JdbcMetadataMeta extends 
BaseTransformMeta<JdbcMetadata, JdbcMetada
     outputFields.add(new OutputField(CONST_TABLE_CAT, CONST_TABLE_CAT));
   }
 
-  @HopMetadataProperty private String connection;
+  @HopMetadataProperty(hopMetadataPropertyType = 
HopMetadataPropertyType.RDBMS_CONNECTION)
+  private String connection;
 
   public String getConnection() {
     return connection;
diff --git 
a/plugins/transforms/synchronizeaftermerge/src/main/java/org/apache/hop/pipeline/transforms/synchronizeaftermerge/SynchronizeAfterMergeMeta.java
 
b/plugins/transforms/synchronizeaftermerge/src/main/java/org/apache/hop/pipeline/transforms/synchronizeaftermerge/SynchronizeAfterMergeMeta.java
index 61b029d01d..8f875b7cbe 100644
--- 
a/plugins/transforms/synchronizeaftermerge/src/main/java/org/apache/hop/pipeline/transforms/synchronizeaftermerge/SynchronizeAfterMergeMeta.java
+++ 
b/plugins/transforms/synchronizeaftermerge/src/main/java/org/apache/hop/pipeline/transforms/synchronizeaftermerge/SynchronizeAfterMergeMeta.java
@@ -38,6 +38,7 @@ import org.apache.hop.core.util.Utils;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.metadata.api.HopMetadataProperty;
+import org.apache.hop.metadata.api.HopMetadataPropertyType;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
 import org.apache.hop.pipeline.DatabaseImpact;
 import org.apache.hop.pipeline.PipelineMeta;
@@ -63,7 +64,8 @@ public class SynchronizeAfterMergeMeta
   @HopMetadataProperty(
       key = "connection",
       injectionKey = "CONNECTION_NAME",
-      injectionKeyDescription = 
"SynchronizeAfterMerge.Injection.CONNECTION_NAME")
+      injectionKeyDescription = 
"SynchronizeAfterMerge.Injection.CONNECTION_NAME",
+      hopMetadataPropertyType = HopMetadataPropertyType.RDBMS_CONNECTION)
   private String connection;
 
   /** Commit size for inserts/updates */

Reply via email to