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 12133f1b61 Fix null value for json output and languagemodelchat (#6919)
12133f1b61 is described below

commit 12133f1b6116e6910ac1f5a0f8c7a3016534c081
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Wed Apr 1 18:04:07 2026 +0200

    Fix null value for json output and languagemodelchat (#6919)
---
 .../jsonoutputenhanced/JsonEOutputMeta.java        |  2 +-
 .../jsonoutputenhanced/JsonEOutputMetaTest.java    | 53 +++++++++++++
 plugins/transforms/languagemodelchat/pom.xml       |  2 +-
 .../languagemodelchat/LanguageModelChat.java       | 63 ++++++++-------
 .../LanguageModelChatDataTest.java                 | 33 ++++++++
 .../internals/LanguageModelChatModelNameTest.java  | 34 +++++++++
 .../LanguageModelFacadeReadMessagesJsonTest.java   | 47 ++++++++++++
 .../internals/LanguageModelTest.java               | 77 +++++++++++++++++++
 .../languagemodelchat/internals/MessageTest.java   | 89 ++++++++++++++++++++++
 .../languagemodelchat/internals/ModelTypeTest.java | 55 +++++++++++++
 10 files changed, 427 insertions(+), 28 deletions(-)

diff --git 
a/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMeta.java
 
b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMeta.java
index ea79f6fd74..0935df79ce 100644
--- 
a/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMeta.java
+++ 
b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMeta.java
@@ -181,7 +181,7 @@ public class JsonEOutputMeta extends 
BaseTransformMeta<JsonEOutput, JsonEOutputD
     operationType = OperationType.WRITE_TO_FILE;
     encoding = Const.XML_ENCODING;
     outputValue = CONST_OUTPUT_VALUE;
-    jsonBloc = "result";
+    jsonBloc = "";
   }
 
   public JsonEOutputMeta(JsonEOutputMeta m) {
diff --git 
a/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMetaTest.java
 
b/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMetaTest.java
index 5b8db3cc44..bd1c79783a 100644
--- 
a/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMetaTest.java
+++ 
b/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsonoutputenhanced/JsonEOutputMetaTest.java
@@ -18,6 +18,7 @@
 
 package org.apache.hop.pipeline.transforms.jsonoutputenhanced;
 
+import static org.apache.hop.core.util.Utils.isEmpty;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -35,6 +36,58 @@ import org.junit.jupiter.api.Test;
 
 class JsonEOutputMetaTest {
 
+  @Test
+  void newMeta_defaultJsonBlocIsEmpty() {
+    assertTrue(isEmpty(new JsonEOutputMeta().getJsonBloc()));
+  }
+
+  @Test
+  void deserializeSelfClosingJsonBloc_overridesConstructor() throws Exception {
+    String xml =
+        "<transform>\n"
+            + "  <outputValue>prompt</outputValue>\n"
+            + "  <jsonBloc/>\n"
+            + "  <operation_type>outputvalue</operation_type>\n"
+            + "  
<use_arrays_with_single_instance>N</use_arrays_with_single_instance>\n"
+            + "  <use_single_item_per_group>N</use_single_item_per_group>\n"
+            + "  <json_prittified>N</json_prittified>\n"
+            + "  <encoding>UTF-8</encoding>\n"
+            + "  <addtoresult>N</addtoresult>\n"
+            + "  <file>\n"
+            + "    <name/>\n"
+            + "    <split_output_after>0</split_output_after>\n"
+            + "    <extention>json</extention>\n"
+            + "    <append>N</append>\n"
+            + "    <split>N</split>\n"
+            + "    <haspartno>N</haspartno>\n"
+            + "    <add_date>N</add_date>\n"
+            + "    <add_time>N</add_time>\n"
+            + "    <create_parent_folder>N</create_parent_folder>\n"
+            + "    <doNotOpenNewFileInit>N</doNotOpenNewFileInit>\n"
+            + "  </file>\n"
+            + "  <additional_fields>\n"
+            + "    <json_size_field/>\n"
+            + "  </additional_fields>\n"
+            + "  <key_fields/>\n"
+            + "  <fields>\n"
+            + "    <field>\n"
+            + "      <name>role</name>\n"
+            + "      <element>role</element>\n"
+            + "      <json_fragment>N</json_fragment>\n"
+            + "      <is_without_enclosing>N</is_without_enclosing>\n"
+            + "      <remove_if_blank>Y</remove_if_blank>\n"
+            + "    </field>\n"
+            + "  </fields>\n"
+            + "</transform>\n";
+    JsonEOutputMeta meta = new JsonEOutputMeta();
+    XmlMetadataUtil.deSerializeFromXml(
+        XmlHandler.loadXmlString(xml, TransformMeta.XML_TAG),
+        JsonEOutputMeta.class,
+        meta,
+        new MemoryMetadataProvider());
+    assertTrue(isEmpty(meta.getJsonBloc()));
+  }
+
   @Test
   void testLoadSave() throws Exception {
     Path path =
diff --git a/plugins/transforms/languagemodelchat/pom.xml 
b/plugins/transforms/languagemodelchat/pom.xml
index ec94aea0b6..dd015e7e80 100644
--- a/plugins/transforms/languagemodelchat/pom.xml
+++ b/plugins/transforms/languagemodelchat/pom.xml
@@ -49,7 +49,7 @@
         <dependency>
             <groupId>com.squareup.moshi</groupId>
             <artifactId>moshi</artifactId>
-            <version>1.3.1</version>
+            <version>1.15.2</version>
         </dependency>
         <dependency>
             <groupId>dev.langchain4j</groupId>
diff --git 
a/plugins/transforms/languagemodelchat/src/main/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChat.java
 
b/plugins/transforms/languagemodelchat/src/main/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChat.java
index 0afdb842ec..d30b38f6ad 100644
--- 
a/plugins/transforms/languagemodelchat/src/main/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChat.java
+++ 
b/plugins/transforms/languagemodelchat/src/main/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChat.java
@@ -27,7 +27,6 @@ import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.apache.commons.lang3.StringUtils.isBlank;
 import static org.apache.commons.lang3.StringUtils.isNotBlank;
 import static org.apache.commons.lang3.StringUtils.trim;
-import static 
org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage;
 import static org.apache.hop.core.util.Utils.isEmpty;
 import static 
org.apache.hop.pipeline.transforms.languagemodelchat.internals.ui.i18nUtil.i18n;
 
@@ -192,42 +191,40 @@ public class LanguageModelChat extends 
BaseTransform<LanguageModelChatMeta, Lang
         () -> {
           try {
             processSync(inputRow, message);
-            jobs.decrementAndGet();
-            synchronized (lock) {
-              lock.notifyAll();
-            }
           } catch (Exception e) {
             try {
-              processError(inputRow, e);
+              if (getTransformMeta().isDoingErrorHandling()) {
+                String errorMessage = e.toString();
+                putError(
+                    getInputRowMeta(),
+                    inputRow,
+                    1L,
+                    errorMessage,
+                    meta.getInputField(),
+                    "LanguageModelChat001");
+              } else {
+                logError(i18n("LanguageModelChat.ErrorInTransformRunning") + 
e.getMessage(), e);
+                setErrors(1L);
+                stopAll();
+              }
             } catch (HopTransformException ex) {
               logError(e.getMessage(), e);
               logError(ex.getMessage(), ex);
+              if (!getTransformMeta().isDoingErrorHandling()) {
+                setErrors(1L);
+                stopAll();
+              }
+            }
+          } finally {
+            jobs.decrementAndGet();
+            synchronized (lock) {
+              lock.notifyAll();
             }
           }
         });
     jobs.incrementAndGet();
   }
 
-  private void processError(Object[] inputRow, Exception e) throws 
HopTransformException {
-    logError(e.getMessage(), e);
-    int newFields = 8;
-    Object[] outputRow = new Object[inputRow.length + newFields + 
OVER_ALLOCATE_SIZE];
-    arraycopy(inputRow, 0, outputRow, 0, inputRow.length);
-
-    LanguageModel model = new LanguageModel(meta);
-
-    outputRow[data.indexOfModelType] = model.getType().code();
-    outputRow[data.indexOfModelName] = model.getName();
-    outputRow[data.indexOfFinishReason] = "error";
-    outputRow[data.indexOfInputTokenCount] = null;
-    outputRow[data.indexOfOutputTokenCount] = null;
-    outputRow[data.indexOfTotalTokenCount] = null;
-    outputRow[data.indexOfInferenceTime] = null;
-    outputRow[data.indexOfOutput] = getRootCauseMessage(e);
-
-    putRow(data.outputRowMeta, outputRow);
-  }
-
   private void processSync(Object[] inputRow, String message)
       throws HopTransformException, HopValueException {
 
@@ -387,4 +384,18 @@ public class LanguageModelChat extends 
BaseTransform<LanguageModelChatMeta, Lang
       super.putRow(rowMeta, row);
     }
   }
+
+  @Override
+  public void putError(
+      IRowMeta rowMeta,
+      Object[] row,
+      long nrErrors,
+      String errorDescriptions,
+      String fieldNames,
+      String errorCodes)
+      throws HopTransformException {
+    synchronized (this) {
+      super.putError(rowMeta, row, nrErrors, errorDescriptions, fieldNames, 
errorCodes);
+    }
+  }
 }
