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 51fd4b68d7 Fixes #8531 : Two integration tests assert behaviour the 
product never had (#8535)
51fd4b68d7 is described below

commit 51fd4b68d7b937f389f74449c412984179dbaf82
Author: Bart Maertens <[email protected]>
AuthorDate: Tue Sep 22 20:43:24 2026 +0200

    Fixes #8531 : Two integration tests assert behaviour the product never had 
(#8535)
---
 .../database/validation/ColumnValueValidator.java  | 32 +++++----
 .../validation/ColumnValueValidatorTest.java       | 26 +++++++
 .../lint/main-0001-lint-check-db-connections.hwf   | 83 ++++++++++++++--------
 .../main/java/org/apache/hop/lint/HopLinter.java   | 62 ++++++++++++----
 .../hop/lint/PipelineVerifyLintExtension.java      | 29 +++++---
 .../hop/lint/LintSuppressionInEditorTest.java      | 47 ++++++++++++
 .../hop/uuid/UuidColumnValueValidationTest.java    | 71 ++++++++++++++++++
 7 files changed, 284 insertions(+), 66 deletions(-)

diff --git 
a/core/src/main/java/org/apache/hop/core/database/validation/ColumnValueValidator.java
 
b/core/src/main/java/org/apache/hop/core/database/validation/ColumnValueValidator.java
index aac89ff95d..88e2e36d2b 100644
--- 
a/core/src/main/java/org/apache/hop/core/database/validation/ColumnValueValidator.java
+++ 
b/core/src/main/java/org/apache/hop/core/database/validation/ColumnValueValidator.java
@@ -87,18 +87,26 @@ public final class ColumnValueValidator {
         converted = spec.getTargetValueMeta().convertData(streamMeta, value);
         workingMeta = spec.getTargetValueMeta();
       } catch (HopValueException e) {
-        errors.add(
-            error(
-                field,
-                spec,
-                ColumnValueErrorCode.CONVERSION,
-                BaseMessages.getString(
-                    PKG,
-                    "ColumnValueValidator.Conversion",
-                    spec.getColumnName(),
-                    typeLabel(spec),
-                    Const.NVL(e.getMessage(), ""),
-                    preview(streamMeta, value, omitValues))));
+        // A uuid or json column is read as its own value type, so a value 
that is not one fails
+        // to convert before the check that knows what is wrong with it can 
run. Those checks go
+        // first here: they say what is wrong with the value, where the 
converter can only report
+        // that the target type refused it, in whatever words the driver or 
parser used.
+        checkUuid(spec, field, streamMeta, value, omitValues, errors);
+        checkJson(spec, field, streamMeta, value, omitValues, errors);
+        if (errors.isEmpty()) {
+          errors.add(
+              error(
+                  field,
+                  spec,
+                  ColumnValueErrorCode.CONVERSION,
+                  BaseMessages.getString(
+                      PKG,
+                      "ColumnValueValidator.Conversion",
+                      spec.getColumnName(),
+                      typeLabel(spec),
+                      Const.NVL(e.getMessage(), ""),
+                      preview(streamMeta, value, omitValues))));
+        }
         return errors;
       }
     }
diff --git 
a/core/src/test/java/org/apache/hop/core/database/validation/ColumnValueValidatorTest.java
 
b/core/src/test/java/org/apache/hop/core/database/validation/ColumnValueValidatorTest.java
index 3254b21b60..83834e02a7 100644
--- 
a/core/src/test/java/org/apache/hop/core/database/validation/ColumnValueValidatorTest.java
+++ 
b/core/src/test/java/org/apache/hop/core/database/validation/ColumnValueValidatorTest.java
@@ -27,6 +27,7 @@ import org.apache.hop.core.HopClientEnvironment;
 import org.apache.hop.core.row.IValueMeta;
 import org.apache.hop.core.row.value.ValueMetaBigNumber;
 import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.row.value.ValueMetaJson;
 import org.apache.hop.core.row.value.ValueMetaString;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -166,6 +167,31 @@ class ColumnValueValidatorTest {
         ColumnValueValidator.validate(json, "payload", meta, "{", 
false).get(0).code());
   }
 
