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 a51d9551a1 Fix empty first row in Generate Random Value type picker 
and add unit tests (#7473)
a51d9551a1 is described below

commit a51d9551a1b168a3ca5b2fd90ae303eb9a698ae1
Author: Lance <[email protected]>
AuthorDate: Fri Jul 10 16:15:29 2026 +0800

    Fix empty first row in Generate Random Value type picker and add unit tests 
(#7473)
    
    Signed-off-by: lance <[email protected]>
---
 .../transforms/randomvalue/RandomValueMeta.java    |   6 +-
 .../transforms/randomvalue/RandomTypeTest.java     | 133 +++++++++
 .../randomvalue/RandomValueDataTest.java           | 100 +++++++
 .../randomvalue/RandomValueMetaTest.java           | 206 ++++++++++++++
 .../transforms/randomvalue/RandomValueTest.java    | 303 +++++++++++++++++++++
 5 files changed, 747 insertions(+), 1 deletion(-)

diff --git 
a/plugins/transforms/randomvalue/src/main/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueMeta.java
 
b/plugins/transforms/randomvalue/src/main/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueMeta.java
index 29d82e9d46..885c0f22ac 100644
--- 
a/plugins/transforms/randomvalue/src/main/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueMeta.java
+++ 
b/plugins/transforms/randomvalue/src/main/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueMeta.java
@@ -17,6 +17,7 @@
 package org.apache.hop.pipeline.transforms.randomvalue;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import lombok.Getter;
 import lombok.Setter;
@@ -167,7 +168,10 @@ public class RandomValueMeta extends 
BaseTransformMeta<RandomValue, RandomValueD
     }
 
     public static String[] getDescriptions() {
-      return IEnumHasCodeAndDescription.getDescriptions(RandomType.class);
+      return Arrays.stream(RandomType.values())
+          .filter(t -> t != RandomType.NONE)
+          .map(RandomType::getDescription)
+          .toArray(String[]::new);
     }
 
     public static RandomType lookupDescription(String description) {
diff --git 
a/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomTypeTest.java
 
b/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomTypeTest.java
new file mode 100644
index 0000000000..df03c1944f
--- /dev/null
+++ 
b/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomTypeTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.randomvalue;
+
+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.assertTrue;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+
+/** Unit test for {@link RandomValueMeta.RandomType}. */
+class RandomTypeTest {
+
+  @Test
+  void testEnumBasicProperties() {
+    RandomValueMeta.RandomType type = RandomValueMeta.RandomType.NUMBER;
+
+    assertEquals("random number", type.getCode());
+    assertNotNull(type.getDescription());
+    assertFalse(type.getDescription().isEmpty());
+  }
+
+  @Test
+  void testNoneDefault() {
+    RandomValueMeta.RandomType none = RandomValueMeta.RandomType.NONE;
+
+    assertEquals("", none.getCode());
+    assertEquals("", none.getDescription());
+  }
+
+  @Test
+  void testLookupDescriptionSuccess() {
+    String desc = RandomValueMeta.RandomType.INTEGER.getDescription();
+
+    RandomValueMeta.RandomType result = 
RandomValueMeta.RandomType.lookupDescription(desc);
+    assertEquals(RandomValueMeta.RandomType.INTEGER, result);
+  }
+
+  @Test
+  void testLookupDescriptionNotFound() {
+    RandomValueMeta.RandomType result = 
RandomValueMeta.RandomType.lookupDescription("not-exist");
+    assertEquals(RandomValueMeta.RandomType.NONE, result);
+  }
+
+  @Test
+  void testLookupCodeSuccess() {
+    RandomValueMeta.RandomType result = 
RandomValueMeta.RandomType.lookupCode("random uuid4");
+    assertEquals(RandomValueMeta.RandomType.UUID4, result);
+  }
+
+  @Test
+  void testLookupCodeNotFound() {
+    RandomValueMeta.RandomType result = 
RandomValueMeta.RandomType.lookupCode("not-exist");
+    assertEquals(RandomValueMeta.RandomType.NONE, result);
+  }
+
+  @Test
+  void testGetDescriptionsExcludesNone() {
+    String[] descriptions = RandomValueMeta.RandomType.getDescriptions();
+
+    assertNotNull(descriptions);
+    assertEquals(RandomValueMeta.RandomType.values().length - 1, 
descriptions.length);
+    for (String description : descriptions) {
+      assertFalse(description.isEmpty());
+    }
+  }
+
+  @Test
+  void testGetDescriptionsContainsKnownType() {
+    String[] descriptions = RandomValueMeta.RandomType.getDescriptions();
+
+    boolean found = false;
+    for (String desc : descriptions) {
+      if (desc.equals(RandomValueMeta.RandomType.STRING.getDescription())) {
+        found = true;
+        break;
+      }
+    }
+
+    assertTrue(found);
+  }
+
+  @Test
+  void testAllEnumHaveCodeAndDescription() {
+    for (RandomValueMeta.RandomType type : 
RandomValueMeta.RandomType.values()) {
+      assertNotNull(type.getCode(), type.name() + " code is null");
+      assertNotNull(type.getDescription(), type.name() + " description is 
null");
+    }
+  }
+
+  @Test
+  void testDescriptionUniqueAmongSelectableTypes() {
+    Set<String> set = new HashSet<>();
+
+    for (RandomValueMeta.RandomType type : 
RandomValueMeta.RandomType.values()) {
+      if (type == RandomValueMeta.RandomType.NONE) {
+        continue;
+      }
+      String desc = type.getDescription();
+      assertTrue(set.add(desc), "Duplicate description: " + desc);
+    }
+  }
+
+  @Test
+  void testDescriptionNotFallbackKey() {
+    for (RandomValueMeta.RandomType type : 
RandomValueMeta.RandomType.values()) {
+      if (type == RandomValueMeta.RandomType.NONE) {
+        continue;
+      }
+
+      String desc = type.getDescription();
+      assertFalse(
+          desc.contains("RandomValueMeta.TypeDesc."), "i18n not resolved for: 
" + type.name());
+    }
+  }
+}
diff --git 
a/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueDataTest.java
 
b/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueDataTest.java
new file mode 100644
index 0000000000..048b1705bb
--- /dev/null
+++ 
b/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueDataTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.randomvalue;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Random;
+import javax.crypto.KeyGenerator;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.util.Uuid4Util;
+import org.apache.hop.pipeline.transform.BaseTransformData;
+import org.junit.jupiter.api.Test;
+
+/** Unit test for {@link RandomValueData}. */
+class RandomValueDataTest {
+
+  @Test
+  void testDefaultState() {
+    RandomValueData data = new RandomValueData();
+
+    assertFalse(data.readsRows);
+    assertNull(data.outputRowMeta);
+    assertNull(data.u4);
+    assertNull(data.keyGenHmacMD5);
+    assertNull(data.keyGenHmacSHA1);
+    assertNull(data.keyGenHmacSHA256);
+    assertNull(data.keyGenHmacSHA512);
+    assertNull(data.keyGenHmacSHA384);
+    assertNull(data.randomGenerator);
+  }
+
+  @Test
+  void testFieldAssignment() {
+    RandomValueData data = new RandomValueData();
+    RowMeta rowMeta = new RowMeta();
+    Random random = new Random(42);
+    Uuid4Util uuid4Util = new Uuid4Util();
+
+    data.readsRows = true;
+    data.outputRowMeta = rowMeta;
+    data.randomGenerator = random;
+    data.u4 = uuid4Util;
+
+    assertTrue(data.readsRows);
+    assertSame(rowMeta, data.outputRowMeta);
+    assertSame(random, data.randomGenerator);
+    assertSame(uuid4Util, data.u4);
+  }
+
+  @Test
+  void testKeyGeneratorAssignment() throws Exception {
+    RandomValueData data = new RandomValueData();
+
+    data.keyGenHmacMD5 = KeyGenerator.getInstance("HmacMD5");
+    data.keyGenHmacSHA1 = KeyGenerator.getInstance("HmacSHA1");
+
+    assertNotNull(data.keyGenHmacMD5);
+    assertNotNull(data.keyGenHmacSHA1);
+    assertEquals("HmacMD5", data.keyGenHmacMD5.getAlgorithm());
+    assertEquals("HmacSHA1", data.keyGenHmacSHA1.getAlgorithm());
+  }
+
+  @Test
+  void testInheritance() {
+    RandomValueData data = new RandomValueData();
+
+    assertNotNull(data);
+    assertInstanceOf(BaseTransformData.class, data);
+  }
+
+  @Test
+  void testThreadSafetyBasic() throws InterruptedException {
+    RandomValueData data = new RandomValueData();
+
+    Thread t = Thread.startVirtualThread(() -> data.readsRows = true);
+    t.join();
+
+    assertTrue(data.readsRows);
+  }
+}
diff --git 
a/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueMetaTest.java
 
b/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueMetaTest.java
index e9ae94c26f..9740ea2bed 100644
--- 
a/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueMetaTest.java
+++ 
b/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueMetaTest.java
@@ -17,11 +17,30 @@
 package org.apache.hop.pipeline.transforms.randomvalue;
 
 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.assertNotSame;
 
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import org.apache.hop.core.ICheckResult;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.core.xml.XmlHandler;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.metadata.serializer.xml.XmlMetadataUtil;
+import org.apache.hop.pipeline.transform.TransformMeta;
 import org.apache.hop.pipeline.transform.TransformSerializationTestUtil;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
 
+/** Unit test for {@link RandomValueMeta}. */
 class RandomValueMetaTest {
 
   @Test
@@ -49,4 +68,191 @@ class RandomValueMetaTest {
     assertEquals(RandomValueMeta.RandomType.HMAC_SHA1, 
fields.get(6).getType());
     assertEquals("hmac_sha1", fields.get(6).getName());
   }
+
+  @Test
+  void testLoadSaveRoundTrip() throws Exception {
+    Path path =
+        Paths.get(
+            
Objects.requireNonNull(getClass().getResource("/generate-random-values-transform.xml"))
+                .toURI());
+    String xml = Files.readString(path);
+    RandomValueMeta meta = new RandomValueMeta();
+    XmlMetadataUtil.deSerializeFromXml(
+        XmlHandler.loadXmlString(xml, TransformMeta.XML_TAG),
+        RandomValueMeta.class,
+        meta,
+        new MemoryMetadataProvider());
+
+    validateLoadedMeta(meta);
+
+    String xmlCopy =
+        XmlHandler.openTag(TransformMeta.XML_TAG)
+            + XmlMetadataUtil.serializeObjectToXml(meta)
+            + XmlHandler.closeTag(TransformMeta.XML_TAG);
+    RandomValueMeta metaCopy = new RandomValueMeta();
+    XmlMetadataUtil.deSerializeFromXml(
+        XmlHandler.loadXmlString(xmlCopy, TransformMeta.XML_TAG),
+        RandomValueMeta.class,
+        metaCopy,
+        new MemoryMetadataProvider());
+
+    validateLoadedMeta(metaCopy);
+  }
+
+  @Test
+  void testRvFieldDefaultsToNone() {
+    RandomValueMeta.RVField field = new RandomValueMeta.RVField();
+
+    assertEquals(RandomValueMeta.RandomType.NONE, field.getType());
+  }
+
+  @Test
+  void testRvFieldCopyConstructor() {
+    RandomValueMeta.RVField original = new RandomValueMeta.RVField();
+    original.setName("field1");
+    original.setType(RandomValueMeta.RandomType.STRING);
+
+    RandomValueMeta.RVField copy = new RandomValueMeta.RVField(original);
+
+    assertEquals("field1", copy.getName());
+    assertEquals(RandomValueMeta.RandomType.STRING, copy.getType());
+    assertNotSame(original, copy);
+  }
+
+  @Test
+  void testCloneDeepCopy() {
+    RandomValueMeta meta = sampleMeta();
+
+    RandomValueMeta cloned = meta.clone();
+
+    assertNotSame(meta, cloned);
+    assertEquals(meta.getSeed(), cloned.getSeed());
+    assertEquals(meta.getFields().size(), cloned.getFields().size());
+    assertNotSame(meta.getFields().getFirst(), cloned.getFields().getFirst());
+    assertEquals(meta.getFields().getFirst().getName(), 
cloned.getFields().getFirst().getName());
+    assertEquals(meta.getFields().getFirst().getType(), 
cloned.getFields().getFirst().getType());
+  }
+
+  @Test
+  void testCopyConstructor() {
+    RandomValueMeta meta = sampleMeta();
+
+    RandomValueMeta copy = new RandomValueMeta(meta);
+
+    assertEquals(meta.getSeed(), copy.getSeed());
+    assertEquals(1, copy.getFields().size());
+    assertNotSame(meta.getFields().getFirst(), copy.getFields().getFirst());
+  }
+
+  @ParameterizedTest
+  @EnumSource(RandomValueMeta.RandomType.class)
+  void testGetFieldsProducesExpectedValueMetaType(RandomValueMeta.RandomType 
type) {
+    RandomValueMeta meta = new RandomValueMeta();
+    RandomValueMeta.RVField field = new RandomValueMeta.RVField();
+    field.setName("f1");
+    field.setType(type);
+    meta.getFields().add(field);
+
+    RowMeta rowMeta = new RowMeta();
+    meta.getFields(rowMeta, "transform", null, null, new Variables(), null);
+
+    assertEquals(1, rowMeta.size());
+    IValueMeta valueMeta = rowMeta.getValueMeta(0);
+    assertEquals("f1", valueMeta.getName());
+    assertEquals("transform", valueMeta.getOrigin());
+
+    switch (type) {
+      case NUMBER -> {
+        assertEquals(IValueMeta.TYPE_NUMBER, valueMeta.getType());
+        assertEquals(10, valueMeta.getLength());
+        assertEquals(5, valueMeta.getPrecision());
+      }
+      case INTEGER -> {
+        assertEquals(IValueMeta.TYPE_INTEGER, valueMeta.getType());
+        assertEquals(10, valueMeta.getLength());
+        assertEquals(0, valueMeta.getPrecision());
+      }
+      case STRING -> {
+        assertEquals(IValueMeta.TYPE_STRING, valueMeta.getType());
+        assertEquals(13, valueMeta.getLength());
+      }
+      case UUID, UUID4 -> {
+        assertEquals(IValueMeta.TYPE_STRING, valueMeta.getType());
+        assertEquals(36, valueMeta.getLength());
+      }
+      case HMAC_MD5, HMAC_SHA1, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384 -> {
+        assertEquals(IValueMeta.TYPE_STRING, valueMeta.getType());
+        assertEquals(100, valueMeta.getLength());
+      }
+      case NONE -> assertEquals(IValueMeta.TYPE_NONE, valueMeta.getType());
+    }
+  }
+
+  @Test
+  void testCheckErrorWhenTypeNone() {
+    RandomValueMeta meta = new RandomValueMeta();
+    RandomValueMeta.RVField field = new RandomValueMeta.RVField();
+    field.setName("missing-type");
+    field.setType(RandomValueMeta.RandomType.NONE);
+    meta.getFields().add(field);
+
+    List<ICheckResult> remarks = new ArrayList<>();
+    meta.check(remarks, null, null, null, null, null, null, new Variables(), 
null);
+
+    assertFalse(remarks.isEmpty());
+    assertEquals(ICheckResult.TYPE_RESULT_ERROR, remarks.getFirst().getType());
+  }
+
+  @Test
+  void testCheckOkWhenAllTypesSpecified() {
+    RandomValueMeta meta = sampleMeta();
+
+    List<ICheckResult> remarks = new ArrayList<>();
+    meta.check(remarks, null, null, null, null, null, null, new Variables(), 
null);
+
+    assertEquals(1, remarks.size());
+    assertEquals(ICheckResult.TYPE_RESULT_OK, remarks.getFirst().getType());
+  }
+
+  @Test
+  void testCheckReportsOnlyMissingTypes() {
+    RandomValueMeta meta = new RandomValueMeta();
+
+    RandomValueMeta.RVField valid = new RandomValueMeta.RVField();
+    valid.setName("valid");
+    valid.setType(RandomValueMeta.RandomType.NUMBER);
+    meta.getFields().add(valid);
+
+    RandomValueMeta.RVField invalid = new RandomValueMeta.RVField();
+    invalid.setName("invalid");
+    invalid.setType(RandomValueMeta.RandomType.NONE);
+    meta.getFields().add(invalid);
+
+    List<ICheckResult> remarks = new ArrayList<>();
+    meta.check(remarks, null, null, null, null, null, null, new Variables(), 
null);
+
+    assertEquals(1, remarks.size());
+    assertEquals(ICheckResult.TYPE_RESULT_ERROR, remarks.getFirst().getType());
+  }
+
+  private static RandomValueMeta sampleMeta() {
+    RandomValueMeta meta = new RandomValueMeta();
+    meta.setSeed("seed");
+
+    RandomValueMeta.RVField field = new RandomValueMeta.RVField();
+    field.setName("num");
+    field.setType(RandomValueMeta.RandomType.NUMBER);
+    meta.getFields().add(field);
+
+    return meta;
+  }
+
+  private static void validateLoadedMeta(RandomValueMeta meta) {
+    assertNotNull(meta.getFields());
+    assertFalse(meta.getFields().isEmpty());
+    assertEquals("12345", meta.getSeed());
+    assertEquals(7, meta.getFields().size());
+    assertEquals(RandomValueMeta.RandomType.NUMBER, 
meta.getFields().getFirst().getType());
+    assertEquals("num", meta.getFields().getFirst().getName());
+  }
 }
diff --git 
a/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueTest.java
 
b/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueTest.java
new file mode 100644
index 0000000000..ebf6f44482
--- /dev/null
+++ 
b/plugins/transforms/randomvalue/src/test/java/org/apache/hop/pipeline/transforms/randomvalue/RandomValueTest.java
@@ -0,0 +1,303 @@
+/*
+ * 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.randomvalue;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.UUID;
+import java.util.regex.Pattern;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.QueueRowSet;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.ILoggingObject;
+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.TransformMeta;
+import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+/** Unit tests for {@link RandomValue}. */
+@ExtendWith(RestoreHopEngineEnvironmentExtension.class)
+class RandomValueTest {
+
+  private static final Pattern UUID_PATTERN =
+      Pattern.compile(
+          "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", 
Pattern.CASE_INSENSITIVE);
+
+  private TransformMockHelper<RandomValueMeta, RandomValueData> mockHelper;
+
+  @BeforeEach
+  void setUp() {
+    mockHelper =
+        new TransformMockHelper<>("RANDOM_VALUE", RandomValueMeta.class, 
RandomValueData.class);
+    when(mockHelper.logChannelFactory.create(any(), any(ILoggingObject.class)))
+        .thenReturn(mockHelper.iLogChannel);
+    when(mockHelper.pipeline.isRunning()).thenReturn(true);
+    when(mockHelper.pipeline.isStopped()).thenReturn(false);
+  }
+
+  @AfterEach
+  void tearDown() {
+    mockHelper.cleanUp();
+  }
+
+  @Test
+  void processRowSingleRowModeProducesOneOutputRowAndStops() throws 
HopException {
+    RandomValueMeta meta = metaWithFields(field("num", 
RandomValueMeta.RandomType.NUMBER));
+    when(mockHelper.pipelineMeta.findPreviousTransforms(any())).thenReturn(new 
ArrayList<>());
+
+    RandomValueData data = new RandomValueData();
+    RandomValue transform = newTransform(meta, data);
+    QueueRowSet outputRowSet = new QueueRowSet();
+    transform.addRowSetToOutputRowSets(outputRowSet);
+
+    assertTrue(transform.init());
+    assertFalse(transform.processRow());
+
+    Object[] output = outputRowSet.getRow();
+    assertNotNull(output);
+    assertEquals(1, data.outputRowMeta.size());
+    assertNotNull(output[0]);
+    assertTrue(data.outputRowMeta.indexOfValue("num") >= 0);
+  }
+
+  @Test
+  void processRowSingleRowModeSeedProducesDeterministicValues() throws 
HopException {
+    RandomValueMeta meta =
+        metaWithSeed(
+            "12345",
+            field("num", RandomValueMeta.RandomType.NUMBER),
+            field("int", RandomValueMeta.RandomType.INTEGER),
+            field("str", RandomValueMeta.RandomType.STRING));
+    when(mockHelper.pipelineMeta.findPreviousTransforms(any())).thenReturn(new 
ArrayList<>());
+
+    Random r = new Random(Const.toLong("12345", 0));
+    double expectedNumber = r.nextDouble();
+    long expectedInteger = (long) r.nextInt();
+    String expectedString = Long.toString(Math.abs(r.nextLong()), 32);
+
+    RandomValueData data = new RandomValueData();
+    RandomValue transform = newTransform(meta, data);
+    QueueRowSet outputRowSet = new QueueRowSet();
+    transform.addRowSetToOutputRowSets(outputRowSet);
+
+    assertTrue(transform.init());
+    transform.processRow();
+
+    Object[] output = outputRowSet.getRow();
+    assertEquals(expectedNumber, output[0]);
+    assertEquals(expectedInteger, output[1]);
+    assertEquals(expectedString, output[2]);
+  }
+
+  @Test
+  void processRowReadsInputAndAppendsRandomValues() throws HopException {
+    RandomValueMeta meta =
+        metaWithSeed(
+            "99",
+            field("num", RandomValueMeta.RandomType.NUMBER),
+            field("str", RandomValueMeta.RandomType.STRING));
+    when(mockHelper.pipelineMeta.findPreviousTransforms(any()))
+        .thenReturn(List.of(mock(TransformMeta.class)));
+
+    RowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaString("input"));
+
+    RandomValueData data = new RandomValueData();
+    RandomValue transform = newTransform(meta, data);
+    transform.setInputRowMeta(inputRowMeta);
+    transform.addRowSetToInputRowSets(
+        mockHelper.getMockInputRowSet(new Object[][] {{"row-1"}, {"row-2"}}));
+    QueueRowSet outputRowSet = new QueueRowSet();
+    transform.addRowSetToOutputRowSets(outputRowSet);
+
+    assertTrue(transform.init());
+    assertTrue(transform.processRow());
+    Object[] firstOutput = outputRowSet.getRow();
+    assertEquals("row-1", firstOutput[0]);
+    assertNotNull(firstOutput[1]);
+    assertNotNull(firstOutput[2]);
+
+    assertTrue(transform.processRow());
+    Object[] secondOutput = outputRowSet.getRow();
+    assertEquals("row-2", secondOutput[0]);
+    assertNotNull(secondOutput[1]);
+    assertNotNull(secondOutput[2]);
+
+    assertFalse(transform.processRow());
+    assertNull(outputRowSet.getRow());
+  }
+
+  @ParameterizedTest
+  @EnumSource(
+      value = RandomValueMeta.RandomType.class,
+      names = {"NONE"},
+      mode = EnumSource.Mode.EXCLUDE)
+  void 
processRowSingleRowModeSupportsAllConfiguredTypes(RandomValueMeta.RandomType 
type)
+      throws HopException {
+    RandomValueMeta meta = metaWithFields(field("value", type));
+    when(mockHelper.pipelineMeta.findPreviousTransforms(any())).thenReturn(new 
ArrayList<>());
+
+    RandomValueData data = new RandomValueData();
+    RandomValue transform = newTransform(meta, data);
+    QueueRowSet outputRowSet = new QueueRowSet();
+    transform.addRowSetToOutputRowSets(outputRowSet);
+
+    assertTrue(transform.init());
+    transform.processRow();
+
+    Object[] output = outputRowSet.getRow();
+    assertNotNull(output);
+    assertEquals(1, data.outputRowMeta.size());
+    assertNotNull(output[0], "Expected value for type " + type);
+
+    switch (type) {
+      case NUMBER -> assertInstanceOf(Double.class, output[0]);
+      case INTEGER -> assertInstanceOf(Long.class, output[0]);
+      case STRING, UUID, UUID4, HMAC_MD5, HMAC_SHA1, HMAC_SHA256, HMAC_SHA512, 
HMAC_SHA384 ->
+          assertInstanceOf(String.class, output[0]);
+      default -> throw new AssertionError("Unhandled type: " + type);
+    }
+
+    if (type == RandomValueMeta.RandomType.UUID || type == 
RandomValueMeta.RandomType.UUID4) {
+      assertTrue(UUID_PATTERN.matcher((String) output[0]).matches());
+    }
+    if (type.name().startsWith("HMAC_")) {
+      assertFalse(((String) output[0]).isEmpty());
+    }
+  }
+
+  @Test
+  void initInitializesRandomGeneratorOnlyWhenNeeded() {
+    RandomValueMeta meta = metaWithFields(field("uuid", 
RandomValueMeta.RandomType.UUID));
+    when(mockHelper.pipelineMeta.findPreviousTransforms(any())).thenReturn(new 
ArrayList<>());
+
+    RandomValueData data = new RandomValueData();
+    RandomValue transform = newTransform(meta, data);
+
+    assertTrue(transform.init());
+    assertNull(data.randomGenerator);
+    assertNull(data.u4);
+  }
+
+  @Test
+  void initInitializesUuid4UtilWhenNeeded() {
+    RandomValueMeta meta = metaWithFields(field("uuid4", 
RandomValueMeta.RandomType.UUID4));
+    when(mockHelper.pipelineMeta.findPreviousTransforms(any())).thenReturn(new 
ArrayList<>());
+
+    RandomValueData data = new RandomValueData();
+    RandomValue transform = newTransform(meta, data);
+
+    assertTrue(transform.init());
+    assertNotNull(data.u4);
+  }
+
+  @Test
+  void initInitializesHmacGeneratorsWhenNeeded() {
+    RandomValueMeta meta =
+        metaWithFields(
+            field("md5", RandomValueMeta.RandomType.HMAC_MD5),
+            field("sha1", RandomValueMeta.RandomType.HMAC_SHA1),
+            field("sha256", RandomValueMeta.RandomType.HMAC_SHA256),
+            field("sha384", RandomValueMeta.RandomType.HMAC_SHA384),
+            field("sha512", RandomValueMeta.RandomType.HMAC_SHA512));
+    when(mockHelper.pipelineMeta.findPreviousTransforms(any())).thenReturn(new 
ArrayList<>());
+
+    RandomValueData data = new RandomValueData();
+    RandomValue transform = newTransform(meta, data);
+
+    assertTrue(transform.init());
+    assertNotNull(data.keyGenHmacMD5);
+    assertNotNull(data.keyGenHmacSHA1);
+    assertNotNull(data.keyGenHmacSHA256);
+    assertNotNull(data.keyGenHmacSHA384);
+    assertNotNull(data.keyGenHmacSHA512);
+  }
+
+  @Test
+  void initSetsReadsRowsWhenPreviousTransformsExist() {
+    RandomValueMeta meta = metaWithFields(field("num", 
RandomValueMeta.RandomType.NUMBER));
+    when(mockHelper.pipelineMeta.findPreviousTransforms(any()))
+        .thenReturn(List.of(mock(TransformMeta.class)));
+
+    RandomValueData data = new RandomValueData();
+    RandomValue transform = newTransform(meta, data);
+
+    assertTrue(transform.init());
+    assertTrue(data.readsRows);
+    assertNotNull(data.randomGenerator);
+  }
+
+  @Test
+  void processRowUuidValueHasStandardFormat() throws HopException {
+    RandomValueMeta meta = metaWithFields(field("uuid", 
RandomValueMeta.RandomType.UUID));
+    when(mockHelper.pipelineMeta.findPreviousTransforms(any())).thenReturn(new 
ArrayList<>());
+
+    RandomValueData data = new RandomValueData();
+    RandomValue transform = newTransform(meta, data);
+    QueueRowSet outputRowSet = new QueueRowSet();
+    transform.addRowSetToOutputRowSets(outputRowSet);
+
+    assertTrue(transform.init());
+    transform.processRow();
+
+    String uuid = (String) outputRowSet.getRow()[0];
+    assertNotNull(UUID.fromString(uuid));
+  }
+
+  private RandomValue newTransform(RandomValueMeta meta, RandomValueData data) 
{
+    return new RandomValue(
+        mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta, 
mockHelper.pipeline);
+  }
+
+  private static RandomValueMeta metaWithFields(RandomValueMeta.RVField... 
fields) {
+    return metaWithSeed(null, fields);
+  }
+
+  private static RandomValueMeta metaWithSeed(String seed, 
RandomValueMeta.RVField... fields) {
+    RandomValueMeta meta = new RandomValueMeta();
+    meta.setSeed(seed);
+    for (RandomValueMeta.RVField field : fields) {
+      meta.getFields().add(field);
+    }
+    return meta;
+  }
+
+  private static RandomValueMeta.RVField field(String name, 
RandomValueMeta.RandomType type) {
+    RandomValueMeta.RVField field = new RandomValueMeta.RVField();
+    field.setName(name);
+    field.setType(type);
+    return field;
+  }
+}

Reply via email to