Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ export class RuntimeOrchestrationContext extends OrchestrationContext {
}

waitForExternalEvent<T>(name: string): Task<T> {
if (!name) {
throw new Error("waitForExternalEvent: 'name' is required and cannot be empty.");
}

// Check to see if this event has already been received, in which case we
// can return it immediately. Otherwise, record out intent to receive an
// event with the given name so that we can resume the generator when it
Expand Down Expand Up @@ -439,6 +443,14 @@ export class RuntimeOrchestrationContext extends OrchestrationContext {
* Sends an event to another orchestration instance.
*/
sendEvent(instanceId: string, eventName: string, eventData?: any): void {
if (!instanceId) {
throw new Error("sendEvent: 'instanceId' is required and cannot be empty.");
}

if (!eventName) {
throw new Error("sendEvent: 'eventName' is required and cannot be empty.");
}

const id = this.nextSequenceNumber();
const encodedData = eventData !== undefined ? JSON.stringify(eventData) : undefined;
const action = ph.newSendEventAction(id, instanceId, eventName, encodedData);
Expand Down
52 changes: 52 additions & 0 deletions packages/durabletask-js/test/orchestration_context_methods.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,58 @@ describe("OrchestrationContext.sendEvent", () => {
// No data should be set (or empty)
expect(sendEvent?.getData()?.getValue() ?? "").toEqual("");
});

it("should fail the orchestration when eventName is empty", async () => {
const orchestrator: TOrchestrator = async (ctx: OrchestrationContext) => {
ctx.sendEvent("target-instance-id", "", { data: "value" });
return "done";
};

const registry = new Registry();
const name = registry.addOrchestrator(orchestrator);
const newEvents = [
newOrchestratorStartedEvent(),
newExecutionStartedEvent(name, TEST_INSTANCE_ID),
];
const executor = new OrchestrationExecutor(registry);
const result = await executor.execute(TEST_INSTANCE_ID, [], newEvents);

// The orchestration should fail because sendEvent throws on empty eventName
expect(result.actions.length).toEqual(1);
const completeAction = result.actions[0].getCompleteorchestration();
expect(completeAction?.getOrchestrationstatus()).toEqual(
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
);
expect(completeAction?.getFailuredetails()?.getErrormessage()).toContain(
"'eventName' is required and cannot be empty",
);
});

it("should fail the orchestration when instanceId is empty", async () => {
const orchestrator: TOrchestrator = async (ctx: OrchestrationContext) => {
ctx.sendEvent("", "my-event", { data: "value" });
return "done";
};

const registry = new Registry();
const name = registry.addOrchestrator(orchestrator);
const newEvents = [
newOrchestratorStartedEvent(),
newExecutionStartedEvent(name, TEST_INSTANCE_ID),
];
const executor = new OrchestrationExecutor(registry);
const result = await executor.execute(TEST_INSTANCE_ID, [], newEvents);

// The orchestration should fail because sendEvent throws on empty instanceId
expect(result.actions.length).toEqual(1);
const completeAction = result.actions[0].getCompleteorchestration();
expect(completeAction?.getOrchestrationstatus()).toEqual(
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
);
expect(completeAction?.getFailuredetails()?.getErrormessage()).toContain(
"'instanceId' is required and cannot be empty",
);
});
});

describe("OrchestrationContext.newGuid", () => {
Expand Down
24 changes: 24 additions & 0 deletions packages/durabletask-js/test/orchestration_executor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,30 @@ describe("Orchestration Executor", () => {
}
});

it("should fail the orchestration when waitForExternalEvent is called with an empty name", async () => {
const orchestrator: TOrchestrator = async function* (ctx: OrchestrationContext, _: any): any {
const res = yield ctx.waitForExternalEvent("");
return res;
};

const registry = new Registry();
const orchestratorName = registry.addOrchestrator(orchestrator);

const newEvents = [newOrchestratorStartedEvent(), newExecutionStartedEvent(orchestratorName, TEST_INSTANCE_ID)];

const executor = new OrchestrationExecutor(registry, testLogger);
const result = await executor.execute(TEST_INSTANCE_ID, [], newEvents);

// The orchestration should fail because waitForExternalEvent throws on empty name
const completeAction = getAndValidateSingleCompleteOrchestrationAction(result);
expect(completeAction?.getOrchestrationstatus()).toEqual(
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
);
expect(completeAction?.getFailuredetails()?.getErrormessage()).toContain(
"'name' is required and cannot be empty",
);
});

it("should be able to continue-as-new", async () => {
for (const saveEvent of [true, false]) {
const orchestrator: TOrchestrator = async function* (ctx: OrchestrationContext, input: number): any {
Expand Down
Loading