You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kie.apache.org by ya...@apache.org on 2026/04/28 08:29:30 UTC

(incubator-kie-drools) branch main updated: [incubator-kie-issues#2174] Unit test cases for Dialect Handler classes (#6632)

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

yesamer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git


The following commit(s) were added to refs/heads/main by this push:
     new 68caddd2da [incubator-kie-issues#2174] Unit test cases for Dialect Handler classes (#6632)
68caddd2da is described below

commit 68caddd2dad3310fe8094583eb84a1ec01eb7d7f
Author: ChinchuAjith <Ch...@ibm.com>
AuthorDate: Tue Apr 28 13:59:26 2026 +0530

    [incubator-kie-issues#2174] Unit test cases for Dialect Handler classes (#6632)
    
    * Unit test cases fro Dialect Handler classes
    
    * Added logic to handle duration/period in multiplication and division scenarios
    
    * Added logic to handle exponentials in BFeel
    
    * Arranging tests in the order of precedence
    
    * Fixing review comments
    
    * Fixing review comments
---
 .../ast/dialectHandlers/BFEELDialectHandler.java   | 213 +++++++++++++--
 .../dialectHandlers/BFEELDialectHandlerTest.java   | 294 +++++++++++++++++++++
 .../dialectHandlers/DefaultDialectHandlerTest.java |  93 +++++++
 .../dialectHandlers/DialectHandlerFactoryTest.java | 120 +++++++++
 .../dialectHandlers/FEELDialectHandlerTest.java    | 140 ++++++++++
 5 files changed, 845 insertions(+), 15 deletions(-)

diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandler.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandler.java
index 626c94cff7..0ec669f737 100644
--- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandler.java
+++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandler.java
@@ -19,6 +19,7 @@
 package org.kie.dmn.feel.lang.ast.dialectHandlers;
 
 import java.math.BigDecimal;
+import java.math.MathContext;
 import java.time.Duration;
 import java.time.LocalDate;
 import java.time.chrono.ChronoPeriod;
@@ -33,7 +34,10 @@ import org.kie.dmn.feel.lang.FEELDialect;
 import org.kie.dmn.feel.lang.ast.infixexecutors.EqExecutor;
 import org.kie.dmn.feel.lang.types.impl.ComparablePeriod;
 
+import ch.obermuhlner.math.big.BigDecimalMath;
+
 import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.getString;
+import static org.kie.dmn.feel.util.NumberEvalHelper.getBigDecimalOrNull;
 
 /**
  * Handler implementation of the DialectHandler interface providing BFEEL specific
@@ -51,7 +55,23 @@ public class BFEELDialectHandler extends DefaultDialectHandler implements Dialec
     public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAddOperations(EvaluationContext ctx) {
         Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>();
 
-        // any String operand → concatenate both as strings
+        // B-FEEL Precedence Table for Addition (DMN 1.6 Section 11.9):
+        // Row 1: STRING - The non-string value is converted to a string using the string() B-FEEL function
+        //                 and Table 58 applies. Subtraction returns an empty string.
+        // Row 2: NUMBER - The non-number value is converted to a number using the number() B-FEEL function
+        //                 and Table 58 applies.
+        // Row 3: DATE AND TIME - The non-date and time value is converted to a duration using the duration()
+        //                        B-FEEL function and Table 58 applies.
+        // Row 4: DATE - The non-date value is converted to a duration using the duration() B-FEEL function
+        //               and Table 58 applies.
+        // Row 5: TIME - The non-time value is converted to a duration using the duration() B-FEEL function
+        //               and Table 58 applies.
+        // Row 6: YEARS AND MONTHS DURATION - The non-years and months duration value is converted to a
+        //                                     duration using the duration() B-FEEL function and Table 58 applies.
+        // Row 7: DAYS AND TIME DURATION - The non-days and time duration value is converted to a duration
+        //                                  using the duration() B-FEEL function and Table 58 applies.
+        
+        // Row 1: STRING - any String operand → concatenate both as strings (subtraction returns "")
         map.put(
                 new CheckedPredicate((left, right) -> left instanceof String || right instanceof String, false),
                 (left, right) -> {
@@ -60,20 +80,64 @@ public class BFEELDialectHandler extends DefaultDialectHandler implements Dialec
                     return leftNum + rightNum;
                 });
 
-        // date + number → return the number
+        // Row 2: NUMBER - date + number → convert date to 0, then add: 0 + number = number
+        // Since 0 + number = number, we return the number operand directly
         map.put(
                 new CheckedPredicate((left, right) -> left instanceof LocalDate && right instanceof Number, false),
                 (left, right) -> right);
+        
+        // Row 2: NUMBER - number + date → convert date to 0, then add: number + 0 = number
+        // Since number + 0 = number, we return the number operand directly
         map.put(
                 new CheckedPredicate((left, right) -> left instanceof Number && right instanceof LocalDate, false),
                 (left, right) -> left);
 
-        // Number + null → number
+        // Row 2: NUMBER - duration + number → convert duration to seconds, then add
+        map.put(
+                new CheckedPredicate((left, right) -> left instanceof Duration && right instanceof Number, false),
+                (left, right) -> {
+                    BigDecimal seconds = getBigDecimalOrNull(((Duration) left).getSeconds());
+                    BigDecimal rightNum = getBigDecimalOrNull(right);
+                    if (seconds == null || rightNum == null) return rightNum != null ? rightNum : BigDecimal.ZERO;
+                    return seconds.add(rightNum, MathContext.DECIMAL128);
+                });
+
+        // Row 2: NUMBER - number + duration → convert duration to seconds, then add
+        map.put(
+                new CheckedPredicate((left, right) -> left instanceof Number && right instanceof Duration, false),
+                (left, right) -> {
+                    BigDecimal leftNum = getBigDecimalOrNull(left);
+                    BigDecimal seconds = getBigDecimalOrNull(((Duration) right).getSeconds());
+                    if (leftNum == null || seconds == null) return leftNum != null ? leftNum : BigDecimal.ZERO;
+                    return leftNum.add(seconds, MathContext.DECIMAL128);
+                });
+
+        // Row 2: NUMBER - period + number → convert period to months, then add
+        map.put(
+                new CheckedPredicate((left, right) -> left instanceof ChronoPeriod && right instanceof Number, false),
+                (left, right) -> {
+                    BigDecimal months = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left));
+                    BigDecimal rightNum = getBigDecimalOrNull(right);
+                    if (months == null || rightNum == null) return rightNum != null ? rightNum : BigDecimal.ZERO;
+                    return months.add(rightNum, MathContext.DECIMAL128);
+                });
+
+        // Row 2: NUMBER - number + period → convert period to months, then add
+        map.put(
+                new CheckedPredicate((left, right) -> left instanceof Number && right instanceof ChronoPeriod, false),
+                (left, right) -> {
+                    BigDecimal leftNum = getBigDecimalOrNull(left);
+                    BigDecimal months = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) right));
+                    if (leftNum == null || months == null) return leftNum != null ? leftNum : BigDecimal.ZERO;
+                    return leftNum.add(months, MathContext.DECIMAL128);
+                });
+
+        // Number + null → number (null converts to 0 in B-FEEL numeric context)
         map.put(
                 new CheckedPredicate((left, right) -> left instanceof Number && right == null, false),
                 (left, right) -> left);
 
-        // null + Number → number
+        // null + Number → number (null converts to 0 in B-FEEL numeric context)
         map.put(
                 new CheckedPredicate((left, right) -> left == null && right instanceof Number, false),
                 (left, right) -> right);
@@ -344,19 +408,69 @@ public class BFEELDialectHandler extends DefaultDialectHandler implements Dialec
 
     /**
      * Builds the BFeel specific 'Power' operations.
-     * 
+     * <p>
+     * B-FEEL exponentiation rules:
+     * <ul>
+     *   <li>Implicit Coercion: Both operands are converted to numbers using B-FEEL number() function</li>
+     *   <li>Error Handling: Returns 0 instead of null for semantic errors or out-of-range scale</li>
+     *   <li>Precision: Result limited to 34 digits of precision</li>
+     *   <li>Range: Exponent must be in range [-999,999,999..999,999,999]</li>
+     *   <li>Precedence: -4 ** 2 is interpreted as (-4) ** 2 = 16 (handled by parser)</li>
+     * </ul>
+     *
      * @param ctx : Current Evaluation context
      * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'Power' operations
      */
     @Override
     public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getPowOperations(EvaluationContext ctx) {
         Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>();
-        //TODO Change pow behaviour for BFeel
+        
+        // B-FEEL: Implicit coercion to numbers, return 0 on error instead of null
         map.put(
-                new CheckedPredicate((left, right) -> left instanceof String || right instanceof String, false),
-                (left, right) -> Boolean.FALSE);
+                new CheckedPredicate((left, right) -> true, false),
+                (left, right) -> {
+                    // Implicit coercion: convert both operands to numbers using B-FEEL number() semantics
+                    BigDecimal leftNum = getBigDecimalOrNull(left);
+                    BigDecimal rightNum = getBigDecimalOrNull(right);
+                    
+                    // B-FEEL implicit coercion: Duration → seconds
+                    if (leftNum == null && left instanceof Duration) {
+                        leftNum = BigDecimal.valueOf(((Duration) left).getSeconds());
+                    }
+                    if (rightNum == null && right instanceof Duration) {
+                        rightNum = BigDecimal.valueOf(((Duration) right).getSeconds());
+                    }
+                    
+                    // B-FEEL implicit coercion: ChronoPeriod → total months
+                    if (leftNum == null && left instanceof ChronoPeriod) {
+                        leftNum = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left));
+                    }
+                    if (rightNum == null && right instanceof ChronoPeriod) {
+                        rightNum = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) right));
+                    }
+                    
+                    // B-FEEL returns 0 instead of null for invalid operations
+                    if (leftNum == null || rightNum == null) {
+                        return BigDecimal.ZERO;
+                    }
+                    
+                    // Check exponent range: [-999,999,999..999,999,999]
+                    BigDecimal minExponent = new BigDecimal("-999999999");
+                    BigDecimal maxExponent = new BigDecimal("999999999");
+                    if (rightNum.compareTo(minExponent) < 0 || rightNum.compareTo(maxExponent) > 0) {
+                        return BigDecimal.ZERO; // Out of range, return 0
+                    }
+                    
+                    try {
+                        // Apply standard FEEL semantics with 34 digits precision (DECIMAL128)
+                        BigDecimal result = BigDecimalMath.pow(leftNum, rightNum, MathContext.DECIMAL128);
+                        return result;
+                    } catch (ArithmeticException e) {
+                        // B-FEEL returns 0 for arithmetic errors instead of null
+                        return BigDecimal.ZERO;
+                    }
+                });
 