+  /**
+   * What PostgreSQL actually hands the validator. A jsonb column is read as 
{@link ValueMetaJson},
+   * not as a string, so an invalid document fails to convert to the target 
type before the JSON
+   * check ever sees it. Reported as a conversion failure it carried the 
parser's wording instead of
+   * naming the fault, and INVALID_JSON was unreachable on every database that 
has the type.
+   */
+  @Test
+  void invalidJsonOnAColumnReadAsJson() {
+    ColumnValueConstraints json = new ColumnValueConstraints();
+    json.setColumnName("payload");
+    json.setJson(true);
+    json.setHopType(IValueMeta.TYPE_JSON);
+    json.setTargetValueMeta(new ValueMetaJson("payload"));
+    ValueMetaString streamMeta = new ValueMetaString("payload");
+
+    assertTrue(
+        ColumnValueValidator.validate(json, "payload", streamMeta, 
"{\"a\":1}", false).isEmpty());
+
+    List<ColumnValueError> errors =
+        ColumnValueValidator.validate(json, "payload", streamMeta, "{", false);
+    assertEquals(1, errors.size());
+    assertEquals(ColumnValueErrorCode.INVALID_JSON, errors.get(0).code());
+    assertEquals("column 'payload': invalid JSON; value='{'", 
errors.get(0).message());
+  }
+
   @Test
   void omitValuesHidesFailedData() {
     ColumnValueConstraints spec = varchar("name", 1);
diff --git a/integration-tests/lint/main-0001-lint-check-db-connections.hwf 
b/integration-tests/lint/main-0001-lint-check-db-connections.hwf
index 9dd425f8d2..ceb9973eae 100644
--- a/integration-tests/lint/main-0001-lint-check-db-connections.hwf
+++ b/integration-tests/lint/main-0001-lint-check-db-connections.hwf
@@ -20,7 +20,7 @@ limitations under the License.
 <workflow>
   <name>main-0001-lint-check-db-connections</name>
   <name_sync_with_filename>Y</name_sync_with_filename>
-  <description>Issue #8295: the linter must not report 
CONNECTION_DOES_NOT_EXIST for connections that are in the project 
metadata.</description>
+  <description>Issue #8295: the linter must not report a connection that is in 
the project metadata as missing.</description>
   <extended_description/>
   <workflow_version/>
   <created_user>-</created_user>
@@ -50,7 +50,7 @@ limitations under the License.
     </action>
     <action>
       <name>Lint the Check DB connections workflows</name>
-      <description>Runs hop lint over two subject workflows and checks the 
CONNECTION_DOES_NOT_EXIST findings.</description>
+      <description>Runs hop lint over two subject workflows and checks the 
missing-connection findings.</description>
       <type>SHELL</type>
       <attributes/>
       <filename/>
@@ -67,10 +67,15 @@ limitations under the License.
       <script>#!/bin/bash
 # Issue #8295: "Invalid linter warnings in action Check DB Connections".
 #
-# The linter reported CONNECTION_DOES_NOT_EXIST for database connections that 
are present in the
-# project metadata. The checker behind that rule 
(ReferencedDatabaseConnectionChecker) asks the
-# metadata serializer whether the connection exists, and used to report "does 
not exist" when that
-# lookup *failed* as well as when it answered no. A failed lookup is not 
evidence of absence.
+# The linter reported "does not exist" for database connections that are 
present in the project
+# metadata. The checker behind it (ReferencedDatabaseConnectionChecker) asks 
the metadata
+# serializer whether the connection exists, and used to report "does not 
exist" when that lookup
+# *failed* as well as when it answered no. A failed lookup is not evidence of 
absence.
+#
+# The assertions read the messages in the report, not the checker's error 
codes. A remark from
+# Hop's own verify is reported under the rule that covers it - HOP-CHECK in 
the core pack - so
+# CONNECTION_DOES_NOT_EXIST never reaches the JSON. The messages are what the 
three outcomes the
+# checker distinguishes look like from outside: absent, present, and never 
looked up at all.
 #
 # Note on ${} below: Hop resolves its own variables in this script before 
running it, so only Hop
 # variables are written with braces. Shell variables are written without them.
@@ -79,19 +84,13 @@ set -u
 
 PROJECT_DIR="${PROJECT_HOME}"
 
-# Finding the hop CLI: run-tests.sh sets HOP_LOCATION but does not export it, 
so it does not reach
-# this shell - printenv rather than $HOP_LOCATION, which would abort under 
"set -u". The test image
-# puts the client on the PATH, which is what normally answers here.
-HOP_CLI="$(printenv HOP_LOCATION 2>/dev/null || true)"
-if [ -n "$HOP_CLI" ] &amp;&amp; [ -x "$HOP_CLI/hop" ]; then
-  HOP_CLI="$HOP_CLI/hop"
-elif command -v hop > /dev/null 2>/dev/null; then
-  HOP_CLI="$(command -v hop)"
-elif [ -x /opt/hop/hop ]; then
-  HOP_CLI=/opt/hop/hop
-else
-  echo "FAIL: no hop CLI found (HOP_LOCATION is not set, hop is not on the 
PATH, and there is"
-  echo "      nothing at /opt/hop/hop). The linter cannot be tested without 
it."
+# Finding the hop CLI: hop-run.sh and hop-gui.sh both start the JVM from the 
Hop installation, so
+# ${user.dir} is the install folder - /opt/hop in the test image, the client 
folder when this
+# workflow is run from Hop GUI. Every system property is a Hop variable 
(Variables.initializeFrom),
+# so Hop substitutes it before this script runs.
+HOP_CLI="${user.dir}/hop"
+if [ ! -x "$HOP_CLI" ]; then
+  echo "FAIL: no hop CLI at $HOP_CLI. The linter cannot be tested without it."
   exit 1
 fi
 echo "Using hop CLI: $HOP_CLI"
@@ -100,7 +99,10 @@ echo "Using hop CLI: $HOP_CLI"
 # points at, exactly like the rest of this test suite.
 export HOP_CONFIG_FOLDER="$PROJECT_DIR"
 
-RULE=CONNECTION_DOES_NOT_EXIST
+# Fragments of the checker's messages, as 
engine/src/main/resources/org/apache/hop/metadata/
+# validation/messages/messages_en_US.properties writes them.
+MISSING_MSG="does not exist"
+UNVERIFIED_MSG="could not be checked"
 REPORT_DIR="$PROJECT_DIR/output"
 mkdir -p "$REPORT_DIR"
 
@@ -126,23 +128,34 @@ run_lint() {
   return 0
 }
 
-count_rule() {
-  grep -c "$RULE" "$1" 2>/dev/null || true
+# How many findings say $1. grep -c exits 1 on no match, which "set -u" would 
not mind but the
+# caller would read as a failed run, so a count of zero is printed rather than 
raised.
+count_message() {
+  grep -c "$1" "$2" 2>/dev/null || true
 }
 
 echo "=== Subject 1: every referenced connection exists (OPS, Vault, CRM) ==="
 EXISTING_REPORT="$REPORT_DIR/lint-existing-connections.json"
 if run_lint "$PROJECT_DIR/subject/existing-connections.hwf" 
"$EXISTING_REPORT"; then
-  found=$(count_rule "$EXISTING_REPORT")
+  found=$(count_message "$MISSING_MSG" "$EXISTING_REPORT")
+  unverified=$(count_message "$UNVERIFIED_MSG" "$EXISTING_REPORT")
   if [ "$found" -ne 0 ]; then
-    echo "FAIL: expected no $RULE findings, got $found."
+    echo "FAIL: expected no missing-connection findings, got $found."
     echo "      OPS, Vault and CRM are all present in 
$PROJECT_DIR/metadata/rdbms."
     echo "----- report -----"
     cat "$EXISTING_REPORT"
     echo "------------------"
     failures=$((failures + 1))
+  elif [ "$unverified" -ne 0 ]; then
+    # A clean report proves nothing if the lookup never answered. That is the 
state #8295 turned
+    # into a warning on every connection in the project, so it is worth 
failing on separately.
+    echo "FAIL: the connections could not be looked up, so this subject proves 
nothing."
+    echo "----- report -----"
+    cat "$EXISTING_REPORT"
+    echo "------------------"
+    failures=$((failures + 1))
   else
-    echo "OK: no $RULE findings for connections that exist."
+    echo "OK: connections that exist are not reported as missing."
   fi
 else
   failures=$((failures + 1))
@@ -151,15 +164,25 @@ fi
 echo "=== Subject 2: the referenced connection really is absent 
(NoSuchConnection) ==="
 MISSING_REPORT="$REPORT_DIR/lint-missing-connection.json"
 if run_lint "$PROJECT_DIR/subject/missing-connection.hwf" "$MISSING_REPORT"; 
then
-  found=$(count_rule "$MISSING_REPORT")
+  found=$(count_message "$MISSING_MSG" "$MISSING_REPORT")
   if [ "$found" -ne 1 ]; then
-    echo "FAIL: expected exactly one $RULE finding, got $found."
+    echo "FAIL: expected exactly one missing-connection finding, got $found."
+    echo "----- report -----"
+    cat "$MISSING_REPORT"
+    echo "------------------"
+    failures=$((failures + 1))
+  elif ! grep -q "'NoSuchConnection'.*$MISSING_MSG" "$MISSING_REPORT"; then
+    echo "FAIL: the missing-connection finding does not name NoSuchConnection."
     echo "----- report -----"
     cat "$MISSING_REPORT"
     echo "------------------"
     failures=$((failures + 1))
-  elif ! grep -q "NoSuchConnection" "$MISSING_REPORT"; then
-    echo "FAIL: the $RULE finding does not name NoSuchConnection."
+  elif ! grep -q '"info" : 0' "$MISSING_REPORT"; then
+    # An absent connection is a warning; only a lookup that failed reports at 
info. This subject
+    # holds one action, so an info finding here is that finding downgraded - 
and a finding below
+    # warning would not reach a build that fails on warnings. Reading the 
severity off the summary
+    # rather than off the finding keeps this from breaking when the report's 
field order changes.
+    echo "FAIL: the missing connection is reported below warning level."
     echo "----- report -----"
     cat "$MISSING_REPORT"
     echo "------------------"
@@ -192,7 +215,7 @@ exit 0
       <type>ABORT</type>
       <attributes/>
       <always_log_rows>N</always_log_rows>
-      <message>The Hop linter reported incorrect CONNECTION_DOES_NOT_EXIST 
findings. See issue #8295.</message>
+      <message>The Hop linter reported incorrect missing-connection findings. 
See issue #8295.</message>
       <parallel>N</parallel>
       <xloc>560</xloc>
       <yloc>208</yloc>
diff --git a/plugins/misc/lint/src/main/java/org/apache/hop/lint/HopLinter.java 
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/HopLinter.java
index 3c85a2bc7b..f1e9d5098a 100644
--- a/plugins/misc/lint/src/main/java/org/apache/hop/lint/HopLinter.java
+++ b/plugins/misc/lint/src/main/java/org/apache/hop/lint/HopLinter.java
@@ -130,6 +130,31 @@ public class HopLinter {
     }
   }
 
+  /**
+   * The rules in force for Hop's own verify remarks.
+   *
+   * <p>Every path that reports a native remark classifies it through this. A 
remark reported under
+   * one rule id from the CLI and another from the canvas cannot be suppressed 
once for both: the
+   * project writes the id it was shown, and {@link LintPolicy#isSuppressed} 
matches on it.
+   *
+   * <p>It is for native remarks alone. The blanket rule in the core pack 
names no plugin and no
+   * message, so it matches every {@link ICheckResult} put in front of it — 
including a lint finding
+   * that {@code toCheckResults} has turned into one, whose own rule id it 
would overwrite.
+   */
+  private NativeCheckClassifier nativeClassifier() {
+    ensureEffectiveRuleSet();
+    return new NativeCheckClassifier(effectiveRuleSet.getNativeVerifyRules());
+  }
+
+  /**
+   * Hop's own verify remarks as lint findings, reported under the rules in 
force.
+   *
+   * @param remarks Hop's own remarks, and only those
+   */
+  public List<LintResult> fromNativeRemarks(List<ICheckResult> remarks, String 
fileName) {
+    return LintCheckResultAdapter.fromCheckResults(remarks, fileName, 
nativeClassifier());
+  }
+
   /**
    * Run the linter on a Hop project
    *
@@ -483,13 +508,17 @@ public class HopLinter {
             pipelineMeta.checkTransforms(
                 remarks, false, new NullProgressMonitor(), variables, 
metadataProvider));
 
+    List<LintResult> results = new ArrayList<>(fromNativeRemarks(remarks, 
fileName));
+
     if (shouldIncludeLintInPipelineVerify()) {
-      remarks.addAll(
-          LintCheckResultAdapter.toCheckResults(
-              runPolicyRules(pipelineMeta, fileName), pipelineMeta));
+      results.addAll(
+          LintCheckResultAdapter.fromCheckResults(
+              LintCheckResultAdapter.toCheckResults(
+                  runPolicyRules(pipelineMeta, fileName), pipelineMeta),
+              fileName));
     }
 
-    return applyPolicy(LintCheckResultAdapter.fromCheckResults(remarks, 
fileName), fileName);
+    return applyPolicy(results, fileName);
   }
 
   /** Compute workflow lint results the same way as workflow verify plus 
optional policy rules. */
@@ -508,13 +537,17 @@ public class HopLinter {
             workflowMeta.checkActions(
                 remarks, false, new NullProgressMonitor(), variables, 
metadataProvider));
 
+    List<LintResult> results = new ArrayList<>(fromNativeRemarks(remarks, 
fileName));
+
     if (shouldIncludeLintInWorkflowVerify()) {
-      remarks.addAll(
-          WorkflowCheckResultAdapter.toCheckResults(
-              runPolicyRules(workflowMeta, fileName), workflowMeta));
+      results.addAll(
+          LintCheckResultAdapter.fromCheckResults(
+              WorkflowCheckResultAdapter.toCheckResults(
+                  runPolicyRules(workflowMeta, fileName), workflowMeta),
+              fileName));
     }
 
-    return applyPolicy(LintCheckResultAdapter.fromCheckResults(remarks, 
fileName), fileName);
+    return applyPolicy(results, fileName);
   }
 
   private boolean shouldIncludeLintInWorkflowVerify() {
@@ -584,14 +617,9 @@ public class HopLinter {
       throws HopException {
     List<LintResult> results = new ArrayList<>(policyResults);
     if (shouldIncludeNativeChecks() && hopObject != null) {
-      ensureEffectiveRuleSet();
       results.addAll(
           HopNativeCheckRunner.runNativeChecks(
-              hopObject,
-              fileName,
-              variables,
-              metadataProvider,
-              new 
NativeCheckClassifier(effectiveRuleSet.getNativeVerifyRules())));
+              hopObject, fileName, variables, metadataProvider, 
nativeClassifier()));
     }
     // Suppressions are applied last, so they cover Hop's native remarks as 
well as policy
     // findings — a team accepting something should not have to care which 
produced it.
@@ -668,9 +696,13 @@ public class HopLinter {
     // Resolved once: the project root is the same for every remark, and 
finding it walks the
     // filesystem.
     Path projectRoot = projectRootFor(fileName);
+    // Classified like anywhere else: a suppression names the rule id the 
person was shown, so
+    // reading the raw error code here would leave the finding on the canvas 
after they accepted
+    // it in the report, or the other way round.
+    NativeCheckClassifier classifier = nativeClassifier();
     remarks.removeIf(
         remark -> {
-          LintResult result = LintCheckResultAdapter.fromCheckResult(remark, 
fileName);
+          LintResult result = LintCheckResultAdapter.fromCheckResult(remark, 
fileName, classifier);
           return result != null && policy.isSuppressed(result, projectRoot);
         });
   }
diff --git 
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/PipelineVerifyLintExtension.java
 
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/PipelineVerifyLintExtension.java
index 71effe68b1..35ad6e23ef 100644
--- 
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/PipelineVerifyLintExtension.java
+++ 
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/PipelineVerifyLintExtension.java
@@ -16,7 +16,9 @@
  */
 package org.apache.hop.lint;
 
+import java.util.ArrayList;
 import java.util.List;
+import org.apache.hop.core.ICheckResult;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.extension.ExtensionPoint;
 import org.apache.hop.core.extension.IExtensionPoint;
@@ -63,18 +65,27 @@ public class PipelineVerifyLintExtension implements 
IExtensionPoint<CheckTransfo
         return;
       }
 
