From 2886e7c8947476607f3c77e043778f604f0e2af0 Mon Sep 17 00:00:00 2001 From: Andrzej Ludwikowski Date: Mon, 21 Aug 2023 09:22:43 +0300 Subject: [PATCH] fix: workflows, exception when running a step with a null workflow state (#1767) --- .../javasdk/impl/workflow/WorkflowImpl.scala | 6 +- .../SpringWorkflowIntegrationTest.java | 20 ++++++ .../WorkflowWithoutInitialState.java | 65 +++++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 sdk/java-sdk-spring/src/it/java/com/example/wiring/workflowentities/WorkflowWithoutInitialState.java diff --git a/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/workflow/WorkflowImpl.scala b/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/workflow/WorkflowImpl.scala index 840b570d44..68430f42a2 100644 --- a/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/workflow/WorkflowImpl.scala +++ b/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/workflow/WorkflowImpl.scala @@ -306,8 +306,10 @@ final class WorkflowImpl(system: ActorSystem, val services: Map[String, Workflow val timerScheduler = new TimerSchedulerImpl(service.messageCodec, system) val stepResponse = try { - val decoded = service.messageCodec.decodeMessage(executeStep.userState.get) - router._internalSetInitState(decoded, false) // here we know that workflow is still running + executeStep.userState.foreach { state => + val decoded = service.messageCodec.decodeMessage(state) + router._internalSetInitState(decoded, finished = false) // here we know that workflow is still running + } router._internalHandleStep( executeStep.commandId, executeStep.input, diff --git a/sdk/java-sdk-spring/src/it/java/com/example/wiring/workflowentities/SpringWorkflowIntegrationTest.java b/sdk/java-sdk-spring/src/it/java/com/example/wiring/workflowentities/SpringWorkflowIntegrationTest.java index 3814408d63..44ed9f1c05 100644 --- a/sdk/java-sdk-spring/src/it/java/com/example/wiring/workflowentities/SpringWorkflowIntegrationTest.java +++ b/sdk/java-sdk-spring/src/it/java/com/example/wiring/workflowentities/SpringWorkflowIntegrationTest.java @@ -459,6 +459,26 @@ public void shouldNotUpdateWorkflowStateAfterEndTransition() { assertThat(execute(componentClient.forWorkflow(workflowId).call(DummyWorkflow::get))).isEqualTo(10); } + @Test + public void shouldRunWorkflowStepWithoutInitialState() { + //given + var workflowId = randomId(); + + //when + String response = execute(componentClient.forWorkflow(workflowId) + .call(WorkflowWithoutInitialState::start)); + + assertThat(response).contains("ok"); + + //then + await() + .atMost(10, TimeUnit.of(SECONDS)) + .untilAsserted(() -> { + var state = execute(componentClient.forWorkflow(workflowId).call(WorkflowWithoutInitialState::get)); + assertThat(state).contains("success"); + }); + } + private T execute(DeferredCall deferredCall) { return execute(deferredCall, timeout); } diff --git a/sdk/java-sdk-spring/src/it/java/com/example/wiring/workflowentities/WorkflowWithoutInitialState.java b/sdk/java-sdk-spring/src/it/java/com/example/wiring/workflowentities/WorkflowWithoutInitialState.java new file mode 100644 index 0000000000..3bd29fbf6f --- /dev/null +++ b/sdk/java-sdk-spring/src/it/java/com/example/wiring/workflowentities/WorkflowWithoutInitialState.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021 Lightbend Inc. + * + * 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 com.example.wiring.workflowentities; + +import kalix.javasdk.annotations.Id; +import kalix.javasdk.annotations.TypeId; +import kalix.javasdk.client.ComponentClient; +import kalix.javasdk.workflow.Workflow; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.util.concurrent.CompletableFuture; + +@Id("id") +@TypeId("workflow-without-initial-state") +@RequestMapping("/workflow-without-initial-state/{id}") +public class WorkflowWithoutInitialState extends Workflow { + + + private ComponentClient componentClient; + + public WorkflowWithoutInitialState(ComponentClient componentClient) { + this.componentClient = componentClient; + } + + @Override + public WorkflowDef definition() { + var test = + step("test") + .asyncCall(() -> CompletableFuture.completedFuture("ok")) + .andThen(String.class, result -> effects().updateState("success").end()); + + return workflow() + .addStep(test); + } + + @PutMapping() + public Effect start() { + return effects().transitionTo("test").thenReply("ok"); + } + + @GetMapping() + public Effect get() { + if (currentState() == null) { + return effects().reply("empty"); + } else { + return effects().reply(currentState()); + } + } +}