Skip to content

Commit 5667569

Browse files
authored
Add input validation for empty event names in waitForExternalEvent() and sendEvent() (#276)
1 parent b1f039a commit 5667569

3 files changed

Lines changed: 88 additions & 0 deletions

File tree

packages/durabletask-js/src/worker/runtime-orchestration-context.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@ export class RuntimeOrchestrationContext extends OrchestrationContext {
371371
}
372372

373373
waitForExternalEvent<T>(name: string): Task<T> {
374+
if (!name) {
375+
throw new Error("waitForExternalEvent: 'name' is required and cannot be empty.");
376+
}
377+
374378
// Check to see if this event has already been received, in which case we
375379
// can return it immediately. Otherwise, record out intent to receive an
376380
// event with the given name so that we can resume the generator when it
@@ -450,6 +454,14 @@ export class RuntimeOrchestrationContext extends OrchestrationContext {
450454
* Sends an event to another orchestration instance.
451455
*/
452456
sendEvent(instanceId: string, eventName: string, eventData?: any): void {
457+
if (!instanceId) {
458+
throw new Error("sendEvent: 'instanceId' is required and cannot be empty.");
459+
}
460+
461+
if (!eventName) {
462+
throw new Error("sendEvent: 'eventName' is required and cannot be empty.");
463+
}
464+
453465
const id = this.nextSequenceNumber();
454466
const encodedData = eventData !== undefined ? JSON.stringify(eventData) : undefined;
455467
const action = ph.newSendEventAction(id, instanceId, eventName, encodedData);

packages/durabletask-js/test/orchestration_context_methods.spec.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,58 @@ describe("OrchestrationContext.sendEvent", () => {
312312
// No data should be set (or empty)
313313
expect(sendEvent?.getData()?.getValue() ?? "").toEqual("");
314314
});
315+
316+
it("should fail the orchestration when eventName is empty", async () => {
317+
const orchestrator: TOrchestrator = async (ctx: OrchestrationContext) => {
318+
ctx.sendEvent("target-instance-id", "", { data: "value" });
319+
return "done";
320+
};
321+
322+
const registry = new Registry();
323+
const name = registry.addOrchestrator(orchestrator);
324+
const newEvents = [
325+
newOrchestratorStartedEvent(),
326+
newExecutionStartedEvent(name, TEST_INSTANCE_ID),
327+
];
328+
const executor = new OrchestrationExecutor(registry);
329+
const result = await executor.execute(TEST_INSTANCE_ID, [], newEvents);
330+
331+
// The orchestration should fail because sendEvent throws on empty eventName
332+
expect(result.actions.length).toEqual(1);
333+
const completeAction = result.actions[0].getCompleteorchestration();
334+
expect(completeAction?.getOrchestrationstatus()).toEqual(
335+
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
336+
);
337+
expect(completeAction?.getFailuredetails()?.getErrormessage()).toContain(
338+
"'eventName' is required and cannot be empty",
339+
);
340+
});
341+
342+
it("should fail the orchestration when instanceId is empty", async () => {
343+
const orchestrator: TOrchestrator = async (ctx: OrchestrationContext) => {
344+
ctx.sendEvent("", "my-event", { data: "value" });
345+
return "done";
346+
};
347+
348+
const registry = new Registry();
349+
const name = registry.addOrchestrator(orchestrator);
350+
const newEvents = [
351+
newOrchestratorStartedEvent(),
352+
newExecutionStartedEvent(name, TEST_INSTANCE_ID),
353+
];
354+
const executor = new OrchestrationExecutor(registry);
355+
const result = await executor.execute(TEST_INSTANCE_ID, [], newEvents);
356+
357+
// The orchestration should fail because sendEvent throws on empty instanceId
358+
expect(result.actions.length).toEqual(1);
359+
const completeAction = result.actions[0].getCompleteorchestration();
360+
expect(completeAction?.getOrchestrationstatus()).toEqual(
361+
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
362+
);
363+
expect(completeAction?.getFailuredetails()?.getErrormessage()).toContain(
364+
"'instanceId' is required and cannot be empty",
365+
);
366+
});
315367
});
316368

317369
describe("OrchestrationContext.newGuid", () => {

packages/durabletask-js/test/orchestration_executor.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,30 @@ describe("Orchestration Executor", () => {
876876
}
877877
});
878878

879+
it("should fail the orchestration when waitForExternalEvent is called with an empty name", async () => {
880+
const orchestrator: TOrchestrator = async function* (ctx: OrchestrationContext, _: any): any {
881+
const res = yield ctx.waitForExternalEvent("");
882+
return res;
883+
};
884+
885+
const registry = new Registry();
886+
const orchestratorName = registry.addOrchestrator(orchestrator);
887+
888+
const newEvents = [newOrchestratorStartedEvent(), newExecutionStartedEvent(orchestratorName, TEST_INSTANCE_ID)];
889+
890+
const executor = new OrchestrationExecutor(registry, testLogger);
891+
const result = await executor.execute(TEST_INSTANCE_ID, [], newEvents);
892+
893+
// The orchestration should fail because waitForExternalEvent throws on empty name
894+
const completeAction = getAndValidateSingleCompleteOrchestrationAction(result);
895+
expect(completeAction?.getOrchestrationstatus()).toEqual(
896+
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
897+
);
898+
expect(completeAction?.getFailuredetails()?.getErrormessage()).toContain(
899+
"'name' is required and cannot be empty",
900+
);
901+
});
902+
879903
it("should be able to continue-as-new", async () => {
880904
for (const saveEvent of [true, false]) {
881905
const orchestrator: TOrchestrator = async function* (ctx: OrchestrationContext, input: number): any {

0 commit comments

Comments
 (0)