diff --git 
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChatDataTest.java
 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChatDataTest.java
new file mode 100644
index 0000000000..36ab9f3410
--- /dev/null
+++ 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/LanguageModelChatDataTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.languagemodelchat;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class LanguageModelChatDataTest {
+
+  @Test
+  void defaultIndicesAreUnset() {
+    LanguageModelChatData d = new LanguageModelChatData();
+    assertEquals(-1, d.indexOfInputField);
+    assertEquals(-1, d.indexOfOutput);
+    assertEquals(0, d.nrPrevFields);
+  }
+}
diff --git 
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelChatModelNameTest.java
 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelChatModelNameTest.java
new file mode 100644
index 0000000000..7c21004d44
--- /dev/null
+++ 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelChatModelNameTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.languagemodelchat.internals;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+
+class LanguageModelChatModelNameTest {
+
+  @Test
+  void everyEnumHasModelId() {
+    for (LanguageModelChatModelName n : LanguageModelChatModelName.values()) {
+      assertNotNull(n.toString());
+      assertFalse(n.toString().isBlank());
+    }
+  }
+}
diff --git 
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelFacadeReadMessagesJsonTest.java
 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelFacadeReadMessagesJsonTest.java
new file mode 100644
index 0000000000..5c424df8db
--- /dev/null
+++ 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelFacadeReadMessagesJsonTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.languagemodelchat.internals;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import 
org.apache.hop.pipeline.transforms.languagemodelchat.LanguageModelChatMeta;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class LanguageModelFacadeReadMessagesJsonTest {
+
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  private LanguageModelFacade facade() {
+    return new LanguageModelFacade(new Variables(), new 
LanguageModelChatMeta());
+  }
+
+  @Test
+  void readMessagesJson_topLevelArray() throws HopValueException {
+    List<Message> messages =
+        facade().readMessagesJson("[{\"role\":\"user\",\"content\":\"hello 
from array\"}]");
+    assertEquals(1, messages.size());
+    assertEquals("user", messages.get(0).getRole());
+    assertEquals("hello from array", messages.get(0).getContent());
+  }
+}
diff --git 
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelTest.java
 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelTest.java
