From 758c8d8194f42e1393bcfbfbef83fd43acb63309 Mon Sep 17 00:00:00 2001 From: Miguel Prieto Date: Wed, 23 Sep 2026 11:31:41 -0300 Subject: [PATCH] fix(client): startWorkflow must not return a null workflow id A successful response carrying no workflow id came back as a null return value, so a caller saw a successful start with no id and no way to find the execution. It now raises instead. Scoped to startWorkflow; deserialization is unchanged for every other endpoint. Co-Authored-By: Claude Opus 5 (1M context) --- .../conductor/client/http/WorkflowClient.java | 11 ++- .../http/StartWorkflowEmptyResponseTest.java | 85 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 conductor-client/src/test/java/com/netflix/conductor/client/http/StartWorkflowEmptyResponseTest.java diff --git a/conductor-client/src/main/java/com/netflix/conductor/client/http/WorkflowClient.java b/conductor-client/src/main/java/com/netflix/conductor/client/http/WorkflowClient.java index 4b82a2972..56e4d6cf4 100644 --- a/conductor-client/src/main/java/com/netflix/conductor/client/http/WorkflowClient.java +++ b/conductor-client/src/main/java/com/netflix/conductor/client/http/WorkflowClient.java @@ -210,10 +210,19 @@ public String startWorkflow(StartWorkflowRequest startWorkflowRequest) { .build(); ConductorClientResponse resp = client.execute(request, STRING_TYPE); + String workflowId = resp.getData(); + if (workflowId == null || workflowId.isBlank()) { + // Error. Indeterminate outcome. + throw new ConductorClientException( + "Error when starting workflow. No workflow id was returned", + resp.getStatusCode(), + resp.getHeaders(), + null); + } eventDispatcher .publish(new WorkflowStartedEvent(startWorkflowRequest.getName(), startWorkflowRequest.getVersion())); - return resp.getData(); + return workflowId; } public void checkAndUploadToExternalStorage(StartWorkflowRequest startWorkflowRequest) { diff --git a/conductor-client/src/test/java/com/netflix/conductor/client/http/StartWorkflowEmptyResponseTest.java b/conductor-client/src/test/java/com/netflix/conductor/client/http/StartWorkflowEmptyResponseTest.java new file mode 100644 index 000000000..1fae4303f --- /dev/null +++ b/conductor-client/src/test/java/com/netflix/conductor/client/http/StartWorkflowEmptyResponseTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2026 Conductor 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 com.netflix.conductor.client.http; + +import java.io.IOException; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.netflix.conductor.client.exception.ConductorClientException; +import com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * A successful response carrying no workflow id used to come back as a null return value, so a + * caller saw a "successful" start with no id and no error. It must raise instead. + */ +class StartWorkflowEmptyResponseTest { + + private MockWebServer server; + private WorkflowClient workflowClient; + + @BeforeEach + void setUp() throws IOException { + server = new MockWebServer(); + server.start(); + workflowClient = new WorkflowClient(new ConductorClient(server.url("/api").toString())); + } + + @AfterEach + void tearDown() throws IOException { + server.shutdown(); + Thread.interrupted(); + } + + private StartWorkflowRequest request() { + StartWorkflowRequest request = new StartWorkflowRequest(); + request.setName("greetings"); + request.setVersion(1); + return request; + } + + @Test + void emptyTwoHundredBodyRaisesInsteadOfReturningNull() { + server.enqueue(new MockResponse().setResponseCode(200).setBody("")); + + ConductorClientException e = + assertThrows( + ConductorClientException.class, + () -> workflowClient.startWorkflow(request())); + + assertEquals(200, e.getStatus()); + assertTrue( + e.getMessage().contains("No workflow id was returned"), + "message should say what went wrong, was: " + e.getMessage()); + } + + @Test + void aBodyIsStillReturned() { + server.enqueue( + new MockResponse() + .setResponseCode(200) + .setBody("3919784b-7691-11f1-a292-00163e983f22")); + + assertEquals( + "3919784b-7691-11f1-a292-00163e983f22", workflowClient.startWorkflow(request())); + } +}