-        map.putAll(getCommonPowOperations(ctx));
         return map;
     }
 
@@ -415,13 +529,39 @@ public class BFEELDialectHandler extends DefaultDialectHandler implements Dialec
                 new CheckedPredicate((left, right) -> left == null && right instanceof ChronoPeriod, false),
                 (left, right) -> ComparablePeriod.ofMonths(0));
 
+        // Period × Duration (mixed types) → B-FEEL Section 11.10: YMD has higher precedence than DTD
+        // Convert Duration to seconds (as number), result is Period
+        map.put(
+                new CheckedPredicate((left, right) -> left instanceof ChronoPeriod && right instanceof Duration, false),
+                (left, right) -> {
+                    BigDecimal months = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left));
+                    BigDecimal seconds = getBigDecimalOrNull(((Duration) right).getSeconds());
+                    if (months == null || seconds == null)
+                        return ComparablePeriod.ofMonths(0);
+                    return ComparablePeriod.ofMonths(months.multiply(seconds, MathContext.DECIMAL128).intValue());
+                });
+
+        // Duration × Period (mixed types) → B-FEEL Section 11.10: YMD has higher precedence than DTD
+        // Convert Duration to seconds (as number), result is Period
+        map.put(
+                new CheckedPredicate((left, right) -> left instanceof Duration && right instanceof ChronoPeriod, false),
+                (left, right) -> {
+                    BigDecimal seconds = getBigDecimalOrNull(((Duration) left).getSeconds());
+                    BigDecimal months = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) right));
+                    if (seconds == null || months == null)
+                        return ComparablePeriod.ofMonths(0);
+                    return ComparablePeriod.ofMonths(seconds.multiply(months, MathContext.DECIMAL128).intValue());
+                });
+
+        // Duration × Duration (same type) → returns zero duration (B-FEEL default for disallowed operation)
         map.put(
                 new CheckedPredicate((left, right) -> left instanceof Duration && right instanceof Duration, false),
-                (left, right) -> null);
+                (left, right) -> Duration.ZERO);
 
