diff --git a/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java b/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java index 8fdb3f7a..9c70db02 100644 --- a/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java +++ b/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java @@ -882,7 +882,12 @@ private void processEvent(HistoryEvent e) { // Try getting the default orchestrator factory = TaskOrchestrationExecutor.this.orchestrationFactories.get("*"); } - // TODO: Throw if the factory is null (orchestration by that name doesn't exist) + if (factory == null) { + throw new IllegalStateException(String.format( + "No orchestration factory registered for orchestration type '%s'. This usually means that a worker that doesn't support this orchestration type is connected to this task hub. Make sure the worker has a registered orchestration for '%s'.", + name, + name)); + } TaskOrchestration orchestrator = factory.create(); orchestrator.run(this); break; diff --git a/client/src/test/java/com/microsoft/durabletask/TaskOrchestrationExecutorTest.java b/client/src/test/java/com/microsoft/durabletask/TaskOrchestrationExecutorTest.java new file mode 100644 index 00000000..7d62df2b --- /dev/null +++ b/client/src/test/java/com/microsoft/durabletask/TaskOrchestrationExecutorTest.java @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.durabletask; + +import com.google.protobuf.StringValue; +import com.google.protobuf.Timestamp; +import com.microsoft.durabletask.implementation.protobuf.OrchestratorService.*; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.*; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for TaskOrchestrationExecutor. + */ +public class TaskOrchestrationExecutorTest { + + private static final Logger logger = Logger.getLogger(TaskOrchestrationExecutorTest.class.getName()); + + @Test + void execute_unregisteredOrchestrationType_failsWithDescriptiveMessage() { + // Arrange: create executor with no registered orchestrations + HashMap factories = new HashMap<>(); + TaskOrchestrationExecutor executor = new TaskOrchestrationExecutor( + factories, + new JacksonDataConverter(), + Duration.ofDays(3), + logger, + null); + + String unknownName = "NonExistentOrchestration"; + + // Build history events simulating an orchestration start + HistoryEvent orchestratorStarted = HistoryEvent.newBuilder() + .setEventId(-1) + .setTimestamp(Timestamp.getDefaultInstance()) + .setOrchestratorStarted(OrchestratorStartedEvent.getDefaultInstance()) + .build(); + + HistoryEvent executionStarted = HistoryEvent.newBuilder() + .setEventId(-1) + .setTimestamp(Timestamp.getDefaultInstance()) + .setExecutionStarted(ExecutionStartedEvent.newBuilder() + .setName(unknownName) + .setVersion(StringValue.of("")) + .setInput(StringValue.of("\"test-input\"")) + .setOrchestrationInstance(OrchestrationInstance.newBuilder() + .setInstanceId("test-instance-id") + .build()) + .build()) + .build(); + + HistoryEvent orchestratorCompleted = HistoryEvent.newBuilder() + .setEventId(-1) + .setTimestamp(Timestamp.getDefaultInstance()) + .setOrchestratorCompleted(OrchestratorCompletedEvent.getDefaultInstance()) + .build(); + + List pastEvents = Arrays.asList(orchestratorStarted, executionStarted, orchestratorCompleted); + List newEvents = Collections.emptyList(); + + // Act + TaskOrchestratorResult result = executor.execute(pastEvents, newEvents); + + // Assert: the result should contain a CompleteOrchestrationAction with FAILED status + // and a failure message mentioning the unknown orchestration name + OrchestratorAction action = result.getActions().iterator().next(); + assertTrue(action.hasCompleteOrchestration(), "Expected a CompleteOrchestrationAction"); + + CompleteOrchestrationAction completeAction = action.getCompleteOrchestration(); + assertEquals(OrchestrationStatus.ORCHESTRATION_STATUS_FAILED, completeAction.getOrchestrationStatus()); + assertTrue(completeAction.hasFailureDetails(), "Expected failure details"); + + TaskFailureDetails failureDetails = completeAction.getFailureDetails(); + assertEquals("java.lang.IllegalStateException", failureDetails.getErrorType()); + assertTrue(failureDetails.getErrorMessage().contains(unknownName), + "Error message should contain the orchestration name: " + failureDetails.getErrorMessage()); + assertTrue(failureDetails.getErrorMessage().contains("worker"), + "Error message should mention workers: " + failureDetails.getErrorMessage()); + } +}