Repository: authorjapps/zerocode (java)

Issue:
Exceptions thrown from classes executed by the java api are not propagated
When a java api operation throws an exception, the exception message and stacktrace are not shown as part of the scenario stacktrace, making it difficult to debug.

**To reproduce:**
- Start from a new branch
- Edit `org.jsmart.zerocode.core.AddService` and replace `return 30` in the 'anInteger' method by `throw new RuntimeException("INTENTIONAL EXCEPTION")`
- Optionally, edit the resource file `unit_test_files/java_apis/02_test_json_java_service_method_no_param.json` and remove `"loop": 5,`
- Run the test in package `org.jsmart.zerocode.core.javamethod`

**Expected:** Some information about the RuntimeException `INTENTIONAL EXCEPTION`, and its stacktrace should be shown
**Actual:** It is ommited. This is the stacktrace:

```
2024-10-20 11:55:44,184 [main] ERROR org.jsmart.zerocode.core.engine.executor.javaapi.JavaMethodExecutorImpl - Java exec(): Invocation failed for method anInteger in class org.jsmart.zerocode.core.AddService. Exception - java.lang.reflect.InvocationTargetException
2024-10-20 11:55:44,184 [main] ERROR org.jsmart.zerocode.core.engine.executor.javaapi.JavaMethodExecutorImpl - Exception - java.lang.RuntimeException: Java exec(): Invocation failed for method anInteger in class org.jsmart.zerocode.core.AddService
2024-10-20 11:55:44,185 [main] ERROR org.jsmart.zerocode.core.engine.executor.ApiServiceExecutorImpl - Java method execution exception - java.lang.RuntimeException: java.lang.RuntimeException: Java exec(): Invocation failed for method anInteger in class org.jsmart.zerocode.core.AddService
java.lang.RuntimeException: java.lang.RuntimeException: java.lang.RuntimeException: Java exec(): Invocation failed for method anInteger in class org.jsmart.zerocode.core.AddService
	at org.jsmart.zerocode.core.engine.executor.ApiServiceExecutorImpl.executeJavaOperation(ApiServiceExecutorImpl.java:53)
	at org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunnerImpl.executeApi(ZeroCodeMultiStepsScenarioRunnerImpl.java:471)
	at org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunnerImpl.executeRetry(ZeroCodeMultiStepsScenarioRunnerImpl.java:256)
	at org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunnerImpl.executeRetryWithSteps(ZeroCodeMultiStepsScenarioRunnerImpl.java:195)
	at org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunnerImpl.executeSteps(ZeroCodeMultiStepsScenarioRunnerImpl.java:177)
	at org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunnerImpl.runScenario(ZeroCodeMultiStepsScenarioRunnerImpl.java:140)
(...)
Caused by: java.lang.RuntimeException: java.lang.RuntimeException: Java exec(): Invocation failed for method anInteger in class org.jsmart.zerocode.core.AddService
	at org.jsmart.zerocode.core.engine.executor.javaapi.JavaMethodExecutorImpl.execute(JavaMethodExecutorImpl.java:63)
	at org.jsmart.zerocode.core.engine.executor.ApiServiceExecutorImpl.executeJavaOperation(ApiServiceExecutorImpl.java:50)
	... 22 more
Caused by: java.lang.RuntimeException: Java exec(): Invocation failed for method anInteger in class org.jsmart.zerocode.core.AddService
	at org.jsmart.zerocode.core.engine.executor.javaapi.JavaMethodExecutorImpl.executeWithParams(JavaMethodExecutorImpl.java:114)
	at org.jsmart.zerocode.core.engine.executor.javaapi.JavaMethodExecutorImpl.execute(JavaMethodExecutorImpl.java:47)
	... 23 more
2024-10-20 11:55:44,186 [main] ERROR org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunnerImpl - ###Exception while executing a step in the zerocode dsl.
2024-10-20 11:55:44,188 [main] ERROR org.jsmart.zerocode.core.runner.StepNotificationHandler - Exception occurred while executing Scenario:[Given_When_Then-Flow name For Java Service], --> Step:[javaMethodExecution], Details: java.lang.RuntimeException: ZeroCode Step execution failed. Details:java.lang.RuntimeException: java.lang.RuntimeException: java.lang.RuntimeException: Java exec(): Invocation failed for method anInteger in class org.jsmart.zerocode.core.AddService
2024-10-20 11:55:44,193[main] org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunnerImpl -m
```


Pull request:
ISSUE-688 # Propagate exceptions from java api operations
# Propagate exceptions from java api operations

## Fixes Issue
- [x] Which issue or ticket was(will be) fixed by this PR? Fix #688 

PR Branch
**https://github.com/javiertuya/zerocode/tree/688-exceptions-java-api**

## Motivation and Context

When executing a java method that is invoked by the java api as an operation, any exception in the java method should be shown in the stacktrace to facilitate debugging. The executor exception handler should include this exception

## Checklist:

* [x] New Unit tests were added
  * [ ] Covered in existing Unit tests

* [ ] Integration tests were added
  * [ ] Covered in existing Integration tests

* [x] Test names are meaningful

* [x] Feature manually tested and outcome is successful

* [x] PR doesn't break any of the earlier features for end users
  * [ ] WARNING! This might break one or more earlier earlier features, hence left a comment tagging all reviewrs