+        // Period × Period (same type) → returns zero period (B-FEEL default for disallowed operation)
         map.put(
                 new CheckedPredicate((left, right) -> left instanceof ChronoPeriod && right instanceof ChronoPeriod, false),
-                (left, right) -> null);
+                (left, right) -> ComparablePeriod.ofMonths(0));
         map.putAll(getCommonMultOperations(ctx));
         return map;
     }
@@ -445,13 +585,25 @@ public class BFEELDialectHandler extends DefaultDialectHandler implements Dialec
         map.put(new CheckedPredicate((l, r) -> l instanceof String && r instanceof String, false),
                 (l, r) -> BigDecimal.ZERO);
 
-        // number ÷ duration → invalid → null
+        // number ÷ duration → B-FEEL implicit coercion: convert duration to seconds, then divide
         map.put(new CheckedPredicate((l, r) -> l instanceof Number && r instanceof Duration, false),
-                (l, r) -> null);
+                (l, r) -> {
+                    BigDecimal leftNum = getBigDecimalOrNull(l);
+                    BigDecimal rightSecs = getBigDecimalOrNull(((Duration) r).getSeconds());
+                    if (leftNum == null || rightSecs == null || rightSecs.compareTo(BigDecimal.ZERO) == 0)
+                        return BigDecimal.ZERO; // B-FEEL returns 0 instead of null
+                    return leftNum.divide(rightSecs, MathContext.DECIMAL128);
+                });
 
-        // number ÷ period → invalid → null
+        // number ÷ period → B-FEEL implicit coercion: convert period to total months, then divide
         map.put(new CheckedPredicate((l, r) -> l instanceof Number && r instanceof ChronoPeriod, false),
-                (l, r) -> null);
+                (l, r) -> {
+                    BigDecimal leftNum = getBigDecimalOrNull(l);
+                    BigDecimal rightMonths = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) r));
+                    if (leftNum == null || rightMonths == null || rightMonths.compareTo(BigDecimal.ZERO) == 0)
+                        return BigDecimal.ZERO; // B-FEEL returns 0 instead of null
+                    return leftNum.divide(rightMonths, MathContext.DECIMAL128);
+                });
 
         // duration ÷ null → Duration.ZERO
         map.put(new CheckedPredicate((l, r) -> l instanceof Duration && r == null, false),
@@ -473,6 +625,37 @@ public class BFEELDialectHandler extends DefaultDialectHandler implements Dialec
         map.put(new CheckedPredicate((l, r) -> l == null && r instanceof Number, false),
                 (l, r) -> BigDecimal.ZERO);
 
+        // number ÷ number (including division by zero) → B-FEEL returns BigDecimal.ZERO for division by zero
+        map.put(new CheckedPredicate((l, r) -> l instanceof Number && r instanceof Number, false),
+                (l, r) -> {
+                    BigDecimal leftBD = getBigDecimalOrNull(l);
+                    BigDecimal rightBD = getBigDecimalOrNull(r);
+                    if (leftBD == null || rightBD == null || rightBD.compareTo(BigDecimal.ZERO) == 0) {
+                        return BigDecimal.ZERO; // B-FEEL returns 0 for division by zero
+                    }
+                    return leftBD.divide(rightBD, MathContext.DECIMAL128);
+                });
+
+        // duration ÷ duration → number (B-FEEL: returns 0 instead of null when divisor is zero)
+        map.put(new CheckedPredicate((l, r) -> l instanceof Duration && r instanceof Duration, false),
+                (l, r) -> {
+                    BigDecimal leftSecs = getBigDecimalOrNull(((Duration) l).getSeconds());
+                    BigDecimal rightSecs = getBigDecimalOrNull(((Duration) r).getSeconds());
+                    if (leftSecs == null || rightSecs == null || rightSecs.compareTo(BigDecimal.ZERO) == 0)
+                        return BigDecimal.ZERO; // B-FEEL returns 0 instead of null
+                    return leftSecs.divide(rightSecs, MathContext.DECIMAL128);
+                });
+
+        // period ÷ period → number (B-FEEL: returns 0 instead of null when divisor is zero)
+        map.put(new CheckedPredicate((l, r) -> l instanceof ChronoPeriod && r instanceof ChronoPeriod, false),
+                (l, r) -> {
+                    BigDecimal leftMonths = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) l));
+                    BigDecimal rightMonths = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) r));
+                    if (leftMonths == null || rightMonths == null || rightMonths.compareTo(BigDecimal.ZERO) == 0)
+                        return BigDecimal.ZERO; // B-FEEL returns 0 instead of null
+                    return leftMonths.divide(rightMonths, MathContext.DECIMAL128);
+                });
+
         map.putAll(getCommonDivisionOperations(ctx));
         return map;
     }
diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandlerTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandlerTest.java
new file mode 100644
index 0000000000..532a009471
--- /dev/null
+++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandlerTest.java
@@ -0,0 +1,294 @@
+/*
+ * 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.kie.dmn.feel.lang.ast.dialectHandlers;
+
+import java.math.BigDecimal;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.Period;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.kie.dmn.feel.lang.EvaluationContext;
+import org.kie.dmn.feel.lang.types.impl.ComparablePeriod;
+import org.kie.dmn.feel.util.EvaluationContextTestUtil;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Unit tests for BFEELDialectHandler - BFEEL uses type coercion and returns default values
+ */
+class BFEELDialectHandlerTest {
+
+    private BFEELDialectHandler handler;
+    private EvaluationContext ctx;
+
+    @BeforeEach
+    void setUp() {
+        handler = new BFEELDialectHandler();
+        ctx = EvaluationContextTestUtil.newEmptyEvaluationContext();
+    }
+    
+    @Test
+    void testAdditionWithPrecedenceOrder() {
+        //Basic addition operation
+        assertThat(handler.executeAdd(10, 20, ctx)).isEqualTo(new BigDecimal("30"));
+        assertThat(handler.executeAdd(10, null, ctx)).isEqualTo(10);
+        // Case 1 : STRING - If either operand is a string, convert non-string to string
+        // Left: string + other
+        assertThat(handler.executeAdd("Hello", 123, ctx)).isEqualTo("Hello123");
+        assertThat(handler.executeAdd("Date:", LocalDate.of(2024, 1, 1), ctx)).isEqualTo("Date:2024-01-01");
+        assertThat(handler.executeAdd("Duration:", Duration.ofDays(5), ctx)).isEqualTo("Duration:PT120H");
+        assertThat(handler.executeAdd("Period:", Period.ofMonths(3), ctx)).isEqualTo("Period:P3M");
+        
+        // Right: other + string
+        assertThat(handler.executeAdd(123, "World", ctx)).isEqualTo("123World");
+        assertThat(handler.executeAdd(LocalDate.of(2024, 1, 1), " is the date", ctx)).isEqualTo("2024-01-01 is the date");
+        assertThat(handler.executeAdd(Duration.ofDays(5), " duration", ctx)).isEqualTo("PT120H duration");
+        assertThat(handler.executeAdd(Period.ofMonths(3), " period", ctx)).isEqualTo("P3M period");
+        
+        // Case 2 : NUMBER - If either operand is a number (and neither is string), convert non-number to number
+        // Per BFEEL Section 11.3: number(date) = 0, number(duration) = seconds, number(period) = months
+        
+        // Left: date + number → 0 + number = number
+        assertThat(handler.executeAdd(LocalDate.of(2024, 1, 1), 5, ctx)).isEqualTo(5);
+        assertThat(handler.executeAdd(LocalDate.of(2024, 1, 1), new BigDecimal("10.5"), ctx))
+                .isEqualTo(new BigDecimal("10.5"));
+        
+        // Right: number + date → number + 0 = number
+        assertThat(handler.executeAdd(5, LocalDate.of(2024, 1, 1), ctx)).isEqualTo(5);
+        assertThat(handler.executeAdd(new BigDecimal("10.5"), LocalDate.of(2024, 1, 1), ctx))
+                .isEqualTo(new BigDecimal("10.5"));
+        
+        // Duration + number → seconds + number (Row 2: NUMBER precedence)
+        assertThat(handler.executeAdd(Duration.ofSeconds(10), 5, ctx)).isEqualTo(new BigDecimal("15"));
+        assertThat(handler.executeAdd(5, Duration.ofSeconds(10), ctx)).isEqualTo(new BigDecimal("15"));
+        
+        // Period + number → months + number (Row 2: NUMBER precedence)
+        assertThat(handler.executeAdd(Period.ofMonths(12), 3, ctx)).isEqualTo(new BigDecimal("15"));
+        assertThat(handler.executeAdd(3, Period.ofMonths(12), ctx)).isEqualTo(new BigDecimal("15"));
+        
+        // Case 3: DATE - If either operand is a date, convert non-date to duration
+        // Left: date + duration
+        assertThat(handler.executeAdd(LocalDate.of(2024, 1, 1), Duration.ofDays(5), ctx))
+                .isEqualTo(LocalDate.of(2024, 1, 6));
+        assertThat(handler.executeAdd(LocalDate.of(2024, 1, 1), Period.ofMonths(2), ctx))
+                .isEqualTo(LocalDate.of(2024, 3, 1));
+        assertThat(handler.executeAdd("Hello", null, ctx)).isEqualTo("Hello");
+    }
+
+    @Test
+    void testSubtractionWithPrecedenceOrder() {
+        // Basic subtraction operation
+        assertThat(handler.executeSub(20, 10, ctx)).isEqualTo(new BigDecimal("10"));
+        
+        // Case 1: STRING - If either operand is a string, subtraction returns empty string ""
+        // Left: string - other
+        assertThat(handler.executeSub("Hello", 123, ctx)).isEqualTo("");
+        assertThat(handler.executeSub("Date", LocalDate.of(2024, 1, 1), ctx)).isEqualTo("");
+        assertThat(handler.executeSub("Duration", Duration.ofDays(5), ctx)).isEqualTo("");
+        
+        // Right: other - string
+        assertThat(handler.executeSub(123, "World", ctx)).isEqualTo("");
+        assertThat(handler.executeSub(LocalDate.of(2024, 1, 1), "date", ctx)).isEqualTo("");
+        assertThat(handler.executeSub(Duration.ofDays(5), "duration", ctx)).isEqualTo("");
+        
+        // Case 2: NUMBER - If either operand is a number (and neither is string), convert non-number to number
+        // Standard numeric subtraction applies after conversion
+        assertThat(handler.executeSub(10, 5, ctx)).isEqualTo(new BigDecimal("5"));
+        assertThat(handler.executeSub(new BigDecimal("20.5"), new BigDecimal("10.5"), ctx))
+                .isEqualTo(new BigDecimal("10.0"));
+        
+        // Case 3: DATE - If either operand is a date, convert non-date to duration
+        // Left: date - duration
+        assertThat(handler.executeSub(LocalDate.of(2024, 1, 10), Duration.ofDays(5), ctx))
+                .isEqualTo(LocalDate.of(2024, 1, 5));
+        assertThat(handler.executeSub(LocalDate.of(2024, 3, 1), Period.ofMonths(1), ctx))
+                .isEqualTo(LocalDate.of(2024, 2, 1));
+        
+        //Case 4 : DAYS AND TIME DURATION - Duration subtraction
+        assertThat(handler.executeSub(Duration.ofDays(10), Duration.ofDays(3), ctx))
+                .isEqualTo(Duration.ofDays(7));
+    }
+
+    @Test
+    void testMultiplication() {
+        // basic multiplication operation
+        assertThat(handler.executeMult(5, 10, ctx)).isEqualTo(new BigDecimal("50"));
+
+        // multiplication - invalid types
+        assertThat(handler.executeMult("test", 5, ctx)).isEqualTo(BigDecimal.ZERO);
+        assertThat(handler.executeMult(Duration.ofDays(5), null, ctx)).isEqualTo(Duration.ZERO);
+        assertThat(handler.executeMult(Period.ofMonths(3), null, ctx)).isEqualTo(ComparablePeriod.ofMonths(0));
+
+        // Duration × Duration (same type) → zero duration (B-FEEL default)
+        assertThat(handler.executeMult(Duration.ofDays(5), Duration.ofDays(3), ctx)).isEqualTo(Duration.ZERO);
+        assertThat(handler.executeMult(Period.ofMonths(6), Period.ofMonths(2), ctx)).isEqualTo(ComparablePeriod.ofMonths(0));
+        
+        // YEARS AND MONTHS DURATION (Period) × Number
+        // Left: period × number
+        assertThat(handler.executeMult(Period.ofMonths(12), 2, ctx))
+                .isEqualTo(ComparablePeriod.ofMonths(24));
+        // Right: number × period
+        assertThat(handler.executeMult(2, Period.ofMonths(12), ctx))
+                .isEqualTo(ComparablePeriod.ofMonths(24));
+        
+        // DAYS AND TIME DURATION (Duration) × Number
+        // Left: duration × number
+        assertThat(handler.executeMult(Duration.ofDays(5), 2, ctx))
+                .isEqualTo(Duration.ofDays(10));
+        // Right: number × duration
+        assertThat(handler.executeMult(2, Duration.ofDays(5), ctx))
+                .isEqualTo(Duration.ofDays(10));
+        
+        // Period × Duration: B-FEEL Section 11.10 - YMD has higher precedence than DTD
+        // Duration converted to seconds (as number), result is Period
+        // P1Y (12 months) × PT1S (1 second) = P12M
+        assertThat(handler.executeMult(Period.ofYears(1), Duration.ofSeconds(1), ctx))
+                .isEqualTo(ComparablePeriod.ofMonths(12));
+        
+        // Duration × Period: B-FEEL Section 11.10 - YMD has higher precedence than DTD
+        // Duration converted to seconds (as number), result is Period
+        // PT1S (1 second) × P1Y (12 months) = P12M (or P1Y)
+        assertThat(handler.executeMult(Duration.ofSeconds(1), Period.ofYears(1), ctx))
+                .isEqualTo(ComparablePeriod.ofMonths(12));
+    }
+    
+    @Test
+    void testDivision() {
+        // basic division operation
+        assertThat(handler.executeDivision(20, 4, ctx)).isEqualTo(new BigDecimal("5"));
+        assertThat(handler.executeDivision(10, 0, ctx)).isEqualTo(BigDecimal.ZERO); // Division by zero returns 0
+
+        // null or non-numeric left operand
+        assertThat(handler.executeDivision("test", 5, ctx)).isEqualTo(BigDecimal.ZERO);
+        assertThat(handler.executeDivision(null, 5, ctx)).isEqualTo(BigDecimal.ZERO);
+        assertThat(handler.executeDivision(Duration.ofDays(5), null, ctx)).isEqualTo(Duration.ZERO);
+        assertThat(handler.executeDivision(Period.ofMonths(3), null, ctx)).isEqualTo(ComparablePeriod.ofMonths(0));
+
+        // Number ÷ Duration: Duration converted to seconds
+        assertThat(handler.executeDivision(10, Duration.ofDays(5), ctx)).isInstanceOf(BigDecimal.class);
+        assertThat(((BigDecimal) handler.executeDivision(10, Duration.ofDays(5), ctx)).compareTo(BigDecimal.ZERO))
+                .isGreaterThan(0);   
+
+        // Number ÷ Period: Period converted to months
+        assertThat(handler.executeDivision(26, Period.ofYears(2), ctx)).isInstanceOf(BigDecimal.class);
+        BigDecimal result = (BigDecimal) handler.executeDivision(26, Period.ofYears(2), ctx);
+        assertThat(result.compareTo(new BigDecimal("1.08"))).isGreaterThan(0);
+        assertThat(result.compareTo(new BigDecimal("1.09"))).isLessThan(0);
+
+        // Duration ÷ Duration → number (returns ratio)
+        assertThat(handler.executeDivision(Duration.ofSeconds(10), Duration.ofSeconds(5), ctx))
+                .isEqualTo(new BigDecimal("2"));
+        assertThat(handler.executeDivision(Duration.ofSeconds(10), Duration.ZERO, ctx))
+                .isEqualTo(BigDecimal.ZERO);
+        
+        // Period ÷ Period → number (returns ratio)
+        assertThat(handler.executeDivision(Period.ofYears(2), Period.ofYears(1), ctx))
+                .isEqualTo(new BigDecimal("2"));
+        assertThat(handler.executeDivision(Period.ofMonths(12), Period.ofMonths(0), ctx))
+                .isEqualTo(BigDecimal.ZERO);
+
+        // division by zero
+        assertThat(handler.executeDivision(10, Duration.ZERO, ctx)).isEqualTo(BigDecimal.ZERO);
+        assertThat(handler.executeDivision(10, Period.ofMonths(0), ctx)).isEqualTo(BigDecimal.ZERO);
+    }
+    
+    @Test
+    void testExponentiation() {
+        // basic cases
+        assertThat(handler.executePow(2, 3, ctx)).isEqualTo(new BigDecimal("8"));
+        assertThat(handler.executePow(4, 2, ctx)).isEqualTo(new BigDecimal("16"));
+        assertThat(handler.executePow(-4, 2, ctx)).isEqualTo(new BigDecimal("16"));
+        assertThat(handler.executePow(-2, 3, ctx)).isEqualTo(new BigDecimal("-8"));
+
+        // testExponentiation_ImplicitCoercion
+        assertThat(handler.executePow("2", 3, ctx)).isEqualTo(new BigDecimal("8"));
+        assertThat(handler.executePow(2, "3", ctx)).isEqualTo(new BigDecimal("8"));
+        assertThat(handler.executePow(Duration.ofSeconds(2), 3, ctx)).isEqualTo(new BigDecimal("8"));
+
+        // testExponentiation_ErrorHandling
+        assertThat(handler.executePow(null, 2, ctx)).isEqualTo(BigDecimal.ZERO);
+        assertThat(handler.executePow(2, null, ctx)).isEqualTo(BigDecimal.ZERO);
+        assertThat(handler.executePow("invalid", 2, ctx)).isEqualTo(BigDecimal.ZERO);
+        assertThat(handler.executePow(2, "invalid", ctx)).isEqualTo(BigDecimal.ZERO);
+
+        // testExponentiation_RangeValidation
+        assertThat(handler.executePow(2, 999999999, ctx)).isInstanceOf(BigDecimal.class);
+        assertThat(handler.executePow(2, -999999999, ctx)).isInstanceOf(BigDecimal.class);
+        assertThat(handler.executePow(2, new BigDecimal("1000000000"), ctx)).isEqualTo(BigDecimal.ZERO);
+        assertThat(handler.executePow(2, new BigDecimal("-1000000000"), ctx)).isEqualTo(BigDecimal.ZERO);
+
+        // testExponentiation_FractionalExponents
+        BigDecimal sqrtResult = (BigDecimal) handler.executePow(4, 0.5, ctx);
+        assertThat(sqrtResult.compareTo(new BigDecimal("2"))).isEqualTo(0);
+        assertThat(handler.executePow(8, new BigDecimal("0.333333333333333"), ctx))
+                .isInstanceOf(BigDecimal.class);
+
+        // testExponentiation_BoundaryCases
+        assertThat(handler.executePow(0, 5, ctx)).isEqualTo(BigDecimal.ZERO);
+        assertThat(handler.executePow(5, 0, ctx)).isEqualTo(BigDecimal.ONE);
+        assertThat(handler.executePow(1, 1000, ctx)).isEqualTo(BigDecimal.ONE);
+    }
+
+    @Test
+    void testLogicalOperations() {
+        // LogicalOperations_And
+        assertThat(handler.executeAnd(Boolean.TRUE, Boolean.TRUE, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeAnd(Boolean.TRUE, Boolean.FALSE, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeAnd("test", Boolean.TRUE, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeAnd("notBoolean", "alsoNotBoolean", ctx)).isEqualTo(Boolean.FALSE);
+   
+        // LogicalOperations_Or
+        assertThat(handler.executeOr(Boolean.TRUE, Boolean.FALSE, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeOr(Boolean.FALSE, Boolean.FALSE, ctx)).isEqualTo(Boolean.FALSE);
+    }
+
+    @Test
+    void testComparisonOperations() {
+        // Comparison_Equality
+        assertThat(handler.executeEqual(null, null, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeEqual(10, 10, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeEqual(10, 20, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeEqual("10", 10, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeEqual(null, 10, ctx)).isEqualTo(Boolean.FALSE);
+        
+        assertThat(handler.executeNotEqual(null, null, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeNotEqual(10, 20, ctx)).isEqualTo(Boolean.TRUE);
+    
+        // Comparison_Relational
+        assertThat(handler.executeGte(Boolean.FALSE, Boolean.FALSE, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeGte("test", Boolean.TRUE, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeGte(20, 10, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeGte("test", 10, ctx)).isEqualTo(Boolean.FALSE);
+        
+        assertThat(handler.executeGt(20, 10, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeGt(new Object(), new Object(), ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeLte(5, 10, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeLt(5, 10, ctx)).isEqualTo(Boolean.FALSE);
+    
+        // Comparison_CompareMethod
+        assertThat(handler.compare(null, null, (l, r) -> l.compareTo(r) > 0)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.compare(20, 10, (l, r) -> l.compareTo(r) > 0)).isTrue();
+        assertThat(handler.compare("test", "test", (l, r) -> l.compareTo(r) == 0)).isTrue();
+        assertThat(handler.compare("test", 10, (l, r) -> l.compareTo(r) > 0)).isEqualTo(Boolean.FALSE);
+    }
+
+}
+
diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/DefaultDialectHandlerTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/DefaultDialectHandlerTest.java
new file mode 100644
index 0000000000..032b4ab02a
--- /dev/null
+++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/DefaultDialectHandlerTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.kie.dmn.feel.lang.ast.dialectHandlers;
+
+import java.time.LocalTime;
+import java.time.Period;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiPredicate;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Unit tests for DefaultDialectHandler static utility methods and inner classes.
+ * 
+ * Note: This is an abstract class. Its instance methods are tested through
+ * concrete implementations (FEELDialectHandler and BFEELDialectHandler):
+ * 
+ * This test class focuses on:
+ * - Static utility methods (isEqual, compare with fallback suppliers)
+ * - Inner classes (CheckedPredicate)
+ */
+class DefaultDialectHandlerTest {
+
+    @Test
+    void testIsEqual_StaticMethod() {
+        // Basic equality
+        assertThat(DefaultDialectHandler.isEqual(null, null, () -> Boolean.FALSE, () -> null))
+                .isEqualTo(Boolean.FALSE);
+        assertThat(DefaultDialectHandler.isEqual(10, 10, () -> null, () -> null)).isTrue();
+        assertThat(DefaultDialectHandler.isEqual(10, 20, () -> null, () -> null)).isFalse();
+        assertThat(DefaultDialectHandler.isEqual("test", "test", () -> null, () -> null)).isTrue();
+        assertThat(DefaultDialectHandler.isEqual("test1", "test2", () -> null, () -> null)).isFalse();
+        
+        // Singleton collection and element
+        List<Integer> list = Arrays.asList(10);
+        assertThat(DefaultDialectHandler.isEqual(list, 10, () -> null, () -> null)).isTrue();
+        assertThat(DefaultDialectHandler.isEqual(10, list, () -> null, () -> null)).isTrue();
+        
+        // Temporal types
+        assertThat(DefaultDialectHandler.isEqual(Period.ofMonths(6), Period.ofMonths(6), () -> null, () -> null))
+                .isTrue();
+        assertThat(DefaultDialectHandler.isEqual(LocalTime.of(14, 30), LocalTime.of(14, 30), () -> null, () -> null))
+                .isTrue();
+        
+        // Null fallback suppliers throw exception
+        assertThatThrownBy(() -> DefaultDialectHandler.isEqual(10, 20, null, () -> null))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Fallback suppliers must not be null");
+        
+        assertThatThrownBy(() -> DefaultDialectHandler.isEqual(10, 20, () -> null, null))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Fallback suppliers must not be null");
+    }
+
+    @Test
+    void testCheckedPredicate() {
+        BiPredicate<Object, Object> predicate = (l, r) -> l instanceof Number && r instanceof Number;
+        DefaultDialectHandler.CheckedPredicate cp1 = new DefaultDialectHandler.CheckedPredicate(predicate, false);
+        DefaultDialectHandler.CheckedPredicate cp2 = new DefaultDialectHandler.CheckedPredicate(predicate, true);
+        
+        // Equality based on predicate only (not on checked flag)
+        assertThat(cp1).isEqualTo(cp2);
+        assertThat(cp1.hashCode()).isEqualTo(cp2.hashCode());
+        
+        // Self equality
+        assertThat(cp1).isEqualTo(cp1);
+        
+        // Not equal to different types
+        assertThat(cp1).isNotEqualTo("string");
+        assertThat(cp1).isNotEqualTo(null);
+    }
+}
+
diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/DialectHandlerFactoryTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/DialectHandlerFactoryTest.java
new file mode 100644
index 0000000000..904293f74c
--- /dev/null
+++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/DialectHandlerFactoryTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.kie.dmn.feel.lang.ast.dialectHandlers;
+
+import org.junit.jupiter.api.Test;
+import org.kie.dmn.feel.lang.EvaluationContext;
+import org.kie.dmn.feel.lang.FEELDialect;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for DialectHandlerFactory.getHandler() method
+ */
+class DialectHandlerFactoryTest {
+
+    @Test
+    void testGetHandler_NullContextReturnsFEELHandler() {
+        DialectHandler handler = DialectHandlerFactory.getHandler(null);
+        
+        assertThat(handler).isNotNull();
+        assertThat(handler).isInstanceOf(FEELDialectHandler.class);
+    }
+
+    @Test
+    void testGetHandler_NullDialectReturnsFEELHandler() {
+        EvaluationContext ctx = mock(EvaluationContext.class);
+        when(ctx.getFEELDialect()).thenReturn(null);
+        
+        DialectHandler handler = DialectHandlerFactory.getHandler(ctx);
+        
+        assertThat(handler).isNotNull();
+        assertThat(handler).isInstanceOf(FEELDialectHandler.class);
+    }
+
+    @Test
+    void testGetHandler_FEELDialectReturnsFEELHandler() {
+        EvaluationContext ctx = mock(EvaluationContext.class);
+        when(ctx.getFEELDialect()).thenReturn(FEELDialect.FEEL);
+        
+        DialectHandler handler = DialectHandlerFactory.getHandler(ctx);
+        
+        assertThat(handler).isNotNull();
+        assertThat(handler).isInstanceOf(FEELDialectHandler.class);
+    }
+
+    @Test
+    void testGetHandler_BFEELDialectReturnsBFEELHandler() {
+        EvaluationContext ctx = mock(EvaluationContext.class);
+        when(ctx.getFEELDialect()).thenReturn(FEELDialect.BFEEL);
+        
+        DialectHandler handler = DialectHandlerFactory.getHandler(ctx);
+        
+        assertThat(handler).isNotNull();
+        assertThat(handler).isInstanceOf(BFEELDialectHandler.class);
+    }
+
+    @Test
+    void testGetHandler_DifferentDialectsReturnDifferentHandlers() {
+        EvaluationContext ctxFEEL = mock(EvaluationContext.class);
+        when(ctxFEEL.getFEELDialect()).thenReturn(FEELDialect.FEEL);
+        
+        EvaluationContext ctxBFEEL = mock(EvaluationContext.class);
+        when(ctxBFEEL.getFEELDialect()).thenReturn(FEELDialect.BFEEL);
+        
+        DialectHandler handlerFEEL = DialectHandlerFactory.getHandler(ctxFEEL);
+        DialectHandler handlerBFEEL = DialectHandlerFactory.getHandler(ctxBFEEL);
+        
+        assertThat(handlerFEEL).isInstanceOf(FEELDialectHandler.class);
+        assertThat(handlerBFEEL).isInstanceOf(BFEELDialectHandler.class);
+        assertThat(handlerFEEL).isNotSameAs(handlerBFEEL);
+    }
+
+    @Test
+    void testGetHandler_FEELCachesSingletonInstances() {
+        EvaluationContext ctx1 = mock(EvaluationContext.class);
+        when(ctx1.getFEELDialect()).thenReturn(FEELDialect.FEEL);
+        
+        EvaluationContext ctx2 = mock(EvaluationContext.class);
+        when(ctx2.getFEELDialect()).thenReturn(FEELDialect.FEEL);
+        
+        DialectHandler handler1 = DialectHandlerFactory.getHandler(ctx1);
+        DialectHandler handler2 = DialectHandlerFactory.getHandler(ctx2);
+        
+        // Should return the same cached instance
+        assertThat(handler1).isSameAs(handler2);
+    }
+
+    @Test
+    void testGetHandler_BFEELCachesSingletonInstances() {
+        EvaluationContext ctx1 = mock(EvaluationContext.class);
+        when(ctx1.getFEELDialect()).thenReturn(FEELDialect.BFEEL);
+        
+        EvaluationContext ctx2 = mock(EvaluationContext.class);
+        when(ctx2.getFEELDialect()).thenReturn(FEELDialect.BFEEL);
+        
+        DialectHandler handler1 = DialectHandlerFactory.getHandler(ctx1);
+        DialectHandler handler2 = DialectHandlerFactory.getHandler(ctx2);
+        
+        // Should return the same cached instance
+        assertThat(handler1).isSameAs(handler2);
+    }
+}
diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/FEELDialectHandlerTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/FEELDialectHandlerTest.java
new file mode 100644
index 0000000000..50a5de98a1
--- /dev/null
+++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/dialectHandlers/FEELDialectHandlerTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.kie.dmn.feel.lang.ast.dialectHandlers;
+
+import java.math.BigDecimal;
+import java.time.Duration;
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.kie.dmn.feel.lang.EvaluationContext;
+import org.kie.dmn.feel.util.EvaluationContextTestUtil;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Unit tests for FEELDialectHandler - FEEL returns null for invalid operations
+ */
+class FEELDialectHandlerTest {
+
+    private FEELDialectHandler handler;
+    private EvaluationContext ctx;
+
+    @BeforeEach
+    void setUp() {
+        handler = new FEELDialectHandler();
+        ctx = EvaluationContextTestUtil.newEmptyEvaluationContext();
+    }
+
+    @Test
+    void testAddOperations() {
+        // Valid operations
+        assertThat(handler.executeAdd("Hello", "World", ctx)).isEqualTo("HelloWorld");
+        assertThat(handler.executeAdd(10, 20, ctx)).isEqualTo(new BigDecimal("30"));
+        assertThat(handler.executeAdd(LocalDate.of(2024, 1, 1), Duration.ofDays(5), ctx))
+                .isEqualTo(LocalDate.of(2024, 1, 6));
+        
+        // Invalid operations return null
+        assertThat(handler.executeAdd("Hello", 123, ctx)).isNull();
+        assertThat(handler.executeAdd(LocalDate.of(2024, 1, 1), 5, ctx)).isNull();
+        assertThat(handler.executeAdd(10, null, ctx)).isNull();
+    }
+
+    @Test
+    void testLogicalOperations() {
+        // AND operations
+        assertThat(handler.executeAnd(Boolean.TRUE, Boolean.TRUE, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeAnd(Boolean.TRUE, Boolean.FALSE, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeAnd(Boolean.TRUE, null, ctx)).isNull();
+        
+        // OR operations
+        assertThat(handler.executeOr(Boolean.TRUE, Boolean.FALSE, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeOr(Boolean.FALSE, Boolean.FALSE, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeOr(Boolean.FALSE, null, ctx)).isNull();
+    }
+
+    @Test
+    void testEqualityOperations() {
+        // Equal
+        assertThat(handler.executeEqual(null, null, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeEqual(10, 10, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeEqual(10, 20, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeEqual("10", 10, ctx)).isNull(); // Different types
+        
+        // Not Equal
+        assertThat(handler.executeNotEqual(null, null, ctx)).isEqualTo(Boolean.FALSE);
+        assertThat(handler.executeNotEqual(10, 20, ctx)).isEqualTo(Boolean.TRUE);
+    }
+
+    @Test
+    void testComparisonOperations() {
+        // Greater than or equal
+        assertThat(handler.executeGte(Boolean.TRUE, null, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeGte(Boolean.FALSE, null, ctx)).isNull();
+        assertThat(handler.executeGte(20, 10, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeGte(10, 10, ctx)).isEqualTo(Boolean.TRUE);
+        
+        // Greater than
+        assertThat(handler.executeGt(20, 10, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeGt(10, 10, ctx)).isEqualTo(Boolean.FALSE);
+        
+        // Less than or equal
+        assertThat(handler.executeLte(5, 10, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeLte(10, 10, ctx)).isEqualTo(Boolean.TRUE);
+        
+        // Less than
+        assertThat(handler.executeLt(5, 10, ctx)).isEqualTo(Boolean.TRUE);
+        assertThat(handler.executeLt(10, 10, ctx)).isEqualTo(Boolean.FALSE);
+    }
+
+    @Test
+    void testArithmeticOperations() {
+        // Power
+        assertThat(handler.executePow("2", 3, ctx)).isNull(); // Invalid
+        assertThat(handler.executePow(2, 3, ctx)).isEqualTo(new BigDecimal("8"));
+        
+        // Subtraction
+        assertThat(handler.executeSub("10", 5, ctx)).isNull(); // Invalid
+        assertThat(handler.executeSub(20, 10, ctx)).isEqualTo(new BigDecimal("10"));
+        assertThat(handler.executeSub(LocalDate.of(2024, 1, 10), Duration.ofDays(5), ctx))
+                .isEqualTo(LocalDate.of(2024, 1, 5));
+        
+        // Multiplication
+        assertThat(handler.executeMult("test", 5, ctx)).isNull(); // Invalid
+        assertThat(handler.executeMult(5, 10, ctx)).isEqualTo(new BigDecimal("50"));
+        assertThat(handler.executeMult(Duration.ofDays(5), null, ctx)).isNull();
+        assertThat(handler.executeMult(Duration.ofDays(5), Duration.ofDays(3), ctx)).isNull();
+        assertThat(handler.executeMult(LocalDate.of(2024, 1, 1), 5, ctx)).isNull();
+        
+        // Division
+        assertThat(handler.executeDivision("test", 5, ctx)).isNull(); // Invalid
+        assertThat(handler.executeDivision(20, 4, ctx)).isEqualTo(new BigDecimal("5"));
+        assertThat(handler.executeDivision(10, Duration.ofDays(5), ctx)).isNull();
+        assertThat(handler.executeDivision(Duration.ofDays(5), null, ctx)).isNull();
+        assertThat(handler.executeDivision(null, 5, ctx)).isNull();
+    }
+
+    @Test
+    void testCompareMethod() {
+        assertThat(handler.compare(null, null, (l, r) -> l.compareTo(r) > 0)).isNull();
+        assertThat(handler.compare(20, 10, (l, r) -> l.compareTo(r) > 0)).isTrue();
+        assertThat(handler.compare("test", "test", (l, r) -> l.compareTo(r) == 0)).isTrue();
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@kie.apache.org
For additional commands, e-mail: commits-help@kie.apache.org