new file mode 100644
index 0000000000..7b77ae4fef
--- /dev/null
+++ 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/LanguageModelTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.languagemodelchat.internals;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import 
org.apache.hop.pipeline.transforms.languagemodelchat.LanguageModelChatMeta;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class LanguageModelTest {
+
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  @Test
+  void openAiModel() {
+    LanguageModelChatMeta meta = new LanguageModelChatMeta();
+    meta.setModelType(ModelType.OPEN_AI.code());
+    meta.setOpenAiModelName("gpt-test");
+    LanguageModel lm = new LanguageModel(meta);
+    assertEquals(ModelType.OPEN_AI, lm.getType());
+    assertEquals("gpt-test", lm.getName());
+  }
+
+  @Test
+  void huggingFaceModel() {
+    LanguageModelChatMeta meta = new LanguageModelChatMeta();
+    meta.setModelType(ModelType.HUGGING_FACE.code());
+    meta.setHuggingFaceModelId("org/llama");
+    LanguageModel lm = new LanguageModel(meta);
+    assertEquals(ModelType.HUGGING_FACE, lm.getType());
+    assertEquals("org/llama", lm.getName());
+  }
+
+  @Test
+  void equalsAndHashCode() {
+    LanguageModelChatMeta m1 = new LanguageModelChatMeta();
+    m1.setModelType(ModelType.OLLAMA.code());
+    m1.setOllamaModelName("phi3");
+    LanguageModelChatMeta m2 = new LanguageModelChatMeta();
+    m2.setModelType(ModelType.OLLAMA.code());
+    m2.setOllamaModelName("phi3");
+    LanguageModel a = new LanguageModel(m1);
+    LanguageModel b = new LanguageModel(m2);
+    assertEquals(a, b);
+    assertEquals(a.hashCode(), b.hashCode());
+  }
+
+  @Test
+  void notEqualsWhenNameDiffers() {
+    LanguageModelChatMeta m1 = new LanguageModelChatMeta();
+    m1.setModelType(ModelType.MISTRAL.code());
+    m1.setMistralModelName("a");
+    LanguageModelChatMeta m2 = new LanguageModelChatMeta();
+    m2.setModelType(ModelType.MISTRAL.code());
+    m2.setMistralModelName("b");
+    assertNotEquals(new LanguageModel(m1), new LanguageModel(m2));
+  }
+}
diff --git 
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/MessageTest.java
 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/MessageTest.java
