From 51d5b04e365b7e8664c4f902f73abea2216a5da8 Mon Sep 17 00:00:00 2001 From: Aymalla Date: Mon, 12 Jun 2023 18:42:21 +0100 Subject: [PATCH] GetInstanceState implementation (#1) * addiny getInstanceMetadata, waitForInstanceStart and waitForInstanceCompletion implementation --------- Co-authored-by: aymanmahmoud_microsoft Signed-off-by: Aymand Mahmoud Signed-off-by: Mahmut Canga --- .../workflows/DemoWorkflowClient.java | 39 ++- sdk-workflows/pom.xml | 6 + .../workflows/client/DaprWorkflowClient.java | 79 +++++- .../client/WorkflowFailureDetails.java | 64 +++++ .../client/WorkflowRuntimeStatus.java | 144 +++++++++++ .../dapr/workflows/client/WorkflowState.java | 196 +++++++++++++++ .../runtime/DaprWorkflowContextImpl.java | 3 + .../client/DaprWorkflowClientTest.java | 71 +++++- .../client/WorkflowRuntimeStatusTest.java | 94 +++++++ .../workflows/client/WorkflowStateTest.java | 230 ++++++++++++++++++ .../dapr/client/domain/query/QueryTest.java | 4 +- 11 files changed, 919 insertions(+), 11 deletions(-) create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowFailureDetails.java create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowRuntimeStatus.java create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowState.java create mode 100644 sdk-workflows/src/test/java/io/dapr/workflows/client/WorkflowRuntimeStatusTest.java create mode 100644 sdk-workflows/src/test/java/io/dapr/workflows/client/WorkflowStateTest.java diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java index cf439c969..3dc5222a1 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java @@ -14,8 +14,11 @@ package io.dapr.examples.workflows; import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowState; +import java.time.Duration; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * For setup instructions, see the README. @@ -24,6 +27,7 @@ public class DemoWorkflowClient { /** * The main method. + * * @param args Input arguments (unused). * @throws InterruptedException If program has been interrupted. */ @@ -31,14 +35,37 @@ public static void main(String[] args) throws InterruptedException { DaprWorkflowClient client = new DaprWorkflowClient(); try (client) { - System.out.println("*****"); - String instanceId = client.scheduleNewWorkflow(DemoWorkflow.class); + String separatorStr = "*******"; + System.out.println(separatorStr); + String instanceId = client.scheduleNewWorkflow(DemoWorkflow.class, "input data"); System.out.printf("Started new workflow instance with random ID: %s%n", instanceId); - System.out.println("Sleep and allow this workflow instance to timeout..."); - TimeUnit.SECONDS.sleep(10); + System.out.println(separatorStr); + System.out.println("**GetInstanceMetadata:Running Workflow**"); + WorkflowState workflowMetadata = client.getInstanceState(instanceId, true); + System.out.printf("Result: %s%n", workflowMetadata); - System.out.println("*****"); + System.out.println(separatorStr); + System.out.println("**WaitForInstanceStart**"); + try { + WorkflowState waitForInstanceStartResult = + client.waitForInstanceStart(instanceId, Duration.ofSeconds(60), true); + System.out.printf("Result: %s%n", waitForInstanceStartResult); + } catch (TimeoutException ex) { + System.out.printf("waitForInstanceStart has an exception:%s%n", ex); + } + + System.out.println(separatorStr); + System.out.println("**WaitForInstanceCompletion**"); + try { + WorkflowState waitForInstanceCompletionResult = + client.waitForInstanceCompletion(instanceId, Duration.ofSeconds(60), true); + System.out.printf("Result: %s%n", waitForInstanceCompletionResult); + } catch (TimeoutException ex) { + System.out.printf("waitForInstanceCompletion has an exception:%s%n", ex); + } + + System.out.println(separatorStr); String instanceToTerminateId = "terminateMe"; client.scheduleNewWorkflow(DemoWorkflow.class, null, instanceToTerminateId); System.out.printf("Started new workflow instance with specified ID: %s%n", instanceToTerminateId); @@ -46,7 +73,7 @@ public static void main(String[] args) throws InterruptedException { TimeUnit.SECONDS.sleep(5); System.out.println("Terminate this workflow instance manually before the timeout is reached"); client.terminateWorkflow(instanceToTerminateId, null); - System.out.println("*****"); + System.out.println(separatorStr); } System.out.println("Exiting DemoWorkflowClient."); diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index c7338c5df..63fdf94a5 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -37,6 +37,12 @@ mockito-core test + + org.mockito + mockito-inline + 4.2.0 + test + org.junit.jupiter junit-jupiter-api diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java index 30605642e..14a260c4b 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java @@ -15,6 +15,7 @@ import com.microsoft.durabletask.DurableTaskClient; import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; +import com.microsoft.durabletask.OrchestrationMetadata; import io.dapr.config.Properties; import io.dapr.utils.Version; import io.dapr.workflows.runtime.Workflow; @@ -22,8 +23,13 @@ import io.grpc.ManagedChannelBuilder; import javax.annotation.Nullable; +import java.time.Duration; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +/** + * Defines client operations for managing Dapr Workflow instances. + */ public class DaprWorkflowClient implements AutoCloseable { private final DurableTaskClient innerClient; @@ -50,7 +56,6 @@ private DaprWorkflowClient(ManagedChannel grpcChannel) { * * @param innerClient DurableTaskGrpcClient with GRPC Channel set up. * @param grpcChannel ManagedChannel for instance variable setting. - * */ private DaprWorkflowClient(DurableTaskClient innerClient, ManagedChannel grpcChannel) { this.innerClient = innerClient; @@ -134,8 +139,78 @@ public void terminateWorkflow(String workflowInstanceId, @Nullable Object output } /** - * Closes the inner DurableTask client and shutdown the GRPC channel. + * Fetches workflow instance metadata from the configured durable store. + * + * @param instanceId the unique ID of the workflow instance to fetch + * @param getInputsAndOutputs true to fetch the workflow instance's + inputs, outputs, and custom status, or false to omit them + * @return a metadata record that describes the workflow instance and its + execution status, or a default instance if no such instance is found. + */ + @Nullable + public WorkflowState getInstanceState(String instanceId, boolean getInputsAndOutputs) { + OrchestrationMetadata metadata = this.innerClient.getInstanceMetadata(instanceId, getInputsAndOutputs); + if (metadata == null) { + return null; + } + return new WorkflowState(metadata); + } + + /** + * Waits for an workflow to start running and returns an + * {@link WorkflowState} object that contains metadata about the started + * instance and optionally its input, output, and custom status payloads. + * + *

A "started" workflow instance is any instance not in the Pending state. + * + *

If an workflow instance is already running when this method is called, + * the method will return immediately. + * + * @param instanceId the unique ID of the workflow instance to wait for + * @param timeout the amount of time to wait for the workflow instance to start + * @param getInputsAndOutputs true to fetch the workflow instance's + * inputs, outputs, and custom status, or false to omit them + * @throws TimeoutException when the workflow instance is not started within the specified amount of time + * @return the workflow instance metadata or null if no such instance is found + */ + @Nullable + public WorkflowState waitForInstanceStart(String instanceId, Duration timeout, boolean getInputsAndOutputs) + throws TimeoutException { + + OrchestrationMetadata metadata = this.innerClient.waitForInstanceStart(instanceId, timeout, getInputsAndOutputs); + return metadata == null ? null : new WorkflowState(metadata); + } + + /** + * Waits for an workflow to complete and returns an {@link WorkflowState} object that contains + * metadata about the completed instance. + * + *

A "completed" workflow instance is any instance in one of the terminal states. For example, the + * Completed, Failed, or Terminated states. + * + *

Workflows are long-running and could take hours, days, or months before completing. + * Workflows can also be eternal, in which case they'll never complete unless terminated. + * In such cases, this call may block indefinitely, so care must be taken to ensure appropriate timeouts are used. + * If an workflow instance is already complete when this method is called, the method will return immediately. * + * @param instanceId the unique ID of the workflow instance to wait for + * @param timeout the amount of time to wait for the workflow instance to complete + * @param getInputsAndOutputs true to fetch the workflow instance's inputs, outputs, and custom + * status, or false to omit them + * @throws TimeoutException when the workflow instance is not completed within the specified amount of time + * @return the workflow instance metadata or null if no such instance is found + */ + @Nullable + public WorkflowState waitForInstanceCompletion(String instanceId, Duration timeout, + boolean getInputsAndOutputs) throws TimeoutException { + + OrchestrationMetadata metadata = + this.innerClient.waitForInstanceCompletion(instanceId, timeout, getInputsAndOutputs); + return metadata == null ? null : new WorkflowState(metadata); + } + + /** + * Closes the inner DurableTask client and shutdown the GRPC channel. */ public void close() throws InterruptedException { if (this.innerClient != null) { diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowFailureDetails.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowFailureDetails.java new file mode 100644 index 000000000..18ab00658 --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowFailureDetails.java @@ -0,0 +1,64 @@ +/* + * Copyright 2023 The Dapr Authors + * Licensed 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 io.dapr.workflows.client; + +import com.microsoft.durabletask.FailureDetails; + +/** + * Represents a workflow failure details. + */ +public class WorkflowFailureDetails { + + FailureDetails workflowFailureDetails; + + /** + * Class constructor. + * @param failureDetails failure Details + */ + public WorkflowFailureDetails(FailureDetails failureDetails) { + this.workflowFailureDetails = failureDetails; + } + + /** + * Gets the error type, which is the namespace-qualified exception type name. + * + * @return the error type, which is the namespace-qualified exception type name + */ + public String getErrorType() { + return workflowFailureDetails.getErrorType(); + } + + /** + * Gets the error message. + * + * @return the error message + */ + public String getErrorMessage() { + return workflowFailureDetails.getErrorMessage(); + } + + /** + * Gets the stack trace. + * + * @return the stack trace + */ + public String getStackTrace() { + return workflowFailureDetails.getStackTrace(); + } + + @Override + public String toString() { + return workflowFailureDetails.toString(); + } +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowRuntimeStatus.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowRuntimeStatus.java new file mode 100644 index 000000000..0b24cd824 --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowRuntimeStatus.java @@ -0,0 +1,144 @@ +/* + * Copyright 2023 The Dapr Authors + * Licensed 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 io.dapr.workflows.client; + +import com.microsoft.durabletask.OrchestrationRuntimeStatus; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Enum describing the runtime status of a workflow. + */ +public enum WorkflowRuntimeStatus { + /** + * The workflow started running. + */ + RUNNING, + + /** + * The workflow completed normally. + */ + COMPLETED, + + /** + * The workflow is continued as new. + */ + CONTINUED_AS_NEW, + + /** + * The workflow completed with an unhandled exception. + */ + FAILED, + + /** + * The workflow was abruptly cancelled via a management API call. + */ + CANCELED, + + /** + * The workflow was abruptly terminated via a management API call. + */ + TERMINATED, + + /** + * The workflow was scheduled but hasn't started running. + */ + PENDING, + + /** + * The workflow was suspended. + */ + SUSPENDED, + + /** + * The status of the workflow is unknown. + */ + UNKNOWN; + + /** + * Convert runtime OrchestrationRuntimeStatus to WorkflowRuntimeStatus. + * + * @param status The OrchestrationRuntimeStatus to convert to WorkflowRuntimeStatus. + * @return The runtime status of the workflow. + */ + public static WorkflowRuntimeStatus fromOrchestrationRuntimeStatus(OrchestrationRuntimeStatus status) { + + if (status == null) { + return WorkflowRuntimeStatus.UNKNOWN; + } + + switch (status) { + case RUNNING: + return WorkflowRuntimeStatus.RUNNING; + case COMPLETED: + return WorkflowRuntimeStatus.COMPLETED; + case CONTINUED_AS_NEW: + return WorkflowRuntimeStatus.CONTINUED_AS_NEW; + case FAILED: + return WorkflowRuntimeStatus.FAILED; + case CANCELED: + return WorkflowRuntimeStatus.CANCELED; + case TERMINATED: + return WorkflowRuntimeStatus.TERMINATED; + case PENDING: + return WorkflowRuntimeStatus.PENDING; + case SUSPENDED: + return WorkflowRuntimeStatus.SUSPENDED; + default: + return WorkflowRuntimeStatus.UNKNOWN; + } + } + + /** + * Convert runtime WorkflowRuntimeStatus to OrchestrationRuntimeStatus. + * + * @param status The OrchestrationRuntimeStatus to convert to WorkflowRuntimeStatus. + * @return The runtime status of the Orchestration. + */ + public static OrchestrationRuntimeStatus toOrchestrationRuntimeStatus(WorkflowRuntimeStatus status) { + + switch (status) { + case RUNNING: + return OrchestrationRuntimeStatus.RUNNING; + case COMPLETED: + return OrchestrationRuntimeStatus.COMPLETED; + case CONTINUED_AS_NEW: + return OrchestrationRuntimeStatus.CONTINUED_AS_NEW; + case FAILED: + return OrchestrationRuntimeStatus.FAILED; + case CANCELED: + return OrchestrationRuntimeStatus.CANCELED; + case TERMINATED: + return OrchestrationRuntimeStatus.TERMINATED; + case PENDING: + return OrchestrationRuntimeStatus.PENDING; + case SUSPENDED: + return OrchestrationRuntimeStatus.SUSPENDED; + default: + return null; + } + } + + /** + * Convert runtime WorkflowRuntimeStatus to OrchestrationRuntimeStatus. + * + * @param statuses The list of orchestrationRuntimeStatus to convert to a list of WorkflowRuntimeStatuses. + * @return The list runtime status of the Orchestration. + */ + public static List toOrchestrationRuntimeStatus(List statuses) { + return statuses.stream() + .map(x -> toOrchestrationRuntimeStatus(x)) + .collect(Collectors.toList()); + } +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowState.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowState.java new file mode 100644 index 000000000..abf73dca6 --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowState.java @@ -0,0 +1,196 @@ +/* + * Copyright 2023 The Dapr Authors + * Licensed 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 io.dapr.workflows.client; + +import com.microsoft.durabletask.FailureDetails; +import com.microsoft.durabletask.OrchestrationMetadata; +import com.microsoft.durabletask.OrchestrationRuntimeStatus; +import java.time.Instant; + +/** + * Represents a snapshot of an workflow instance's current state, including + * metadata. + */ +public class WorkflowState { + + OrchestrationMetadata orchestrationMetadata; + WorkflowFailureDetails failureDetails; + + /** + * Class constructor. + * + * @param orchestrationMetadata Durable task orchestration metadata + */ + public WorkflowState(OrchestrationMetadata orchestrationMetadata) { + // This value will be null if the workflow doesn't exist. + this.orchestrationMetadata = orchestrationMetadata; + + FailureDetails details = orchestrationMetadata.getFailureDetails(); + if (details != null) { + this.failureDetails = new WorkflowFailureDetails(details); + } + } + + /** + * Gets the name of the workflow. + * + * @return the name of the workflow + */ + public String getName() { + return orchestrationMetadata.getName(); + } + + /** + * Gets the unique ID of the workflow instance. + * + * @return the unique ID of the workflow instance + */ + public String getInstanceId() { + return orchestrationMetadata.getInstanceId(); + } + + /** + * Gets the current runtime status of the workflow instance at the time this + * object was fetched. + * + * @return the current runtime status of the workflow instance at the time this + * object was fetched + */ + public WorkflowRuntimeStatus getRuntimeStatus() { + return WorkflowRuntimeStatus.fromOrchestrationRuntimeStatus(orchestrationMetadata.getRuntimeStatus()); + } + + /** + * Gets the workflow instance's creation time in UTC. + * + * @return the workflow instance's creation time in UTC + */ + public Instant getCreatedAt() { + return orchestrationMetadata.getCreatedAt(); + } + + /** + * Gets the workflow instance's last updated time in UTC. + * + * @return the workflow instance's last updated time in UTC + */ + public Instant getLastUpdatedAt() { + return orchestrationMetadata.getLastUpdatedAt(); + } + + /** + * Gets the workflow instance's serialized input, if any, as a string value. + * + * @return the workflow instance's serialized input or {@code null} + */ + public String getSerializedInput() { + return orchestrationMetadata.getSerializedInput(); + } + + /** + * Gets the workflow instance's serialized output, if any, as a string value. + * + * @return the workflow instance's serialized output or {@code null} + */ + public String getSerializedOutput() { + return orchestrationMetadata.getSerializedOutput(); + } + + /** + * Gets the failure details, if any, for the failed workflow instance. + * + *

This method returns data only if the workflow is in the + * {@link OrchestrationRuntimeStatus#FAILED} state, + * and only if this instance metadata was fetched with the option to include + * output data. + * + * @return the failure details of the failed workflow instance or {@code null} + */ + public WorkflowFailureDetails getFailureDetails() { + return this.failureDetails; + } + + /** + * Gets a value indicating whether the workflow instance was running at the time + * this object was fetched. + * + * @return {@code true} if the workflow existed and was in a running state; + * otherwise {@code false} + */ + public boolean isRunning() { + return orchestrationMetadata.isRunning(); + } + + /** + * Gets a value indicating whether the workflow instance was completed at the + * time this object was fetched. + * + *

A workflow instance is considered completed when its runtime status value is + * {@link WorkflowRuntimeStatus#COMPLETED}, + * {@link WorkflowRuntimeStatus#FAILED}, or + * {@link WorkflowRuntimeStatus#TERMINATED}. + * + * @return {@code true} if the workflow was in a terminal state; otherwise + * {@code false} + */ + public boolean isCompleted() { + return orchestrationMetadata.isCompleted(); + } + + /** + * Deserializes the workflow's input into an object of the specified type. + * + *

Deserialization is performed using the DataConverter that was + * configured on the DurableTaskClient object that created this workflow + * metadata object. + * + * @param type the class associated with the type to deserialize the input data + * into + * @param the type to deserialize the input data into + * @return the deserialized input value + * @throws IllegalStateException if the metadata was fetched without the option + * to read inputs and outputs + */ + public T readInputAs(Class type) { + return orchestrationMetadata.readInputAs(type); + } + + /** + * Deserializes the workflow's output into an object of the specified type. + * + *

Deserialization is performed using the DataConverter that was + * configured on the DurableTaskClient + * object that created this workflow metadata object. + * + * @param type the class associated with the type to deserialize the output data + * into + * @param the type to deserialize the output data into + * @return the deserialized input value + * @throws IllegalStateException if the metadata was fetched without the option + * to read inputs and outputs + */ + public T readOutputAs(Class type) { + return orchestrationMetadata.readOutputAs(type); + } + + /** + * Generates a user-friendly string representation of the current metadata + * object. + * + * @return a user-friendly string representation of the current metadata object + */ + public String toString() { + return orchestrationMetadata.toString(); + } +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java index 3460f2be6..39e715c33 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java @@ -20,6 +20,9 @@ import org.slf4j.helpers.NOPLogger; import java.time.Duration; +/** + * Dapr workflow context implementation. + */ public class DaprWorkflowContextImpl implements WorkflowContext { private final TaskOrchestrationContext innerContext; private final Logger logger; diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/client/DaprWorkflowClientTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/client/DaprWorkflowClientTest.java index b9a32a483..285b45449 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/client/DaprWorkflowClientTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/client/DaprWorkflowClientTest.java @@ -14,16 +14,21 @@ package io.dapr.workflows.client; import com.microsoft.durabletask.DurableTaskClient; +import com.microsoft.durabletask.OrchestrationMetadata; +import com.microsoft.durabletask.OrchestrationRuntimeStatus; import io.dapr.workflows.runtime.Workflow; import io.dapr.workflows.runtime.WorkflowContext; import io.grpc.ManagedChannel; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; - import java.lang.reflect.Constructor; +import java.time.Duration; import java.util.Arrays; +import java.util.concurrent.TimeoutException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.Mockito.*; @@ -103,6 +108,70 @@ public void terminateWorkflow() { verify(mockInnerClient, times(1)).terminate(expectedArgument, null); } + @Test + public void getInstanceMetadata() { + + // Arrange + String instanceId = "TestWorkflowInstanceId"; + + OrchestrationMetadata expectedMetadata = mock(OrchestrationMetadata.class); + when(expectedMetadata.getInstanceId()).thenReturn(instanceId); + when(expectedMetadata.getName()).thenReturn("WorkflowName"); + when(expectedMetadata.getRuntimeStatus()).thenReturn(OrchestrationRuntimeStatus.RUNNING); + when(mockInnerClient.getInstanceMetadata(instanceId, true)).thenReturn(expectedMetadata); + + // Act + WorkflowState metadata = client.getInstanceState(instanceId, true); + + // Assert + verify(mockInnerClient, times(1)).getInstanceMetadata(instanceId, true); + assertNotEquals(metadata, null); + assertEquals(metadata.getInstanceId(), expectedMetadata.getInstanceId()); + assertEquals(metadata.getName(), expectedMetadata.getName()); + assertEquals(metadata.isRunning(), expectedMetadata.isRunning()); + assertEquals(metadata.isCompleted(), expectedMetadata.isCompleted()); + } + + @Test + public void waitForInstanceStart() throws TimeoutException { + + // Arrange + String instanceId = "TestWorkflowInstanceId"; + Duration timeout = Duration.ofSeconds(10); + + OrchestrationMetadata expectedMetadata = mock(OrchestrationMetadata.class); + when(expectedMetadata.getInstanceId()).thenReturn(instanceId); + when(mockInnerClient.waitForInstanceStart(instanceId, timeout, true)).thenReturn(expectedMetadata); + + // Act + WorkflowState result = client.waitForInstanceStart(instanceId, timeout, true); + + // Assert + verify(mockInnerClient, times(1)).waitForInstanceStart(instanceId, timeout, true); + assertNotEquals(result, null); + assertEquals(result.getInstanceId(), expectedMetadata.getInstanceId()); + } + + @Test + public void waitForInstanceCompletion() throws TimeoutException { + + // Arrange + String instanceId = "TestWorkflowInstanceId"; + Duration timeout = Duration.ofSeconds(10); + + OrchestrationMetadata expectedMetadata = mock(OrchestrationMetadata.class); + when(expectedMetadata.getInstanceId()).thenReturn(instanceId); + when(mockInnerClient.waitForInstanceCompletion(instanceId, timeout, true)).thenReturn(expectedMetadata); + + // Act + WorkflowState result = client.waitForInstanceCompletion(instanceId, timeout, true); + + // Assert + verify(mockInnerClient, times(1)).waitForInstanceCompletion(instanceId, timeout, true); + assertNotEquals(result, null); + assertEquals(result.getInstanceId(), expectedMetadata.getInstanceId()); + } + @Test public void close() throws InterruptedException { client.close(); diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/client/WorkflowRuntimeStatusTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/client/WorkflowRuntimeStatusTest.java new file mode 100644 index 000000000..add4a31dc --- /dev/null +++ b/sdk-workflows/src/test/java/io/dapr/workflows/client/WorkflowRuntimeStatusTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2023 The Dapr Authors + * Licensed 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 io.dapr.workflows.client; + + +import com.microsoft.durabletask.OrchestrationRuntimeStatus; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + + +public class WorkflowRuntimeStatusTest { + + @Before + public void setUp() throws Exception { + + } + + @Test + public void fromOrchestrationRuntimeStatus() { + + Assert.assertEquals(WorkflowRuntimeStatus.fromOrchestrationRuntimeStatus(OrchestrationRuntimeStatus.RUNNING), + WorkflowRuntimeStatus.RUNNING); + + Assert.assertEquals(WorkflowRuntimeStatus.fromOrchestrationRuntimeStatus(OrchestrationRuntimeStatus.COMPLETED), + WorkflowRuntimeStatus.COMPLETED); + + Assert.assertEquals( + WorkflowRuntimeStatus.fromOrchestrationRuntimeStatus(OrchestrationRuntimeStatus.CONTINUED_AS_NEW), + WorkflowRuntimeStatus.CONTINUED_AS_NEW); + + Assert.assertEquals(WorkflowRuntimeStatus.fromOrchestrationRuntimeStatus(OrchestrationRuntimeStatus.FAILED), + WorkflowRuntimeStatus.FAILED); + + Assert.assertEquals(WorkflowRuntimeStatus.fromOrchestrationRuntimeStatus(OrchestrationRuntimeStatus.CANCELED), + WorkflowRuntimeStatus.CANCELED); + + Assert.assertEquals(WorkflowRuntimeStatus.fromOrchestrationRuntimeStatus(OrchestrationRuntimeStatus.TERMINATED), + WorkflowRuntimeStatus.TERMINATED); + + Assert.assertEquals(WorkflowRuntimeStatus.fromOrchestrationRuntimeStatus(OrchestrationRuntimeStatus.PENDING), + WorkflowRuntimeStatus.PENDING); + + Assert.assertEquals(WorkflowRuntimeStatus.fromOrchestrationRuntimeStatus(OrchestrationRuntimeStatus.SUSPENDED), + WorkflowRuntimeStatus.SUSPENDED); + + Assert.assertEquals(WorkflowRuntimeStatus.fromOrchestrationRuntimeStatus(null), + WorkflowRuntimeStatus.UNKNOWN); + + } + + @Test + public void toOrchestrationRuntimeStatus() { + Assert.assertEquals(WorkflowRuntimeStatus.toOrchestrationRuntimeStatus(WorkflowRuntimeStatus.RUNNING), + OrchestrationRuntimeStatus.RUNNING); + + Assert.assertEquals(WorkflowRuntimeStatus.toOrchestrationRuntimeStatus(WorkflowRuntimeStatus.COMPLETED), + OrchestrationRuntimeStatus.COMPLETED); + + Assert.assertEquals( + WorkflowRuntimeStatus.toOrchestrationRuntimeStatus(WorkflowRuntimeStatus.CONTINUED_AS_NEW), + OrchestrationRuntimeStatus.CONTINUED_AS_NEW); + + Assert.assertEquals(WorkflowRuntimeStatus.toOrchestrationRuntimeStatus(WorkflowRuntimeStatus.FAILED), + OrchestrationRuntimeStatus.FAILED); + + Assert.assertEquals(WorkflowRuntimeStatus.toOrchestrationRuntimeStatus(WorkflowRuntimeStatus.CANCELED), + OrchestrationRuntimeStatus.CANCELED); + + Assert.assertEquals(WorkflowRuntimeStatus.toOrchestrationRuntimeStatus(WorkflowRuntimeStatus.TERMINATED), + OrchestrationRuntimeStatus.TERMINATED); + + Assert.assertEquals(WorkflowRuntimeStatus.toOrchestrationRuntimeStatus(WorkflowRuntimeStatus.PENDING), + OrchestrationRuntimeStatus.PENDING); + + Assert.assertEquals(WorkflowRuntimeStatus.toOrchestrationRuntimeStatus(WorkflowRuntimeStatus.SUSPENDED), + OrchestrationRuntimeStatus.SUSPENDED); + + Assert.assertEquals(WorkflowRuntimeStatus.toOrchestrationRuntimeStatus(WorkflowRuntimeStatus.UNKNOWN), + null); + + } +} diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/client/WorkflowStateTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/client/WorkflowStateTest.java new file mode 100644 index 000000000..599af5240 --- /dev/null +++ b/sdk-workflows/src/test/java/io/dapr/workflows/client/WorkflowStateTest.java @@ -0,0 +1,230 @@ +/* + * Copyright 2023 The Dapr Authors + * Licensed 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 io.dapr.workflows.client; + +import com.microsoft.durabletask.FailureDetails; +import com.microsoft.durabletask.OrchestrationMetadata; +import com.microsoft.durabletask.OrchestrationRuntimeStatus; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import java.time.Instant; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +public class WorkflowStateTest { + + private OrchestrationMetadata mockOrchestrationMetadata; + private WorkflowState workflowMetadata; + + @Before + public void setUp() throws Exception { + mockOrchestrationMetadata = mock(OrchestrationMetadata.class); + workflowMetadata = new WorkflowState(mockOrchestrationMetadata); + } + + @Test + public void getInstanceId() { + // Arrange + String expected = "instanceId"; + when(mockOrchestrationMetadata.getInstanceId()).thenReturn(expected); + + // Act + String result = workflowMetadata.getInstanceId(); + + // Assert + verify(mockOrchestrationMetadata, times(1)).getInstanceId(); + Assert.assertEquals(result, expected); + } + + @Test + public void getName() { + // Arrange + String expected = "WorkflowName"; + when(mockOrchestrationMetadata.getName()).thenReturn(expected); + + // Act + String result = workflowMetadata.getName(); + + // Assert + verify(mockOrchestrationMetadata, times(1)).getName(); + Assert.assertEquals(result, expected); + } + + @Test + public void getCreatedAt() { + // Arrange + Instant expected = Instant.now(); + when(mockOrchestrationMetadata.getCreatedAt()).thenReturn(expected); + + // Act + Instant result = workflowMetadata.getCreatedAt(); + + // Assert + verify(mockOrchestrationMetadata, times(1)).getCreatedAt(); + Assert.assertEquals(result, expected); + } + + @Test + public void getLastUpdatedAt() { + // Arrange + Instant expected = Instant.now(); + when(mockOrchestrationMetadata.getLastUpdatedAt()).thenReturn(expected); + + // Act + Instant result = workflowMetadata.getLastUpdatedAt(); + + // Assert + verify(mockOrchestrationMetadata, times(1)).getLastUpdatedAt(); + Assert.assertEquals(result, expected); + } + + @Test + public void getFailureDetails() { + // Arrange + FailureDetails mockFailureDetails = mock(FailureDetails.class); + when(mockFailureDetails.getErrorType()).thenReturn("errorType"); + when(mockFailureDetails.getErrorMessage()).thenReturn("errorMessage"); + when(mockFailureDetails.getStackTrace()).thenReturn("stackTrace"); + + OrchestrationMetadata orchestrationMetadata = mock(OrchestrationMetadata.class); + when(orchestrationMetadata.getFailureDetails()).thenReturn(mockFailureDetails); + + // Act + WorkflowState metadata = new WorkflowState(orchestrationMetadata); + WorkflowFailureDetails result = metadata.getFailureDetails(); + + // Assert + verify(orchestrationMetadata, times(1)).getFailureDetails(); + Assert.assertEquals(result.getErrorType(), mockFailureDetails.getErrorType()); + Assert.assertEquals(result.getErrorMessage(), mockFailureDetails.getErrorMessage()); + Assert.assertEquals(result.getStackTrace(), mockFailureDetails.getStackTrace()); + } + + @Test + public void getRuntimeStatus() { + // Arrange + WorkflowRuntimeStatus expected = WorkflowRuntimeStatus.RUNNING; + when(mockOrchestrationMetadata.getRuntimeStatus()).thenReturn(OrchestrationRuntimeStatus.RUNNING); + + // Act + WorkflowRuntimeStatus result = workflowMetadata.getRuntimeStatus(); + + // Assert + verify(mockOrchestrationMetadata, times(1)).getRuntimeStatus(); + Assert.assertEquals(result, expected); + } + + @Test + public void isRunning() { + // Arrange + boolean expected = true; + when(mockOrchestrationMetadata.isRunning()).thenReturn(expected); + + // Act + boolean result = workflowMetadata.isRunning(); + + // Assert + verify(mockOrchestrationMetadata, times(1)).isRunning(); + Assert.assertEquals(result, expected); + } + + @Test + public void isCompleted() { + // Arrange + boolean expected = true; + when(mockOrchestrationMetadata.isCompleted()).thenReturn(expected); + + // Act + boolean result = workflowMetadata.isCompleted(); + + // Assert + verify(mockOrchestrationMetadata, times(1)).isCompleted(); + Assert.assertEquals(result, expected); + } + + @Test + public void getSerializedInput() { + // Arrange + String expected = "{input: \"test\"}"; + when(mockOrchestrationMetadata.getSerializedInput()).thenReturn(expected); + + // Act + String result = workflowMetadata.getSerializedInput(); + + // Assert + verify(mockOrchestrationMetadata, times(1)).getSerializedInput(); + Assert.assertEquals(result, expected); + } + + @Test + public void getSerializedOutput() { + // Arrange + String expected = "{output: \"test\"}"; + when(mockOrchestrationMetadata.getSerializedOutput()).thenReturn(expected); + + // Act + String result = workflowMetadata.getSerializedOutput(); + + // Assert + verify(mockOrchestrationMetadata, times(1)).getSerializedOutput(); + Assert.assertEquals(result, expected); + } + + @Test + public void readInputAs() { + // Arrange + String expected = "[{property: \"test input\"}}]"; + when(mockOrchestrationMetadata.readInputAs(String.class)).thenReturn(expected); + + // Act + String result = workflowMetadata.readInputAs(String.class); + + // Assert + verify(mockOrchestrationMetadata, times(1)).readInputAs(String.class); + Assert.assertEquals(result, expected); + } + + @Test + public void readOutputAs() { + // Arrange + String expected = "[{property: \"test output\"}}]"; + when(mockOrchestrationMetadata.readOutputAs(String.class)).thenReturn(expected); + + // Act + String result = workflowMetadata.readOutputAs(String.class); + + // Assert + verify(mockOrchestrationMetadata, times(1)).readOutputAs(String.class); + Assert.assertEquals(result, expected); + } + + @Test + public void testToString() { + // Arrange + String expected = "string value"; + when(mockOrchestrationMetadata.toString()).thenReturn(expected); + + // Act + String result = workflowMetadata.toString(); + + // Assert + //verify(mockOrchestrationMetadata, times(1)).toString(); + Assert.assertEquals(result, expected); + } +} diff --git a/sdk/src/test/java/io/dapr/client/domain/query/QueryTest.java b/sdk/src/test/java/io/dapr/client/domain/query/QueryTest.java index 74602cf3f..220a7f209 100644 --- a/sdk/src/test/java/io/dapr/client/domain/query/QueryTest.java +++ b/sdk/src/test/java/io/dapr/client/domain/query/QueryTest.java @@ -28,7 +28,7 @@ public void testQuerySerialize() throws JsonProcessingException { orFilter.addClause(new EqFilter<>("v2", true)); orFilter.addClause(new InFilter<>("v3", 1.3, 1.5)); - filter.addClause(orFilter); + filter.addClause((Filter) orFilter); // Add Filter q.setFilter(filter); @@ -110,7 +110,7 @@ public void testQueryInValidFilter() throws JsonProcessingException { orFilter.addClause(new EqFilter<>("v2", true)); // invalid OR filter - filter.addClause(orFilter); + filter.addClause((Filter) orFilter); // Add Filter q.setFilter(filter);