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 5f6097121f Add output field to error rows for unique rows, extra 
tests, fixes #5230 (#5893)
5f6097121f is described below

commit 5f6097121f2a32290edbb0e4f4cbbdca78050ec9
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Sat Oct 25 14:38:37 2025 +0200

    Add output field to error rows for unique rows, extra tests, fixes #5230 
(#5893)
---
 .../transforms/uniquerows/UniqueField.java         |   2 +-
 .../pipeline/transforms/uniquerows/UniqueRows.java |   6 +-
 .../transforms/uniquerows/UniqueFieldTest.java     | 366 +++++++++++++++++
 .../transforms/uniquerows/UniqueRowsDataTest.java  | 207 ++++++++++
 .../uniquerows/UniqueRowsMetaExtendedTest.java     | 446 +++++++++++++++++++++
 .../transforms/uniquerows/UniqueRowsTest.java      | 330 +++++++++++++++
 6 files changed, 1353 insertions(+), 4 deletions(-)

diff --git 
a/plugins/transforms/uniquerows/src/main/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueField.java
 
b/plugins/transforms/uniquerows/src/main/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueField.java
index 4deb1db511..c5b9e74a43 100644
--- 
a/plugins/transforms/uniquerows/src/main/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueField.java
+++ 
b/plugins/transforms/uniquerows/src/main/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueField.java
@@ -61,7 +61,7 @@ public class UniqueField {
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
     UniqueField that = (UniqueField) o;
-    return name.equals(that.name) && caseInsensitive == that.caseInsensitive;
+    return Objects.equals(name, that.name) && caseInsensitive == 
that.caseInsensitive;
   }
 
   @Override
diff --git 
a/plugins/transforms/uniquerows/src/main/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRows.java
 
b/plugins/transforms/uniquerows/src/main/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRows.java
index 83e96868a5..82fa7221ac 100644
--- 
a/plugins/transforms/uniquerows/src/main/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRows.java
+++ 
b/plugins/transforms/uniquerows/src/main/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRows.java
@@ -123,16 +123,16 @@ public class UniqueRows extends 
BaseTransform<UniqueRowsMeta, UniqueRowsData> {
       data.previous = data.inputRowMeta.cloneRow(r);
       data.counter = 1;
     } else {
-      data.counter++;
       if (data.sendDuplicateRows && !first) {
         putError(
-            getInputRowMeta(),
-            r,
+            data.outputRowMeta,
+            RowDataUtil.addValueData(r, data.outputRowMeta.size() - 1, 
data.counter),
             1,
             data.realErrorDescription,
             Utils.isEmpty(data.compareFields) ? null : data.compareFields,
             "UNR001");
       }
+      data.counter++;
     }
 
     if (checkFeedback(getLinesRead()) && isBasic()) {
diff --git 
a/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueFieldTest.java
 
b/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueFieldTest.java
new file mode 100644
index 0000000000..70b960b100
--- /dev/null
+++ 
b/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueFieldTest.java
@@ -0,0 +1,366 @@
+/*
+ * 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.pipeline.transforms.uniquerows;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class UniqueFieldTest {
+
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  @Test
+  void testDefaultConstructor() {
+    UniqueField field = new UniqueField();
+
+    assertNotNull(field, "UniqueField instance should be created 
successfully.");
+    assertNull(field.getName(), "name should be null by default");
+    assertFalse(field.isCaseInsensitive(), "caseInsensitive should be false by 
default");
+  }
+
+  @Test
+  void testParameterizedConstructor() {
+    String fieldName = "testField";
+    boolean caseInsensitive = true;
+
+    UniqueField field = new UniqueField(fieldName, caseInsensitive);
+
+    assertNotNull(field, "UniqueField instance should be created 
successfully.");
+    assertEquals(fieldName, field.getName(), "name should match constructor 
parameter");
+    assertEquals(
+        caseInsensitive,
+        field.isCaseInsensitive(),
+        "caseInsensitive should match constructor parameter");
+  }
+
+  @Test
+  void testParameterizedConstructorWithFalseCaseInsensitive() {
+    String fieldName = "testField";
+    boolean caseInsensitive = false;
+
+    UniqueField field = new UniqueField(fieldName, caseInsensitive);
+
+    assertNotNull(field, "UniqueField instance should be created 
successfully.");
+    assertEquals(fieldName, field.getName(), "name should match constructor 
parameter");
+    assertEquals(
+        caseInsensitive,
+        field.isCaseInsensitive(),
+        "caseInsensitive should match constructor parameter");
+  }
+
+  @Test
+  void testParameterizedConstructorWithNullName() {
+    String fieldName = null;
+    boolean caseInsensitive = true;
+
+    UniqueField field = new UniqueField(fieldName, caseInsensitive);
+
+    assertNotNull(field, "UniqueField instance should be created 
successfully.");
+    assertNull(field.getName(), "name should be null");
+    assertEquals(
+        caseInsensitive,
+        field.isCaseInsensitive(),
+        "caseInsensitive should match constructor parameter");
+  }
+
+  @Test
+  void testParameterizedConstructorWithEmptyName() {
+    String fieldName = "";
+    boolean caseInsensitive = false;
+
+    UniqueField field = new UniqueField(fieldName, caseInsensitive);
+
+    assertNotNull(field, "UniqueField instance should be created 
successfully.");
+    assertEquals(fieldName, field.getName(), "name should match constructor 
parameter");
+    assertEquals(
+        caseInsensitive,
+        field.isCaseInsensitive(),
+        "caseInsensitive should match constructor parameter");
+  }
+
+  @Test
+  void testGetName() {
+    UniqueField field = new UniqueField();
+    String testName = "testField";
+
+    field.setName(testName);
+    assertEquals(testName, field.getName(), "getName should return the set 
name");
+  }
+
+  @Test
+  void testSetName() {
+    UniqueField field = new UniqueField();
+    String testName = "testField";
+
+    field.setName(testName);
+    assertEquals(testName, field.getName(), "setName should set the name 
correctly");
+  }
+
+  @Test
+  void testSetNameWithNull() {
+    UniqueField field = new UniqueField();
+    field.setName("initialName");
+    field.setName(null);
+
+    assertNull(field.getName(), "setName with null should set name to null");
+  }
+
+  @Test
+  void testSetNameWithEmptyString() {
+    UniqueField field = new UniqueField();
+    String emptyName = "";
+
+    field.setName(emptyName);
+    assertEquals(emptyName, field.getName(), "setName with empty string should 
work");
+  }
+
+  @Test
+  void testIsCaseInsensitive() {
+    UniqueField field = new UniqueField();
+    boolean caseInsensitive = true;
+
+    field.setCaseInsensitive(caseInsensitive);
+    assertEquals(
+        caseInsensitive,
+        field.isCaseInsensitive(),
+        "isCaseInsensitive should return the set value");
+  }
+
+  @Test
+  void testSetCaseInsensitive() {
+    UniqueField field = new UniqueField();
+    boolean caseInsensitive = true;
+
+    field.setCaseInsensitive(caseInsensitive);
+    assertEquals(
+        caseInsensitive,
+        field.isCaseInsensitive(),
+        "setCaseInsensitive should set the value correctly");
+  }
+
+  @Test
+  void testSetCaseInsensitiveFalse() {
+    UniqueField field = new UniqueField();
+    boolean caseInsensitive = false;
+
+    field.setCaseInsensitive(caseInsensitive);
+    assertEquals(
+        caseInsensitive,
+        field.isCaseInsensitive(),
+        "setCaseInsensitive should set the value correctly");
+  }
+
+  @Test
+  void testEqualsWithSameObject() {
+    UniqueField field = new UniqueField("testField", true);
+
+    assertTrue(field.equals(field), "field should equal itself");
+  }
+
+  @Test
+  void testEqualsWithNull() {
+    UniqueField field = new UniqueField("testField", true);
+
+    assertFalse(field.equals(null), "field should not equal null");
+  }
+
+  @Test
+  void testEqualsWithDifferentClass() {
+    UniqueField field = new UniqueField("testField", true);
+    String otherObject = "not a UniqueField";
+
+    assertFalse(field.equals(otherObject), "field should not equal object of 
different class");
+  }
+
+  @Test
+  void testEqualsWithSameValues() {
+    UniqueField field1 = new UniqueField("testField", true);
+    UniqueField field2 = new UniqueField("testField", true);
+
+    assertTrue(field1.equals(field2), "fields with same values should be 
equal");
+    assertTrue(field2.equals(field1), "equals should be symmetric");
+  }
+
+  @Test
+  void testEqualsWithDifferentName() {
+    UniqueField field1 = new UniqueField("testField1", true);
+    UniqueField field2 = new UniqueField("testField2", true);
+
+    assertFalse(field1.equals(field2), "fields with different names should not 
be equal");
+    assertFalse(field2.equals(field1), "equals should be symmetric");
+  }
+
+  @Test
+  void testEqualsWithDifferentCaseInsensitive() {
+    UniqueField field1 = new UniqueField("testField", true);
+    UniqueField field2 = new UniqueField("testField", false);
+
+    assertFalse(field1.equals(field2), "fields with different caseInsensitive 
should not be equal");
+    assertFalse(field2.equals(field1), "equals should be symmetric");
+  }
+
+  @Test
+  void testEqualsWithNullName() {
+    UniqueField field1 = new UniqueField(null, true);
+    UniqueField field2 = new UniqueField(null, true);
+
+    assertTrue(
+        field1.equals(field2), "fields with null names should be equal if 
other values match");
+  }
+
+  @Test
+  void testEqualsWithOneNullName() {
+    UniqueField field1 = new UniqueField(null, true);
+    UniqueField field2 = new UniqueField("testField", true);
+
+    assertFalse(field1.equals(field2), "fields with one null name should not 
be equal");
+    assertFalse(field2.equals(field1), "equals should be symmetric");
+  }
+
+  @Test
+  void testHashCodeWithSameValues() {
+    UniqueField field1 = new UniqueField("testField", true);
+    UniqueField field2 = new UniqueField("testField", true);
+
+    assertEquals(
+        field1.hashCode(), field2.hashCode(), "fields with same values should 
have same hashCode");
+  }
+
+  @Test
+  void testHashCodeWithDifferentName() {
+    UniqueField field1 = new UniqueField("testField1", true);
+    UniqueField field2 = new UniqueField("testField2", true);
+
+    assertNotEquals(
+        field1.hashCode(),
+        field2.hashCode(),
+        "fields with different names should have different hashCode");
+  }
+
+  @Test
+  void testHashCodeWithDifferentCaseInsensitive() {
+    UniqueField field1 = new UniqueField("testField", true);
+    UniqueField field2 = new UniqueField("testField", false);
+
+    assertNotEquals(
+        field1.hashCode(),
+        field2.hashCode(),
+        "fields with different caseInsensitive should have different 
hashCode");
+  }
+
+  @Test
+  void testHashCodeWithNullName() {
+    UniqueField field1 = new UniqueField(null, true);
+    UniqueField field2 = new UniqueField(null, true);
+
+    assertEquals(
+        field1.hashCode(),
+        field2.hashCode(),
+        "fields with null names should have same hashCode if other values 
match");
+  }
+
+  @Test
+  void testHashCodeConsistency() {
+    UniqueField field = new UniqueField("testField", true);
+    int hashCode1 = field.hashCode();
+    int hashCode2 = field.hashCode();
+
+    assertEquals(hashCode1, hashCode2, "hashCode should be consistent across 
multiple calls");
+  }
+
+  @Test
+  void testHashCodeAfterModification() {
+    UniqueField field = new UniqueField("testField", true);
+    int originalHashCode = field.hashCode();
+
+    field.setName("newField");
+    int newHashCode = field.hashCode();
+
+    assertNotEquals(
+        originalHashCode, newHashCode, "hashCode should change after name 
modification");
+  }
+
+  @Test
+  void testHashCodeAfterCaseInsensitiveModification() {
+    UniqueField field = new UniqueField("testField", true);
+    int originalHashCode = field.hashCode();
+
+    field.setCaseInsensitive(false);
+    int newHashCode = field.hashCode();
+
+    assertNotEquals(
+        originalHashCode, newHashCode, "hashCode should change after 
caseInsensitive modification");
+  }
+
+  @Test
+  void testToString() {
+    UniqueField field = new UniqueField("testField", true);
+    String toString = field.toString();
+
+    assertNotNull(toString, "toString should not return null");
+    // The default toString() method from Object doesn't contain the field name
+    // This test just verifies that toString() doesn't return null
+  }
+
+  @Test
+  void testToStringWithNullName() {
+    UniqueField field = new UniqueField(null, true);
+    String toString = field.toString();
+
+    assertNotNull(toString, "toString should not return null even with null 
name");
+  }
+
+  @Test
+  void testFieldModification() {
+    UniqueField field = new UniqueField("initialName", false);
+
+    // Test name modification
+    field.setName("newName");
+    assertEquals("newName", field.getName(), "name should be updated");
+    assertFalse(field.isCaseInsensitive(), "caseInsensitive should remain 
unchanged");
+
+    // Test caseInsensitive modification
+    field.setCaseInsensitive(true);
+    assertEquals("newName", field.getName(), "name should remain unchanged");
+    assertTrue(field.isCaseInsensitive(), "caseInsensitive should be updated");
+  }
+
+  @Test
+  void testFieldEqualityAfterModification() {
+    UniqueField field1 = new UniqueField("testField", true);
+    UniqueField field2 = new UniqueField("testField", true);
+
+    // Initially equal
+    assertTrue(field1.equals(field2), "fields should be equal initially");
+
+    // Modify one field
+    field1.setName("newName");
+
+    // Should no longer be equal
+    assertFalse(field1.equals(field2), "fields should not be equal after 
modification");
+  }
+}
diff --git 
a/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRowsDataTest.java
 
b/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRowsDataTest.java
new file mode 100644
index 0000000000..676641ec54
--- /dev/null
+++ 
b/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRowsDataTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.pipeline.transforms.uniquerows;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.pipeline.transform.BaseTransformData;
+import org.apache.hop.pipeline.transform.ITransformData;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class UniqueRowsDataTest {
+
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  @Test
+  void testUniqueRowsDataInitialization() {
+    // Create instance
+    UniqueRowsData data = new UniqueRowsData();
+
+    // Verify object is not null
+    assertNotNull(data, "UniqueRowsData instance should be created 
successfully.");
+
+    // Verify inheritance
+    assertNotNull(data, "UniqueRowsData should extend BaseTransformData.");
+    assertNotNull(data, "UniqueRowsData should implement ITransformData.");
+
+    // Verify initial state
+    assertNull(data.outputRowMeta, "outputRowMeta should be null initially");
+    assertNull(data.compareRowMeta, "compareRowMeta should be null initially");
+    assertNull(data.inputRowMeta, "inputRowMeta should be null initially");
+    assertEquals(0, data.counter, "counter should be 0 initially");
+    assertNull(data.previous, "previous should be null initially");
+    assertNull(data.fieldnrs, "fieldnrs should be null initially");
+    assertNull(data.compareFields, "compareFields should be null initially");
+    assertNull(data.realErrorDescription, "realErrorDescription should be null 
initially");
+    assertFalse(data.sendDuplicateRows, "sendDuplicateRows should be false 
initially");
+  }
+
+  @Test
+  void testUniqueRowsDataFieldAccess() {
+    UniqueRowsData data = new UniqueRowsData();
+
+    // Test setting and getting outputRowMeta
+    IRowMeta outputRowMeta = new RowMeta();
+    outputRowMeta.addValueMeta(new ValueMetaString("test"));
+    data.outputRowMeta = outputRowMeta;
+    assertEquals(outputRowMeta, data.outputRowMeta);
+
+    // Test setting and getting compareRowMeta
+    IRowMeta compareRowMeta = new RowMeta();
+    compareRowMeta.addValueMeta(new ValueMetaString("compare"));
+    data.compareRowMeta = compareRowMeta;
+    assertEquals(compareRowMeta, data.compareRowMeta);
+
+    // Test setting and getting inputRowMeta
+    IRowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaString("input"));
+    data.inputRowMeta = inputRowMeta;
+    assertEquals(inputRowMeta, data.inputRowMeta);
+
+    // Test setting and getting counter
+    data.counter = 42L;
+    assertEquals(42L, data.counter);
+
+    // Test setting and getting previous
+    Object[] previous = new Object[] {"test", "data"};
+    data.previous = previous;
+    assertEquals(previous, data.previous);
+
+    // Test setting and getting fieldnrs
+    int[] fieldnrs = new int[] {0, 1, 2};
+    data.fieldnrs = fieldnrs;
+    assertEquals(fieldnrs, data.fieldnrs);
+
+    // Test setting and getting compareFields
+    String compareFields = "field1,field2";
+    data.compareFields = compareFields;
+    assertEquals(compareFields, data.compareFields);
+
+    // Test setting and getting realErrorDescription
+    String errorDescription = "Test error";
+    data.realErrorDescription = errorDescription;
+    assertEquals(errorDescription, data.realErrorDescription);
+
+    // Test setting and getting sendDuplicateRows
+    data.sendDuplicateRows = true;
+    assertTrue(data.sendDuplicateRows);
+  }
+
+  @Test
+  void testUniqueRowsDataInheritance() {
+    UniqueRowsData data = new UniqueRowsData();
+
+    // Verify inheritance from BaseTransformData
+    assertNotNull(data, "Should be instance of BaseTransformData");
+    assertNotNull(data, "Should be instance of ITransformData");
+
+    // Test that we can cast to parent types
+    BaseTransformData baseData = data;
+    ITransformData transformData = data;
+
+    assertNotNull(baseData, "Should be castable to BaseTransformData");
+    assertNotNull(transformData, "Should be castable to ITransformData");
+  }
+
+  @Test
+  void testUniqueRowsDataDefaultValues() {
+    UniqueRowsData data = new UniqueRowsData();
+
+    // Verify all default values are as expected
+    assertNull(data.outputRowMeta, "outputRowMeta should be null by default");
+    assertNull(data.compareRowMeta, "compareRowMeta should be null by 
default");
+    assertNull(data.inputRowMeta, "inputRowMeta should be null by default");
+    assertEquals(0, data.counter, "counter should be 0 by default");
+    assertNull(data.previous, "previous should be null by default");
+    assertNull(data.fieldnrs, "fieldnrs should be null by default");
+    assertNull(data.compareFields, "compareFields should be null by default");
+    assertNull(data.realErrorDescription, "realErrorDescription should be null 
by default");
+    assertFalse(data.sendDuplicateRows, "sendDuplicateRows should be false by 
default");
+  }
+
+  @Test
+  void testUniqueRowsDataFieldModification() {
+    UniqueRowsData data = new UniqueRowsData();
+
+    // Test counter modification
+    data.counter = 10L;
+    assertEquals(10L, data.counter);
+
+    data.counter++;
+    assertEquals(11L, data.counter);
+
+    data.counter = 0L;
+    assertEquals(0L, data.counter);
+
+    // Test sendDuplicateRows modification
+    data.sendDuplicateRows = true;
+    assertTrue(data.sendDuplicateRows);
+
+    data.sendDuplicateRows = false;
+    assertFalse(data.sendDuplicateRows);
+
+    // Test previous row modification
+    Object[] row1 = new Object[] {"value1", "value2"};
+    Object[] row2 = new Object[] {"value3", "value4"};
+
+    data.previous = row1;
+    assertEquals(row1, data.previous);
+
+    data.previous = row2;
+    assertEquals(row2, data.previous);
+
+    data.previous = null;
+    assertNull(data.previous);
+  }
+
+  @Test
+  void testUniqueRowsDataArrayFields() {
+    UniqueRowsData data = new UniqueRowsData();
+
+    // Test fieldnrs array
+    int[] fieldnrs1 = new int[] {0, 2, 4};
+    int[] fieldnrs2 = new int[] {1, 3, 5};
+
+    data.fieldnrs = fieldnrs1;
+    assertEquals(fieldnrs1, data.fieldnrs);
+    assertEquals(3, data.fieldnrs.length);
+    assertEquals(0, data.fieldnrs[0]);
+    assertEquals(2, data.fieldnrs[1]);
+    assertEquals(4, data.fieldnrs[2]);
+
+    data.fieldnrs = fieldnrs2;
+    assertEquals(fieldnrs2, data.fieldnrs);
+    assertEquals(3, data.fieldnrs.length);
+    assertEquals(1, data.fieldnrs[0]);
+    assertEquals(3, data.fieldnrs[1]);
+    assertEquals(5, data.fieldnrs[2]);
+
+    data.fieldnrs = null;
+    assertNull(data.fieldnrs);
+  }
+}
diff --git 
a/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRowsMetaExtendedTest.java
 
b/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRowsMetaExtendedTest.java
new file mode 100644
index 0000000000..fe7e2654ab
--- /dev/null
+++ 
b/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRowsMetaExtendedTest.java
@@ -0,0 +1,446 @@
+/*
+ * 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.pipeline.transforms.uniquerows;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.hop.core.ICheckResult;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mockito.Mockito;
+
+class UniqueRowsMetaExtendedTest {
+
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  @Test
+  void testDefaultConstructor() {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+
+    assertNotNull(meta, "UniqueRowsMeta instance should be created 
successfully.");
+    assertFalse(meta.isCountRows(), "countRows should be false by default");
+    assertNull(meta.getCountField(), "countField should be null by default");
+    assertNotNull(meta.getCompareFields(), "compareFields should not be null");
+    assertTrue(meta.getCompareFields().isEmpty(), "compareFields should be 
empty by default");
+    assertFalse(meta.isRejectDuplicateRow(), "rejectDuplicateRow should be 
false by default");
+    assertNull(meta.getErrorDescription(), "errorDescription should be null by 
default");
+  }
+
+  @Test
+  void testCopyConstructor() {
+    // Create original meta with some values
+    UniqueRowsMeta original = new UniqueRowsMeta();
+    original.setCountRows(true);
+    original.setCountField("countField");
+    original.setRejectDuplicateRow(true);
+    original.setErrorDescription("Test error");
+
+    List<UniqueField> compareFields = new ArrayList<>();
+    compareFields.add(new UniqueField("field1", true));
+    compareFields.add(new UniqueField("field2", false));
+    original.setCompareFields(compareFields);
+
+    // Create copy using copy constructor
+    UniqueRowsMeta copy = new UniqueRowsMeta(original);
+
+    assertNotNull(copy, "Copy should be created successfully.");
+    assertEquals(original.isCountRows(), copy.isCountRows(), "countRows should 
be copied");
+    assertEquals(original.getCountField(), copy.getCountField(), "countField 
should be copied");
+    assertEquals(
+        original.isRejectDuplicateRow(),
+        copy.isRejectDuplicateRow(),
+        "rejectDuplicateRow should be copied");
+    assertEquals(
+        original.getErrorDescription(),
+        copy.getErrorDescription(),
+        "errorDescription should be copied");
+
+    // Compare fields should be copied but not the same reference
+    assertNotNull(copy.getCompareFields(), "compareFields should not be null");
+    assertEquals(
+        original.getCompareFields().size(),
+        copy.getCompareFields().size(),
+        "compareFields size should match");
+    assertFalse(
+        original.getCompareFields() == copy.getCompareFields(),
+        "compareFields should be different instances");
+
+    // Verify individual fields are copied correctly
+    for (int i = 0; i < original.getCompareFields().size(); i++) {
+      UniqueField originalField = original.getCompareFields().get(i);
+      UniqueField copyField = copy.getCompareFields().get(i);
+      assertEquals(originalField.getName(), copyField.getName(), "Field name 
should be copied");
+      assertEquals(
+          originalField.isCaseInsensitive(),
+          copyField.isCaseInsensitive(),
+          "Field caseInsensitive should be copied");
+    }
+  }
+
+  @Test
+  void testCopyConstructorWithEmptyCompareFields() {
+    UniqueRowsMeta original = new UniqueRowsMeta();
+    original.setCountRows(false);
+    original.setCountField("");
+    original.setRejectDuplicateRow(false);
+    original.setErrorDescription(null);
+    original.setCompareFields(new ArrayList<>());
+
+    UniqueRowsMeta copy = new UniqueRowsMeta(original);
+
+    assertNotNull(copy, "Copy should be created successfully.");
+    assertEquals(original.isCountRows(), copy.isCountRows(), "countRows should 
be copied");
+    assertEquals(original.getCountField(), copy.getCountField(), "countField 
should be copied");
+    assertEquals(
+        original.isRejectDuplicateRow(),
+        copy.isRejectDuplicateRow(),
+        "rejectDuplicateRow should be copied");
+    assertEquals(
+        original.getErrorDescription(),
+        copy.getErrorDescription(),
+        "errorDescription should be copied");
+    assertNotNull(copy.getCompareFields(), "compareFields should not be null");
+    assertTrue(copy.getCompareFields().isEmpty(), "compareFields should be 
empty");
+  }
+
+  @Test
+  void testGettersAndSetters() {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+
+    // Test countRows
+    meta.setCountRows(true);
+    assertTrue(meta.isCountRows(), "setCountRows should set the value 
correctly");
+    meta.setCountRows(false);
+    assertFalse(meta.isCountRows(), "setCountRows should set the value 
correctly");
+
+    // Test countField
+    String countField = "testCountField";
+    meta.setCountField(countField);
+    assertEquals(countField, meta.getCountField(), "setCountField should set 
the value correctly");
+    meta.setCountField(null);
+    assertNull(meta.getCountField(), "setCountField with null should work");
+
+    // Test rejectDuplicateRow
+    meta.setRejectDuplicateRow(true);
+    assertTrue(meta.isRejectDuplicateRow(), "setRejectDuplicateRow should set 
the value correctly");
+    meta.setRejectDuplicateRow(false);
+    assertFalse(
+        meta.isRejectDuplicateRow(), "setRejectDuplicateRow should set the 
value correctly");
+
+    // Test errorDescription
+    String errorDescription = "Test error description";
+    meta.setErrorDescription(errorDescription);
+    assertEquals(
+        errorDescription,
+        meta.getErrorDescription(),
+        "setErrorDescription should set the value correctly");
+    meta.setErrorDescription(null);
+    assertNull(meta.getErrorDescription(), "setErrorDescription with null 
should work");
+
+    // Test compareFields
+    List<UniqueField> compareFields = new ArrayList<>();
+    compareFields.add(new UniqueField("field1", true));
+    compareFields.add(new UniqueField("field2", false));
+    meta.setCompareFields(compareFields);
+    assertEquals(
+        compareFields, meta.getCompareFields(), "setCompareFields should set 
the value correctly");
+    meta.setCompareFields(null);
+    assertNull(meta.getCompareFields(), "setCompareFields with null should 
work");
+  }
+
+  @Test
+  void testClone() {
+    UniqueRowsMeta original = new UniqueRowsMeta();
+    original.setCountRows(true);
+    original.setCountField("countField");
+    original.setRejectDuplicateRow(true);
+    original.setErrorDescription("Test error");
+
+    List<UniqueField> compareFields = new ArrayList<>();
+    compareFields.add(new UniqueField("field1", true));
+    compareFields.add(new UniqueField("field2", false));
+    original.setCompareFields(compareFields);
+
+    UniqueRowsMeta clone = (UniqueRowsMeta) original.clone();
+
+    assertNotNull(clone, "Clone should be created successfully.");
+    assertTrue(clone instanceof UniqueRowsMeta, "Clone should be instance of 
UniqueRowsMeta");
+    assertEquals(original.isCountRows(), clone.isCountRows(), "countRows 
should be cloned");
+    assertEquals(original.getCountField(), clone.getCountField(), "countField 
should be cloned");
+    assertEquals(
+        original.isRejectDuplicateRow(),
+        clone.isRejectDuplicateRow(),
+        "rejectDuplicateRow should be cloned");
+    assertEquals(
+        original.getErrorDescription(),
+        clone.getErrorDescription(),
+        "errorDescription should be cloned");
+
+    // Compare fields should be cloned but not the same reference
+    assertNotNull(clone.getCompareFields(), "compareFields should not be 
null");
+    assertEquals(
+        original.getCompareFields().size(),
+        clone.getCompareFields().size(),
+        "compareFields size should match");
+    assertFalse(
+        original.getCompareFields() == clone.getCompareFields(),
+        "compareFields should be different instances");
+  }
+
+  @Test
+  void testSetDefault() {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+
+    // Set some values first
+    meta.setCountRows(true);
+    meta.setCountField("testField");
+    meta.setRejectDuplicateRow(true);
+    meta.setErrorDescription("Test error");
+
+    List<UniqueField> compareFields = new ArrayList<>();
+    compareFields.add(new UniqueField("field1", true));
+    meta.setCompareFields(compareFields);
+
+    // Call setDefault
+    meta.setDefault();
+
+    // Verify default values
+    assertFalse(meta.isCountRows(), "countRows should be false after 
setDefault");
+    assertEquals("", meta.getCountField(), "countField should be empty string 
after setDefault");
+    assertFalse(meta.isRejectDuplicateRow(), "rejectDuplicateRow should be 
false after setDefault");
+    assertNull(meta.getErrorDescription(), "errorDescription should be null 
after setDefault");
+    assertNotNull(meta.getCompareFields(), "compareFields should not be null 
after setDefault");
+    assertTrue(meta.getCompareFields().isEmpty(), "compareFields should be 
empty after setDefault");
+  }
+
+  @Test
+  void testGetFieldsWithCountRows() throws Exception {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+    meta.setCountRows(true);
+    meta.setCountField("countField");
+
+    IRowMeta row = new RowMeta();
+    row.addValueMeta(new ValueMetaString("name"));
+    row.addValueMeta(new ValueMetaInteger("age"));
+
+    IHopMetadataProvider metadataProvider = 
Mockito.mock(IHopMetadataProvider.class);
+    Variables variables = new Variables();
+
+    meta.getFields(row, "testTransform", null, null, variables, 
metadataProvider);
+
+    // Should have original fields plus count field
+    assertEquals(3, row.size(), "Row should have 3 fields after adding count 
field");
+    assertEquals("name", row.getValueMeta(0).getName(), "First field should be 
name");
+    assertEquals("age", row.getValueMeta(1).getName(), "Second field should be 
age");
+    assertEquals("countField", row.getValueMeta(2).getName(), "Third field 
should be countField");
+    assertTrue(
+        row.getValueMeta(2) instanceof ValueMetaInteger, "Count field should 
be ValueMetaInteger");
+  }
+
+  @Test
+  void testGetFieldsWithoutCountRows() throws Exception {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+    meta.setCountRows(false);
+
+    IRowMeta row = new RowMeta();
+    row.addValueMeta(new ValueMetaString("name"));
+    row.addValueMeta(new ValueMetaInteger("age"));
+
+    IHopMetadataProvider metadataProvider = 
Mockito.mock(IHopMetadataProvider.class);
+    Variables variables = new Variables();
+
+    meta.getFields(row, "testTransform", null, null, variables, 
metadataProvider);
+
+    // Should have original fields only
+    assertEquals(2, row.size(), "Row should have 2 fields without count 
field");
+    assertEquals("name", row.getValueMeta(0).getName(), "First field should be 
name");
+    assertEquals("age", row.getValueMeta(1).getName(), "Second field should be 
age");
+  }
+
+  @Test
+  void testGetFieldsWithCompareFields() throws Exception {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+    meta.setCountRows(false);
+
+    List<UniqueField> compareFields = new ArrayList<>();
+    compareFields.add(new UniqueField("name", true)); // Case insensitive
+    compareFields.add(new UniqueField("age", false)); // Case sensitive
+    meta.setCompareFields(compareFields);
+
+    IRowMeta row = new RowMeta();
+    ValueMetaString nameMeta = new ValueMetaString("name");
+    ValueMetaInteger ageMeta = new ValueMetaInteger("age");
+    row.addValueMeta(nameMeta);
+    row.addValueMeta(ageMeta);
+
+    IHopMetadataProvider metadataProvider = 
Mockito.mock(IHopMetadataProvider.class);
+    Variables variables = new Variables();
+
+    meta.getFields(row, "testTransform", null, null, variables, 
metadataProvider);
+
+    // Verify case insensitive settings
+    assertTrue(row.getValueMeta(0).isCaseInsensitive(), "name field should be 
case insensitive");
+    assertFalse(row.getValueMeta(1).isCaseInsensitive(), "age field should be 
case sensitive");
+  }
+
+  @Test
+  void testCheckWithInput() {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+    PipelineMeta pipelineMeta = Mockito.mock(PipelineMeta.class);
+    TransformMeta transformMeta = Mockito.mock(TransformMeta.class);
+    IRowMeta prev = new RowMeta();
+    String[] input = {"input1", "input2"};
+    String[] output = {"output1"};
+    IRowMeta info = new RowMeta();
+    Variables variables = new Variables();
+    IHopMetadataProvider metadataProvider = 
Mockito.mock(IHopMetadataProvider.class);
+
+    List<ICheckResult> remarks = new ArrayList<>();
+    meta.check(
+        remarks,
+        pipelineMeta,
+        transformMeta,
+        prev,
+        input,
+        output,
+        info,
+        variables,
+        metadataProvider);
+
+    assertEquals(1, remarks.size(), "Should have one check result");
+    ICheckResult result = remarks.get(0);
+    assertEquals(ICheckResult.TYPE_RESULT_OK, result.getType(), "Check result 
should be OK");
+  }
+
+  @Test
+  void testCheckWithoutInput() {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+    PipelineMeta pipelineMeta = Mockito.mock(PipelineMeta.class);
+    TransformMeta transformMeta = Mockito.mock(TransformMeta.class);
+    IRowMeta prev = new RowMeta();
+    String[] input = {}; // No input
+    String[] output = {"output1"};
+    IRowMeta info = new RowMeta();
+    Variables variables = new Variables();
+    IHopMetadataProvider metadataProvider = 
Mockito.mock(IHopMetadataProvider.class);
+
+    List<ICheckResult> remarks = new ArrayList<>();
+    meta.check(
+        remarks,
+        pipelineMeta,
+        transformMeta,
+        prev,
+        input,
+        output,
+        info,
+        variables,
+        metadataProvider);
+
+    assertEquals(1, remarks.size(), "Should have one check result");
+    ICheckResult result = remarks.get(0);
+    assertEquals(ICheckResult.TYPE_RESULT_ERROR, result.getType(), "Check 
result should be ERROR");
+  }
+
+  @Test
+  void testSupportsErrorHandling() {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+
+    // Test with rejectDuplicateRow = true
+    meta.setRejectDuplicateRow(true);
+    assertTrue(
+        meta.supportsErrorHandling(),
+        "Should support error handling when rejectDuplicateRow is true");
+
+    // Test with rejectDuplicateRow = false
+    meta.setRejectDuplicateRow(false);
+    assertFalse(
+        meta.supportsErrorHandling(),
+        "Should not support error handling when rejectDuplicateRow is false");
+  }
+
+  @Test
+  void testCompareFieldsModification() {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+
+    // Initially empty
+    assertTrue(meta.getCompareFields().isEmpty(), "compareFields should be 
empty initially");
+
+    // Add fields
+    List<UniqueField> compareFields = new ArrayList<>();
+    compareFields.add(new UniqueField("field1", true));
+    compareFields.add(new UniqueField("field2", false));
+    meta.setCompareFields(compareFields);
+
+    assertEquals(2, meta.getCompareFields().size(), "compareFields should have 
2 fields");
+    assertEquals(
+        "field1", meta.getCompareFields().get(0).getName(), "First field name 
should be field1");
+    assertTrue(
+        meta.getCompareFields().get(0).isCaseInsensitive(),
+        "First field should be case insensitive");
+    assertEquals(
+        "field2", meta.getCompareFields().get(1).getName(), "Second field name 
should be field2");
+    assertFalse(
+        meta.getCompareFields().get(1).isCaseInsensitive(),
+        "Second field should be case sensitive");
+
+    // Modify existing field
+    meta.getCompareFields().get(0).setName("modifiedField1");
+    assertEquals(
+        "modifiedField1",
+        meta.getCompareFields().get(0).getName(),
+        "Field name should be modified");
+
+    // Add new field
+    meta.getCompareFields().add(new UniqueField("field3", true));
+    assertEquals(
+        3, meta.getCompareFields().size(), "compareFields should have 3 fields 
after addition");
+  }
+
+  @Test
+  void testErrorDescriptionWithVariables() {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+    String errorDescription = "Error in ${TRANSFORM_NAME}";
+    meta.setErrorDescription(errorDescription);
+    assertEquals(
+        errorDescription, meta.getErrorDescription(), "Error description 
should be set correctly");
+  }
+
+  @Test
+  void testCountFieldWithVariables() {
+    UniqueRowsMeta meta = new UniqueRowsMeta();
+    String countField = "count_${TRANSFORM_NAME}";
+    meta.setCountField(countField);
+    assertEquals(countField, meta.getCountField(), "Count field should be set 
correctly");
+  }
+}
diff --git 
a/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRowsTest.java
 
b/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRowsTest.java
new file mode 100644
index 0000000000..625cb0dee7
--- /dev/null
+++ 
b/plugins/transforms/uniquerows/src/test/java/org/apache/hop/pipeline/transforms/uniquerows/UniqueRowsTest.java
@@ -0,0 +1,330 @@
+/*
+ * 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.pipeline.transforms.uniquerows;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class UniqueRowsTest {
+
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  private TransformMockHelper<UniqueRowsMeta, UniqueRowsData> 
transformMockHelper;
+
+  @BeforeAll
+  static void setUpBeforeClass() throws HopException {
+    HopEnvironment.init();
+  }
+
+  @BeforeEach
+  void setUp() {
+    transformMockHelper =
+        new TransformMockHelper<>("UniqueRows", UniqueRowsMeta.class, 
UniqueRowsData.class);
+    when(transformMockHelper.logChannelFactory.create(any(), 
any(ILoggingObject.class)))
+        .thenReturn(transformMockHelper.iLogChannel);
+    when(transformMockHelper.pipeline.isRunning()).thenReturn(true);
+    when(transformMockHelper.transformMeta.getName()).thenReturn("UniqueRows");
+  }
+
+  @AfterEach
+  void tearDown() {
+    transformMockHelper.cleanUp();
+  }
+
+  @Test
+  void testInitWithErrorHandling() {
+    // Setup error handling
+    when(transformMockHelper.transformMeta.getTransformErrorMeta())
+        
.thenReturn(mock(org.apache.hop.pipeline.transform.TransformErrorMeta.class));
+    
when(transformMockHelper.iTransformMeta.supportsErrorHandling()).thenReturn(true);
+    
when(transformMockHelper.iTransformMeta.isRejectDuplicateRow()).thenReturn(true);
+    when(transformMockHelper.iTransformMeta.getErrorDescription())
+        .thenReturn("Duplicate row error");
+
+    UniqueRows uniqueRows =
+        new UniqueRows(
+            transformMockHelper.transformMeta,
+            transformMockHelper.iTransformMeta,
+            transformMockHelper.iTransformData,
+            0,
+            transformMockHelper.pipelineMeta,
+            transformMockHelper.pipeline);
+
+    boolean result = uniqueRows.init();
+
+    assertTrue(result);
+    assertTrue(transformMockHelper.iTransformData.sendDuplicateRows);
+  }
+
+  @Test
+  void testInitWithoutErrorHandling() {
+    // Setup without error handling
+    
when(transformMockHelper.transformMeta.getTransformErrorMeta()).thenReturn(null);
+    
when(transformMockHelper.iTransformMeta.supportsErrorHandling()).thenReturn(false);
+
+    UniqueRows uniqueRows =
+        new UniqueRows(
+            transformMockHelper.transformMeta,
+            transformMockHelper.iTransformMeta,
+            transformMockHelper.iTransformData,
+            0,
+            transformMockHelper.pipelineMeta,
+            transformMockHelper.pipeline);
+
+    boolean result = uniqueRows.init();
+
+    assertTrue(result);
+    assertFalse(transformMockHelper.iTransformData.sendDuplicateRows);
+  }
+
+  @Test
+  void testProcessRowWithNoCompareFields() throws Exception {
+    // Setup meta with no compare fields
+    when(transformMockHelper.iTransformMeta.getCompareFields()).thenReturn(new 
ArrayList<>());
+    when(transformMockHelper.iTransformMeta.isCountRows()).thenReturn(false);
+
+    UniqueRows uniqueRows =
+        new UniqueRows(
+            transformMockHelper.transformMeta,
+            transformMockHelper.iTransformMeta,
+            transformMockHelper.iTransformData,
+            0,
+            transformMockHelper.pipelineMeta,
+            transformMockHelper.pipeline);
+
+    assertTrue(uniqueRows.init());
+
+    // Setup input row meta
+    RowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaString("name"));
+    inputRowMeta.addValueMeta(new ValueMetaInteger("age"));
+    uniqueRows.setInputRowMeta(inputRowMeta);
+
+    // Test first row - should not output anything yet
+    uniqueRows = spy(uniqueRows);
+    doReturn(new Object[] {"Alice", 30L}).when(uniqueRows).getRow();
+    assertTrue(uniqueRows.processRow());
+    // First row is stored but not output yet
+
+    // Test second row (different) - should output previous row
+    doReturn(new Object[] {"Bob", 25L}).when(uniqueRows).getRow();
+    assertTrue(uniqueRows.processRow());
+    verify(uniqueRows).putRow(any(IRowMeta.class), any(Object[].class));
+  }
+
+  @Test
+  void testProcessRowWithCompareFields() throws Exception {
+    // Setup meta with compare fields
+    List<UniqueField> compareFields = new ArrayList<>();
+    compareFields.add(new UniqueField("name", false));
+    
when(transformMockHelper.iTransformMeta.getCompareFields()).thenReturn(compareFields);
+    when(transformMockHelper.iTransformMeta.isCountRows()).thenReturn(false);
+
+    UniqueRows uniqueRows =
+        new UniqueRows(
+            transformMockHelper.transformMeta,
+            transformMockHelper.iTransformMeta,
+            transformMockHelper.iTransformData,
+            0,
+            transformMockHelper.pipelineMeta,
+            transformMockHelper.pipeline);
+
+    assertTrue(uniqueRows.init());
+
+    // Setup input row meta
+    RowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaString("name"));
+    inputRowMeta.addValueMeta(new ValueMetaInteger("age"));
+    uniqueRows.setInputRowMeta(inputRowMeta);
+
+    // Test first row - should not output anything yet
+    uniqueRows = spy(uniqueRows);
+    doReturn(new Object[] {"Alice", 30L}).when(uniqueRows).getRow();
+    assertTrue(uniqueRows.processRow());
+    // First row is stored but not output yet
+
+    // Test second row (different name) - should output previous row
+    doReturn(new Object[] {"Bob", 25L}).when(uniqueRows).getRow();
+    assertTrue(uniqueRows.processRow());
+    verify(uniqueRows).putRow(any(IRowMeta.class), any(Object[].class));
+  }
+
+  @Test
+  void testProcessRowWithFieldNotFound() throws Exception {
+    // Setup meta with a field that doesn't exist
+    List<UniqueField> compareFields = new ArrayList<>();
+    compareFields.add(new UniqueField("nonexistent", false));
+    
when(transformMockHelper.iTransformMeta.getCompareFields()).thenReturn(compareFields);
+
+    UniqueRows uniqueRows =
+        new UniqueRows(
+            transformMockHelper.transformMeta,
+            transformMockHelper.iTransformMeta,
+            transformMockHelper.iTransformData,
+            0,
+            transformMockHelper.pipelineMeta,
+            transformMockHelper.pipeline);
+
+    assertTrue(uniqueRows.init());
+
+    // Setup input row meta
+    RowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaString("name"));
+    uniqueRows.setInputRowMeta(inputRowMeta);
+
+    // Spy on the transform to mock getRow()
+    uniqueRows = spy(uniqueRows);
+    doReturn(new Object[] {"Alice"}).when(uniqueRows).getRow();
+
+    // Execute processRow - should fail due to field not found
+    boolean result = uniqueRows.processRow();
+
+    assertFalse(result);
+    // Verify that errors were set
+    assertTrue(uniqueRows.getErrors() > 0);
+  }
+
+  @Test
+  void testBatchComplete() throws Exception {
+    when(transformMockHelper.iTransformMeta.getCompareFields()).thenReturn(new 
ArrayList<>());
+    when(transformMockHelper.iTransformMeta.isCountRows()).thenReturn(false);
+
+    UniqueRows uniqueRows =
+        new UniqueRows(
+            transformMockHelper.transformMeta,
+            transformMockHelper.iTransformMeta,
+            transformMockHelper.iTransformData,
+            0,
+            transformMockHelper.pipelineMeta,
+            transformMockHelper.pipeline);
+
+    assertTrue(uniqueRows.init());
+
+    // Setup input row meta
+    RowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaString("name"));
+    uniqueRows.setInputRowMeta(inputRowMeta);
+
+    // Set up previous row in data
+    transformMockHelper.iTransformData.previous = new Object[] {"Alice"};
+    transformMockHelper.iTransformData.outputRowMeta = inputRowMeta;
+    transformMockHelper.iTransformData.counter = 2;
+
+    // Spy on the transform to mock putRow
+    uniqueRows = spy(uniqueRows);
+
+    // Call batchComplete
+    uniqueRows.batchComplete();
+
+    // Verify that putRow was called and previous was cleared
+    verify(uniqueRows).putRow(any(IRowMeta.class), any(Object[].class));
+    assertNull(transformMockHelper.iTransformData.previous);
+  }
+
+  @Test
+  void testBatchCompleteWithNoPreviousRow() throws Exception {
+    when(transformMockHelper.iTransformMeta.getCompareFields()).thenReturn(new 
ArrayList<>());
+
+    UniqueRows uniqueRows =
+        new UniqueRows(
+            transformMockHelper.transformMeta,
+            transformMockHelper.iTransformMeta,
+            transformMockHelper.iTransformData,
+            0,
+            transformMockHelper.pipelineMeta,
+            transformMockHelper.pipeline);
+
+    assertTrue(uniqueRows.init());
+
+    // Ensure no previous row
+    transformMockHelper.iTransformData.previous = null;
+
+    // Spy on the transform to mock putRow
+    uniqueRows = spy(uniqueRows);
+
+    // Call batchComplete
+    uniqueRows.batchComplete();
+
+    // Verify that putRow was NOT called
+    verify(uniqueRows, org.mockito.Mockito.never())
+        .putRow(any(IRowMeta.class), any(Object[].class));
+  }
+
+  @Test
+  void testProcessRowWithNullInput() throws Exception {
+    when(transformMockHelper.iTransformMeta.getCompareFields()).thenReturn(new 
ArrayList<>());
+    when(transformMockHelper.iTransformMeta.isCountRows()).thenReturn(false);
+
+    UniqueRows uniqueRows =
+        new UniqueRows(
+            transformMockHelper.transformMeta,
+            transformMockHelper.iTransformMeta,
+            transformMockHelper.iTransformData,
+            0,
+            transformMockHelper.pipelineMeta,
+            transformMockHelper.pipeline);
+
+    assertTrue(uniqueRows.init());
+
+    // Setup input row meta
+    RowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaString("name"));
+    uniqueRows.setInputRowMeta(inputRowMeta);
+
+    // Set up previous row in data
+    transformMockHelper.iTransformData.previous = new Object[] {"Alice"};
+    transformMockHelper.iTransformData.outputRowMeta = inputRowMeta;
+    transformMockHelper.iTransformData.counter = 1;
+
+    // Spy on the transform to mock getRow() to return null
+    uniqueRows = spy(uniqueRows);
+    doReturn(null).when(uniqueRows).getRow();
+
+    // Execute processRow - should handle null input
+    boolean result = uniqueRows.processRow();
+
+    assertFalse(result);
+    // Verify that the previous row was output
+    verify(uniqueRows).putRow(any(IRowMeta.class), any(Object[].class));
+  }
+}

Reply via email to