diff --git a/examples/src/main/java/io/dapr/examples/workflows/README.md b/examples/src/main/java/io/dapr/examples/workflows/README.md index 4d9c057f2..b90726080 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/README.md +++ b/examples/src/main/java/io/dapr/examples/workflows/README.md @@ -420,7 +420,6 @@ client.raiseEvent(instanceId, "Approval", true); Start the workflow and client using the following commands: -ex ```sh dapr run --app-id demoworkflowworker --resources-path ./components/workflows -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.externalevent.DemoExternalEventWorker ``` @@ -652,4 +651,60 @@ Key Points: 1. Each successful booking step adds its compensation action to an ArrayList 2. If an error occurs, the list of compensations is reversed and executed in reverse order 3. The workflow ensures that all resources are properly cleaned up even if the process fails -4. Each activity simulates work with a short delay for demonstration purposes \ No newline at end of file +4. Each activity simulates work with a short delay for demonstration purposes + + +### Suspend/Resume Pattern + +Workflow instances can be suspended and resumed. This example shows how to use the suspend and resume commands. + +For testing the suspend and resume operations we will use the same workflow definition used by the DemoExternalEventWorkflow. + +Start the workflow and client using the following commands: + + + + +```sh +dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeWorker +``` + +```sh +java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeClient +``` + + + +The worker logs: +```text +== APP == 2023-11-07 16:01:23,279 {HH:mm:ss.SSS} [main] INFO io.dapr.workflows.WorkflowContext - Starting Workflow: io.dapr.examples.workflows.suspendresume.DemoExternalEventWorkflow +== APP == 2023-11-07 16:01:23,279 {HH:mm:ss.SSS} [main] INFO io.dapr.workflows.WorkflowContext - Waiting for approval... +== APP == 2023-11-07 16:01:23,324 {HH:mm:ss.SSS} [main] INFO io.dapr.workflows.WorkflowContext - approval granted - do the approved action +== APP == 2023-11-07 16:01:23,348 {HH:mm:ss.SSS} [main] INFO i.d.e.w.e.ApproveActivity - Starting Activity: io.dapr.examples.workflows.externalevent.ApproveActivity +== APP == 2023-11-07 16:01:23,348 {HH:mm:ss.SSS} [main] INFO i.d.e.w.e.ApproveActivity - Running approval activity... +== APP == 2023-11-07 16:01:28,410 {HH:mm:ss.SSS} [main] INFO io.dapr.workflows.WorkflowContext - approval-activity finished +``` + +The client log: +```text +Started a new external-event model workflow with instance ID: 23410d96-1afe-4698-9fcd-c01c1e0db255 +workflow instance with ID: 23410d96-1afe-4698-9fcd-c01c1e0db255 completed. +``` \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeClient.java b/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeClient.java new file mode 100644 index 000000000..7e8289798 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeClient.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025 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.examples.workflows.suspendresume; + +import io.dapr.examples.workflows.externalevent.DemoExternalEventWorkflow; +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowInstanceStatus; + +import java.util.concurrent.TimeoutException; + +public class DemoSuspendResumeClient { + /** + * The main method to start the client. + * + * @param args Input arguments (unused). + * @throws InterruptedException If program has been interrupted. + */ + public static void main(String[] args) { + try (DaprWorkflowClient client = new DaprWorkflowClient()) { + String instanceId = client.scheduleNewWorkflow(DemoExternalEventWorkflow.class); + System.out.printf("Started a new external-event workflow with instance ID: %s%n", instanceId); + + + System.out.printf("Suspending Workflow Instance: %s%n", instanceId ); + client.suspendWorkflow(instanceId, "suspending workflow instance."); + + WorkflowInstanceStatus instanceState = client.getInstanceState(instanceId, false); + assert instanceState != null; + System.out.printf("Workflow Instance Status: %s%n", instanceState.getRuntimeStatus().name() ); + + System.out.printf("Let's resume the Workflow Instance before sending the external event: %s%n", instanceId ); + client.resumeWorkflow(instanceId, "resuming workflow instance."); + + instanceState = client.getInstanceState(instanceId, false); + assert instanceState != null; + System.out.printf("Workflow Instance Status: %s%n", instanceState.getRuntimeStatus().name() ); + + System.out.printf("Now that the instance is RUNNING again, lets send the external event. %n"); + client.raiseEvent(instanceId, "Approval", true); + + client.waitForInstanceCompletion(instanceId, null, true); + System.out.printf("workflow instance with ID: %s completed.", instanceId); + + } catch (TimeoutException | InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeWorker.java b/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeWorker.java new file mode 100644 index 000000000..5ca4bc34b --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeWorker.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 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.examples.workflows.suspendresume; + +import io.dapr.examples.workflows.externalevent.ApproveActivity; +import io.dapr.examples.workflows.externalevent.DemoExternalEventWorkflow; +import io.dapr.examples.workflows.externalevent.DenyActivity; +import io.dapr.workflows.runtime.WorkflowRuntime; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; + +public class DemoSuspendResumeWorker { + /** + * The main method of this app. + * + * @param args The port the app will listen on. + * @throws Exception An Exception. + */ + public static void main(String[] args) throws Exception { + // Register the Workflow with the builder. + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(DemoExternalEventWorkflow.class); + builder.registerActivity(ApproveActivity.class); + builder.registerActivity(DenyActivity.class); + + // Build and then start the workflow runtime pulling and executing tasks + WorkflowRuntime runtime = builder.build(); + System.out.println("Start workflow runtime"); + runtime.start(); + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/DaprWorkflowsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/DaprWorkflowsIT.java index 5b019f831..19fe8f986 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/DaprWorkflowsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/DaprWorkflowsIT.java @@ -20,6 +20,7 @@ import io.dapr.testcontainers.DaprLogLevel; import io.dapr.workflows.client.DaprWorkflowClient; import io.dapr.workflows.client.WorkflowInstanceStatus; +import io.dapr.workflows.client.WorkflowRuntimeStatus; import io.dapr.workflows.runtime.WorkflowRuntime; import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; import org.junit.jupiter.api.BeforeEach; @@ -117,6 +118,36 @@ public void testWorkflows() throws Exception { assertEquals(instanceId, workflowOutput.getWorkflowId()); } + @Test + public void testSuspendAndResumeWorkflows() throws Exception { + TestWorkflowPayload payload = new TestWorkflowPayload(new ArrayList<>()); + String instanceId = workflowClient.scheduleNewWorkflow(TestWorkflow.class, payload); + workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(10), false); + + workflowClient.suspendWorkflow(instanceId, "testing suspend."); + + + WorkflowInstanceStatus instanceState = workflowClient.getInstanceState(instanceId, false); + assertNotNull(instanceState); + assertEquals(WorkflowRuntimeStatus.SUSPENDED, instanceState.getRuntimeStatus()); + + workflowClient.resumeWorkflow(instanceId, "testing resume"); + + instanceState = workflowClient.getInstanceState(instanceId, false); + assertNotNull(instanceState); + assertEquals(WorkflowRuntimeStatus.RUNNING, instanceState.getRuntimeStatus()); + + workflowClient.raiseEvent(instanceId, "MoveForward", payload); + + Duration timeout = Duration.ofSeconds(10); + instanceState = workflowClient.waitForInstanceCompletion(instanceId, timeout, true); + + assertNotNull(instanceState); + assertEquals(WorkflowRuntimeStatus.COMPLETED, instanceState.getRuntimeStatus()); + + } + + private TestWorkflowPayload deserialize(String value) throws JsonProcessingException { return OBJECT_MAPPER.readValue(value, TestWorkflowPayload.class); } 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 ab46dff79..b24c8bcc9 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 @@ -129,6 +129,26 @@ public String scheduleNewWorkflow(Class clazz, NewWorkfl orchestrationInstanceOptions); } + /** + * Suspend the workflow associated with the provided instance id. + * + * @param workflowInstanceId Workflow instance id to suspend. + * @param reason reason for suspending the workflow instance. + */ + public void suspendWorkflow(String workflowInstanceId, @Nullable String reason) { + this.innerClient.suspendInstance(workflowInstanceId, reason); + } + + /** + * Resume the workflow associated with the provided instance id. + * + * @param workflowInstanceId Workflow instance id to resume. + * @param reason reason for resuming the workflow instance. + */ + public void resumeWorkflow(String workflowInstanceId, @Nullable String reason) { + this.innerClient.resumeInstance(workflowInstanceId, reason); + } + /** * Terminates the workflow associated with the provided instance id. * 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 3ad66877c..55f7c9fdd 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 @@ -217,6 +217,17 @@ public void raiseEvent() { expectedEventName, expectedEventPayload); } + @Test + public void suspendResumeInstance() { + String expectedArgument = "TestWorkflowInstanceId"; + client.suspendWorkflow(expectedArgument, "suspending workflow instance"); + client.resumeWorkflow(expectedArgument, "resuming workflow instance"); + verify(mockInnerClient, times(1)).suspendInstance(expectedArgument, + "suspending workflow instance"); + verify(mockInnerClient, times(1)).resumeInstance(expectedArgument, + "resuming workflow instance"); + } + @Test public void purgeInstance() { String expectedArgument = "TestWorkflowInstanceId"; diff --git a/spring-boot-examples/workflows/README.md b/spring-boot-examples/workflows/README.md index 738c41a17..ae02d2ee6 100644 --- a/spring-boot-examples/workflows/README.md +++ b/spring-boot-examples/workflows/README.md @@ -5,6 +5,7 @@ This application allows you to run different [workflow patterns](https://docs.da - Parent/Child Workflows - Continue workflow by sending External Events - Fan Out/In activities for parallel execution +- Suspend/Resume workflows ## Running these examples from source code @@ -392,8 +393,141 @@ i.d.s.e.wfp.fanoutin.CountWordsActivity : Activity finished io.dapr.workflows.WorkflowContext : Workflow finished with result: 60 ``` +### Suspend/Resume Workflow example + +In this example, we start a workflow that executes an activity and then waits for an event. While the workflow instance +is waiting for the event, we execute a suspend workflow operation. Once we check the state of the instance, a resume +operation is executed. + +To start the workflow, you can run: + + + + +```sh +curl -X POST "localhost:8080/wfp/suspendresume?orderId=123" -H 'Content-Type: application/json' +``` + + + +In the application output you should see the workflow activities being executed. + +```bash +io.dapr.workflows.WorkflowContext : Starting Workflow: io.dapr.springboot.examples.wfp.suspendresume.SuspendResumeWorkflow +i.d.s.e.w.WorkflowPatternsRestController : Workflow instance 2de2b968-900a-4f5b-9092-b26aefbfc6b3 started +i.d.s.e.w.s.PerformTaskActivity : Starting Activity: io.dapr.springboot.examples.wfp.suspendresume.PerformTaskActivity +i.d.s.e.w.s.PerformTaskActivity : Running activity... +i.d.s.e.w.s.PerformTaskActivity : Completing activity... +io.dapr.workflows.WorkflowContext : Waiting for approval... + +``` + +You should see the Workflow ID that was created, in this example you don't need to remember this id, +as you can use the orderId to find the right instance. + + + + + +Let's suspend the workflow instance by sending the following request: + +```sh +curl -X POST "localhost:8080/wfp/suspendresume/suspend?orderId=123" -H 'Content-Type: application/json' +``` + + + + +You should see the output of the requests: + +```sh +SUSPENDED +``` + +Now, let's resume the workflow instance: + + + + +To send the event you can run: + +```sh +curl -X POST "localhost:8080/wfp/suspendresume/resume?orderId=123" -H 'Content-Type: application/json' +``` + + + +You should see the output of the requests: + +```sh +RUNNING +``` + +Now, let's send the event that the instance is waiting to validate that the workflow complete after +being suspended and resumed. + + + + +To send the event you can run: + +```sh +curl -X POST "localhost:8080/wfp/suspendresume/continue?orderId=123&decision=true" -H 'Content-Type: application/json' +``` + + + +The output of the request contains the output of the workflow based on the `decision` parameter that we sent. + +```bash +{"approved":true} +``` + +In the application output you should see, that the workflow instance completed correctly: + +```sh +i.d.s.e.w.WorkflowPatternsRestController : Workflow instance 2de2b968-900a-4f5b-9092-b26aefbfc6b3 continue +io.dapr.workflows.WorkflowContext : approval-event arrived +i.d.s.e.w.s.PerformTaskActivity : Starting Activity: io.dapr.springboot.examples.wfp.suspendresume.PerformTaskActivity +i.d.s.e.w.s.PerformTaskActivity : Running activity... +i.d.s.e.w.s.PerformTaskActivity : Completing activity... +``` ## Testing workflow executions Workflow execution can be tested using Testcontainers and you can find all the tests for the patterns covered in this -application [here](test/java/io/dapr/springboot/examples/wfp/TestWorkflowPatternsApplication.java). \ No newline at end of file +application [here](test/java/io/dapr/springboot/examples/wfp/TestWorkflowPatternsApplication.java). diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java index ddffdb018..ad64993e1 100644 --- a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java +++ b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java @@ -24,6 +24,7 @@ import io.dapr.springboot.examples.wfp.fanoutin.Result; import io.dapr.springboot.examples.wfp.remoteendpoint.Payload; import io.dapr.springboot.examples.wfp.remoteendpoint.RemoteEndpointWorkflow; +import io.dapr.springboot.examples.wfp.suspendresume.SuspendResumeWorkflow; import io.dapr.workflows.client.DaprWorkflowClient; import io.dapr.workflows.client.WorkflowInstanceStatus; import org.slf4j.Logger; @@ -153,4 +154,39 @@ public Payload remoteEndpoint(@RequestBody Payload payload) return workflowInstanceStatus.readOutputAs(Payload.class); } + @PostMapping("wfp/suspendresume") + public String suspendResume(@RequestParam("orderId") String orderId) { + String instanceId = daprWorkflowClient.scheduleNewWorkflow(SuspendResumeWorkflow.class); + logger.info("Workflow instance " + instanceId + " started"); + ordersToApprove.put(orderId, instanceId); + return instanceId; + } + + @PostMapping("wfp/suspendresume/suspend") + public String suspendResumeExecuteSuspend(@RequestParam("orderId") String orderId) { + String instanceId = ordersToApprove.get(orderId); + daprWorkflowClient.suspendWorkflow(instanceId, "testing suspend"); + WorkflowInstanceStatus instanceState = daprWorkflowClient.getInstanceState(instanceId, false); + return instanceState.getRuntimeStatus().name(); + } + + @PostMapping("wfp/suspendresume/resume") + public String suspendResumeExecuteResume(@RequestParam("orderId") String orderId) { + String instanceId = ordersToApprove.get(orderId); + daprWorkflowClient.resumeWorkflow(instanceId, "testing resume"); + WorkflowInstanceStatus instanceState = daprWorkflowClient.getInstanceState(instanceId, false); + return instanceState.getRuntimeStatus().name(); + } + + + @PostMapping("wfp/suspendresume/continue") + public Decision suspendResumeContinue(@RequestParam("orderId") String orderId, @RequestParam("decision") Boolean decision) + throws TimeoutException { + String instanceId = ordersToApprove.get(orderId); + logger.info("Workflow instance " + instanceId + " continue"); + daprWorkflowClient.raiseEvent(instanceId, "Approval", decision); + WorkflowInstanceStatus workflowInstanceStatus = daprWorkflowClient + .waitForInstanceCompletion(instanceId, null, true); + return workflowInstanceStatus.readOutputAs(Decision.class); + } } diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/PerformTaskActivity.java b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/PerformTaskActivity.java new file mode 100644 index 000000000..6a2ac1d9a --- /dev/null +++ b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/PerformTaskActivity.java @@ -0,0 +1,43 @@ +/* + * Copyright 2025 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.springboot.examples.wfp.suspendresume; + +import io.dapr.workflows.WorkflowActivity; +import io.dapr.workflows.WorkflowActivityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +@Component +public class PerformTaskActivity implements WorkflowActivity { + @Override + public Object run(WorkflowActivityContext ctx) { + Logger logger = LoggerFactory.getLogger(PerformTaskActivity.class); + logger.info("Starting Activity: " + ctx.getName()); + + logger.info("Running activity..."); + //Sleeping for 5 seconds to simulate long running operation + try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + logger.info("Completing activity..."); + + return "OK"; + } +} diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/SuspendResumeWorkflow.java b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/SuspendResumeWorkflow.java new file mode 100644 index 000000000..b9578a687 --- /dev/null +++ b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/SuspendResumeWorkflow.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 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.springboot.examples.wfp.suspendresume; + +import io.dapr.springboot.examples.wfp.externalevent.Decision; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; +import org.springframework.stereotype.Component; + +@Component +public class SuspendResumeWorkflow implements Workflow { + @Override + public WorkflowStub create() { + return ctx -> { + ctx.getLogger().info("Starting Workflow: " + ctx.getName()); + + ctx.callActivity(PerformTaskActivity.class.getName(), String.class).await(); + + ctx.getLogger().info("Waiting for approval..."); + Boolean approved = ctx.waitForExternalEvent("Approval", boolean.class).await(); + + ctx.getLogger().info("approval-event arrived"); + + ctx.callActivity(PerformTaskActivity.class.getName(), String.class).await(); + + ctx.complete(new Decision(approved)); + }; + } +} diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java index 625a621a6..6b2ed0501 100644 --- a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java +++ b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java @@ -13,10 +13,10 @@ package io.dapr.springboot.examples.wfp; -import io.dapr.client.DaprClient; import io.dapr.springboot.DaprAutoConfiguration; import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog; import io.dapr.springboot.examples.wfp.remoteendpoint.Payload; +import io.dapr.workflows.client.WorkflowRuntimeStatus; import io.github.microcks.testcontainers.MicrocksContainersEnsemble; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -32,15 +32,13 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; @SpringBootTest(classes = {TestWorkflowPatternsApplication.class, DaprTestContainersConfig.class, DaprAutoConfiguration.class, }, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) class WorkflowPatternsAppTests { - @Autowired - private DaprClient daprClient; - @Autowired private MicrocksContainersEnsemble ensemble; @@ -160,4 +158,47 @@ void testRemoteEndpoint() { .getServiceInvocationsCount("API Payload Processor", "1.0.0")); } + @Test + void testSuspendResume() { + + String instanceId = given() + .queryParam("orderId", "123") + .when() + .post("/wfp/suspendresume") + .then() + .statusCode(200).extract().asString(); + + assertNotNull(instanceId); + + // The workflow is waiting on an event, let's suspend the workflow + String state = given() + .queryParam("orderId", "123") + .when() + .post("/wfp/suspendresume/suspend") + .then() + .statusCode(200).extract().asString(); + + assertEquals(WorkflowRuntimeStatus.SUSPENDED.name(), state); + + // The let's resume the suspended workflow and check the state + state = given() + .queryParam("orderId", "123") + .when() + .post("/wfp/suspendresume/resume") + .then() + .statusCode(200).extract().asString(); + + assertEquals(WorkflowRuntimeStatus.RUNNING.name(), state); + + // Now complete the workflow by sending an event + given() + .queryParam("orderId", "123") + .queryParam("decision", false) + .when() + .post("/wfp/suspendresume/continue") + .then() + .statusCode(200).body("approved", equalTo(false)); + + } + }