* [x] Branch build passed in CI

* [x] No 'package.*' in the imports

* [ ] Relevant DOcumentation page added or updated with clear instructions and examples for the end user
  * [x] Not applicable. This was only a code refactor change, no functional or behaviourial changes were introduced

* [ ] Http test added to `http-testing` module(if applicable) ?
  * [x] Not applicable. The changes did not affect HTTP automation flow

* [ ] Kafka test added to `kafka-testing` module(if applicable) ?
  * [x] Not applicable. The changes did not affect Kafka automation flow


Patch:
diff --git a/core/src/main/java/org/jsmart/zerocode/core/AddService.java b/core/src/main/java/org/jsmart/zerocode/core/AddService.java
index 18c1e9b0..e1193789 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/AddService.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/AddService.java
@@ -21,6 +21,12 @@ public class AddService {
         return myNumber.getNumber() * myNumber.getNumber();
     }
 
+    public Double squareRoot(Double number) {
+    	if (number < 0.0)
+    		throw new RuntimeException("Can not square root a negative number");
+        return Math.sqrt(number);
+    }
+    
     public Integer anInteger() {
         logger.debug("Returning a number ");
 
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/executor/javaapi/JavaMethodExecutorImpl.java b/core/src/main/java/org/jsmart/zerocode/core/engine/executor/javaapi/JavaMethodExecutorImpl.java
index 5eddfae9..0d5d4f58 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/engine/executor/javaapi/JavaMethodExecutorImpl.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/executor/javaapi/JavaMethodExecutorImpl.java
@@ -111,7 +111,7 @@ public class JavaMethodExecutorImpl implements JavaMethodExecutor {
         } catch (Exception e) {
             String errMsg = format("Java exec(): Invocation failed for method %s in class %s", methodName, qualifiedClassName);
             LOGGER.error(errMsg + ". Exception - " + e);
-            throw new RuntimeException(errMsg);
+            throw new RuntimeException(errMsg, e);
         }
     }
 


Test patch:
diff --git a/core/src/test/java/org/jsmart/zerocode/core/engine/executor/javaapi/JavaMethodExecutorImplTest.java b/core/src/test/java/org/jsmart/zerocode/core/engine/executor/javaapi/JavaMethodExecutorImplTest.java
index 148de7f5..2b971687 100644
--- a/core/src/test/java/org/jsmart/zerocode/core/engine/executor/javaapi/JavaMethodExecutorImplTest.java
+++ b/core/src/test/java/org/jsmart/zerocode/core/engine/executor/javaapi/JavaMethodExecutorImplTest.java
@@ -3,6 +3,7 @@ package org.jsmart.zerocode.core.engine.executor.javaapi;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.List;
 import org.jsmart.zerocode.core.di.main.ApplicationMainModule;
@@ -16,6 +17,9 @@ import org.junit.rules.ExpectedException;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThrows;
 
 public class JavaMethodExecutorImplTest {
 
@@ -124,6 +128,29 @@ public class JavaMethodExecutorImplTest {
         assertThat(result, is(900));
     }
 
+    @Test
+    public void willPropagateExceptions() throws Exception {
+        String scenariosJsonAsString = SmartUtils.readJsonAsString("unit_test_files/java_apis/03_test_json_java_service_method_Exception.json");
+        final ScenarioSpec scenarioSpec = smartUtils.getMapper().readValue(scenariosJsonAsString, ScenarioSpec.class);
+
+        String serviceName = scenarioSpec.getSteps().get(0).getUrl();
+        String methodName = scenarioSpec.getSteps().get(0).getOperation();
+        String requestJson = scenarioSpec.getSteps().get(0).getRequest().toString();
+        List<Class<?>> argumentTypes = methodExecutor.getParameterTypes(serviceName, methodName);
+
+        Object request = mapper.readValue(requestJson, argumentTypes.get(0));
+        
+        RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+        	methodExecutor.executeWithParams(serviceName, methodName, request);
+		});
+        exception.printStackTrace();
+        assertThat(exception.getMessage(), containsString("Invocation failed for method squareRoot"));
+        // The target exception is included in this exception (inside of the InvocationTargetException)
+        assertThat(exception.getCause(), is(notNullValue()));
+        assertThat(((InvocationTargetException)exception.getCause()).getTargetException().getMessage(), 
+        		is("Can not square root a negative number"));
+    }
+
     @Test
     public void willExecuteJsonWithParams_CustomObject_viaJson() throws Exception {
         String requestJson = "{\n" +
diff --git a/core/src/test/resources/unit_test_files/java_apis/03_test_json_java_service_method_Exception.json b/core/src/test/resources/unit_test_files/java_apis/03_test_json_java_service_method_Exception.json
new file mode 100644
index 00000000..711f04b0
--- /dev/null
+++ b/core/src/test/resources/unit_test_files/java_apis/03_test_json_java_service_method_Exception.json
@@ -0,0 +1,13 @@
+{
+  "scenarioName": "Given_When_Then-Flow name For Java Service",
+  "steps": [
+    {
+      "name": "javaMethodException",
+      "url": "org.jsmart.zerocode.core.AddService",
+      "operation": "squareRoot",
+      "request": "-255.0", //<-- This negative number should throw an exception
+      "assertions": {
+      }
+    }
+  ]
+}