-      List<LintResult> policyResults =
-          linter.applyPolicy(linter.runPolicyRules(pipelineMeta, fileName), 
fileName);
-      extension
-          .getRemarks()
-          .addAll(LintCheckResultAdapter.toCheckResults(policyResults, 
pipelineMeta));
-
       // Hop collected its own remarks before this point, so they have passed 
no suppression yet.
+      // The policy findings added below have: applyPolicy suppresses them as 
it builds them.
       linter.removeSuppressed(extension.getRemarks(), fileName);
 
-      List<LintResult> verifyViewResults =
-          LintResultDeduplicator.deduplicate(
-              LintCheckResultAdapter.fromCheckResults(extension.getRemarks(), 
fileName));
+      // Read while the list still holds Hop's own remarks alone. The blanket 
native rule names no
+      // plugin and no message, so it matches anything put in front of it - 
including a policy
+      // finding turned into a remark, whose own rule id it would overwrite.
+      List<LintResult> results =
+          new ArrayList<>(linter.fromNativeRemarks(extension.getRemarks(), 
fileName));
+
+      List<LintResult> policyResults =
+          linter.applyPolicy(linter.runPolicyRules(pipelineMeta, fileName), 
fileName);
+      List<ICheckResult> policyRemarks =
+          LintCheckResultAdapter.toCheckResults(policyResults, pipelineMeta);
+      extension.getRemarks().addAll(policyRemarks);
+
+      // Through the same conversion as before. This view has always reported 
a policy finding as
+      // Hop's own verify output renders it, and reporting it differently here 
would leave the
+      // Problems bar disagreeing with the background lint about the same file.
+      results.addAll(LintCheckResultAdapter.fromCheckResults(policyRemarks, 
fileName));
+      List<LintResult> verifyViewResults = 
LintResultDeduplicator.deduplicate(results);
       LintResultsManager.getInstance().updateResultsForFile(fileName, 
verifyViewResults);
       LintProblemsBarManager.getInstance().updateProblemsBar(fileName);
 