new file mode 100644
index 0000000000..1ecdff432a
--- /dev/null
+++ 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/MessageTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.languagemodelchat.internals;
+
+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 dev.langchain4j.data.message.ChatMessage;
+import dev.langchain4j.data.message.ChatMessageType;
+import java.util.List;
+import java.util.Optional;
+import org.apache.hop.core.exception.HopValueException;
+import org.junit.jupiter.api.Test;
+
+class MessageTest {
+
+  @Test
+  void toChatMessages_systemAndAssistant() throws HopValueException {
+    Message sys = new Message();
+    sys.setRole("system");
+    sys.setContent("s");
+    Message asst = new Message();
+    asst.setRole("assistant");
+    asst.setContent("a");
+    assertEquals(1, Message.toChatMessages(List.of(sys)).size());
+    assertEquals(1, Message.toChatMessages(List.of(asst)).size());
+  }
+
+  @Test
+  void toChatMessages_userText() throws HopValueException {
+    Message m = new Message();
+    m.setRole("user");
+    m.setContent("hello");
+    List<ChatMessage> out = Message.toChatMessages(List.of(m));
+    assertEquals(1, out.size());
+    assertEquals(ChatMessageType.USER, out.get(0).type());
+  }
+
+  @Test
+  void toChatMessages_missingRole_throws() {
+    Message m = new Message();
+    m.setContent("orphan content");
+    NullPointerException ex =
+        assertThrows(NullPointerException.class, () -> 
Message.toChatMessages(List.of(m)));
+    assertTrue(ex.getMessage().contains("role"));
+  }
+
+  @Test
+  void toChatMessages_invalidRole() {
+    Message m = new Message();
+    m.setRole("unknown-role");
+    m.setContent("x");
+    assertThrows(HopValueException.class, () -> 
Message.toChatMessages(List.of(m)));
+  }
+
+  @Test
+  void detectRoleNames() {
+    Message u = new Message();
+    u.setRole("user");
+    Message s = new Message();
+    s.setRole("system");
+    Message a = new Message();
+    a.setRole("assistant");
+    assertEquals(Optional.of("user"), Message.detectUserRoleName(List.of(u, 
s)));
+    assertEquals(Optional.of("system"), 
Message.detectSystemRoleName(List.of(u, s)));
+    assertEquals(Optional.of("assistant"), 
Message.detectAssistantRoleName(List.of(u, a)));
+  }
+
+  @Test
+  void detectRoleNames_empty() {
+    assertTrue(Message.detectUserRoleName(List.of()).isEmpty());
+  }
+}
diff --git 
a/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/ModelTypeTest.java
 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/ModelTypeTest.java
new file mode 100644
index 0000000000..b081134674
--- /dev/null
+++ 
b/plugins/transforms/languagemodelchat/src/test/java/org/apache/hop/pipeline/transforms/languagemodelchat/internals/ModelTypeTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.languagemodelchat.internals;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class ModelTypeTest {
+
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  @Test
+  void modelTypeDescriptions_hasEntryPerEnum() {
+    assertEquals(ModelType.values().length, 
ModelType.modelTypeDescriptions().length);
+  }
+
+  @Test
+  void typeFromDescription_matchesEnumDescription() {
+    ModelType openAi = ModelType.OPEN_AI;
+    assertEquals(openAi, ModelType.typeFromDescription(openAi.description()));
+  }
+
+  @Test
+  void typeFromDescription_unknownFallsBackToOpenAi() {
+    assertEquals(ModelType.OPEN_AI, 
ModelType.typeFromDescription("___not_a_real_model_label___"));
+  }
+
+  @Test
+  void codeAndDescriptionPresent() {
+    for (ModelType t : ModelType.values()) {
+      assertNotNull(t.code());
+      assertNotNull(t.description());
+    }
+  }
+}

Reply via email to