diff --git 
a/plugins/misc/lint/src/test/java/org/apache/hop/lint/LintSuppressionInEditorTest.java
 
b/plugins/misc/lint/src/test/java/org/apache/hop/lint/LintSuppressionInEditorTest.java
index b7e5629d0d..c04a348a3a 100644
--- 
a/plugins/misc/lint/src/test/java/org/apache/hop/lint/LintSuppressionInEditorTest.java
+++ 
b/plugins/misc/lint/src/test/java/org/apache/hop/lint/LintSuppressionInEditorTest.java
@@ -144,6 +144,53 @@ public class LintSuppressionInEditorTest {
         "only the named rule should have been silenced: " + after);
   }
 
+  /**
+   * The rules the core pack states about Hop's own remarks have to reach the 
canvas.
+   *
+   * <p>They did not: only the command line classified native remarks, so a 
project that switched
+   * {@code HOP-CHECK} off saw its build go quiet while every badge stayed on 
the canvas, and a
+   * severity the pack had capped was still reported as the transform wrote it.
+   */
+  @Test
+  public void disablingTheNativeRuleSilencesTheEditorToo() throws Exception {
+    assertTrue(countOfRule(lintAsEditor(), "HOP-CHECK") > 0, "no native 
findings to switch off");
+
+    writeProjectConfig(
+        """
+        rules:
+          HOP-CHECK:
+            enabled: false
+        """);
+
+    List<LintResult> results = lintAsEditor();
+
+    assertEquals(
+        0, countOfRule(results, "HOP-CHECK"), "Hop's own remarks should be 
gone: " + results);
+    assertTrue(
+        results.stream().anyMatch(r -> "TRANS-002".equals(r.getRuleId())),
+        "switching off the native rule must leave the linter's own rules 
alone: " + results);
+  }
+
+  /**
+   * The blanket native rule names no plugin and no message, so it matches 
every check result put in
+   * front of it. The linter's own findings travel through {@code 
ICheckResult} on this path, and
+   * classifying them along with Hop's remarks would rename every one of them 
to {@code HOP-CHECK} -
+   * silently collapsing the rule ids a project writes its suppressions 
against.
+   */
+  @Test
+  public void policyFindingsKeepTheirOwnRuleIdInTheEditor() throws Exception {
+    List<LintResult> results = lintAsEditor();
+
+    assertTrue(
+        results.stream()
+            .anyMatch(r -> "TRANS-002".equals(r.getRuleId()) && "Fonte 
Sql".equals(sourceName(r))),
+        "the orphaned-transform finding on Fonte Sql lost its rule id: " + 
results);
+    assertTrue(
+        results.stream()
+            .anyMatch(r -> "TRANS-002".equals(r.getRuleId()) && "Salva 
S3".equals(sourceName(r))),
+        "the orphaned-transform finding on Salva S3 lost its rule id: " + 
results);
+  }
+
   private long countOfRule(List<LintResult> results, String ruleId) {
     return results.stream().filter(r -> ruleId.equals(r.getRuleId())).count();
   }
diff --git 
a/plugins/valuetypes/uuid/src/test/java/org/apache/hop/uuid/UuidColumnValueValidationTest.java
 
b/plugins/valuetypes/uuid/src/test/java/org/apache/hop/uuid/UuidColumnValueValidationTest.java
new file mode 100644
index 0000000000..4da404cec3
--- /dev/null
+++ 
b/plugins/valuetypes/uuid/src/test/java/org/apache/hop/uuid/UuidColumnValueValidationTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.uuid;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.database.validation.ColumnValueConstraints;
+import org.apache.hop.core.database.validation.ColumnValueError;
+import org.apache.hop.core.database.validation.ColumnValueErrorCode;
+import org.apache.hop.core.database.validation.ColumnValueValidator;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Database value validation against a column that Hop reads as {@link 
ValueMetaUuid}, which is what
+ * a PostgreSQL uuid column gives it. The validator's own tests can only use a 
string target, since
+ * the UUID value type lives here.
+ */
+class UuidColumnValueValidationTest {
+
+  @BeforeAll
+  static void init() throws Exception {
+    HopClientEnvironment.init();
+  }
+
+  /**
+   * A value that is not a UUID fails to convert to the target type before the 
UUID check sees it.
+   * Reported as a conversion failure it carried the converter's wording 
rather than naming the
+   * fault, and INVALID_UUID was unreachable on every database that has the 
type.
+   */
+  @Test
+  void invalidUuidOnAColumnReadAsUuid() {
+    ColumnValueConstraints spec = new ColumnValueConstraints();
+    spec.setColumnName("uid");
+    spec.setUuid(true);
+    spec.setHopType(IValueMeta.TYPE_UUID);
+    spec.setTargetValueMeta(new ValueMetaUuid("uid"));
+    ValueMetaString streamMeta = new ValueMetaString("uid");
+
+    assertTrue(
+        ColumnValueValidator.validate(
+                spec, "uid", streamMeta, 
"550e8400-e29b-41d4-a716-446655440000", false)
+            .isEmpty());
+
+    List<ColumnValueError> errors =
+        ColumnValueValidator.validate(spec, "uid", streamMeta, "not-a-uuid", 
false);
+    assertEquals(1, errors.size());
+    assertEquals(ColumnValueErrorCode.INVALID_UUID, errors.get(0).code());
+    assertEquals("column 'uid': invalid UUID; value='not-a-uuid'", 
errors.get(0).message());
+  }
+}

Reply via email to