Skip to content

Commit 4ba1a8f

Browse files
committed
test: make taskId 0 tests meaningful by injecting tasks that validate the regression fix
1 parent 71e84bf commit 4ba1a8f

1 file changed

Lines changed: 36 additions & 14 deletions

File tree

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

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { ActivityContext } from "../src/task/context/activity-context";
2828
import { CompletableTask } from "../src/task/completable-task";
2929
import { Task } from "../src/task/task";
3030
import { getName, whenAll, whenAny } from "../src/task";
31+
import { RuntimeOrchestrationContext } from "../src/worker/runtime-orchestration-context";
3132

3233
// Use NoOpLogger to suppress log output during tests
3334
const testLogger = new NoOpLogger();
@@ -271,36 +272,55 @@ describe("Orchestration Executor", () => {
271272
expect(result.actions.length).toEqual(0);
272273
});
273274

274-
it("should handle a task completion event with taskScheduledId 0 without error", async () => {
275+
it("should handle a task completion event with taskScheduledId 0 by looking up the task (not skipping due to falsy 0)", async () => {
276+
// This test validates the fix for issue #148: the old code used `if (taskId)` which
277+
// treated taskId === 0 as falsy and skipped the lookup entirely. The fix uses
278+
// `if (taskId !== undefined)` so that 0 is properly looked up.
275279
const dummyActivity = async (_: ActivityContext) => {
276280
// do nothing
277281
};
282+
const injectedTask = new CompletableTask<string>();
283+
284+
// Use an orchestrator that injects a CompletableTask at _pendingTasks[0]
285+
// to simulate a task with taskScheduledId = 0
278286
const orchestrator: TOrchestrator = async function* (ctx: OrchestrationContext, _: any): any {
287+
const runtimeCtx = ctx as unknown as RuntimeOrchestrationContext;
288+
runtimeCtx._pendingTasks[0] = injectedTask;
279289
const result = yield ctx.callActivity(dummyActivity);
280290
return result;
281291
};
282292
const registry = new Registry();
283293
const name = registry.addOrchestrator(orchestrator);
294+
registry.addActivity(dummyActivity);
284295
const oldEvents = [
285296
newOrchestratorStartedEvent(),
286297
newExecutionStartedEvent(name, TEST_INSTANCE_ID, undefined),
287298
newTaskScheduledEvent(1, dummyActivity.name),
288299
];
289-
// Send a completion event with taskScheduledId 0 — should not be silently skipped
290-
const newEvents = [newTaskCompletedEvent(0, JSON.stringify("result"))];
300+
// Send completion for taskId 0 — with the fix, this completes the injected task
301+
const newEvents = [newTaskCompletedEvent(0, JSON.stringify("result-for-zero"))];
291302
const executor = new OrchestrationExecutor(registry, testLogger);
292303

293-
// Should not throw — taskId 0 is properly handled (lookup attempted, no match found)
294-
const result = await executor.execute(TEST_INSTANCE_ID, oldEvents, newEvents);
295-
// Orchestration should still be waiting (task at id 1 was not completed)
296-
expect(result.actions.length).toEqual(0);
304+
await executor.execute(TEST_INSTANCE_ID, oldEvents, newEvents);
305+
306+
// With the fix (taskId !== undefined): the lookup at _pendingTasks[0] finds the injected
307+
// task and completes it. With the old code (if (taskId)): 0 is falsy, the lookup is
308+
// skipped, and the task is never completed.
309+
expect(injectedTask.isComplete).toBe(true);
310+
expect(injectedTask.getResult()).toEqual("result-for-zero");
297311
});
298312

299-
it("should handle a sub-orchestration completion event with taskScheduledId 0 without error", async () => {
313+
it("should handle a sub-orchestration completion event with taskScheduledId 0 by looking up the task", async () => {
314+
// Same regression test as above but for handleSubOrchestrationCompleted
300315
const subOrchestrator = async (_: OrchestrationContext) => {
301316
// do nothing
302317
};
318+
const injectedTask = new CompletableTask<string>();
319+
303320
const orchestrator: TOrchestrator = async function* (ctx: OrchestrationContext, _: any): any {
321+
const runtimeCtx = ctx as unknown as RuntimeOrchestrationContext;
322+
// Inject a CompletableTask at ID 0
323+
runtimeCtx._pendingTasks[0] = injectedTask;
304324
const res = yield ctx.callSubOrchestrator(subOrchestrator);
305325
return res;
306326
};
@@ -312,14 +332,16 @@ describe("Orchestration Executor", () => {
312332
newExecutionStartedEvent(orchestratorName, TEST_INSTANCE_ID, undefined),
313333
newSubOrchestrationCreatedEvent(1, subOrchestratorName, "sub-orch-123"),
314334
];
315-
// Send a completion event with taskScheduledId 0 — should not be silently skipped
316-
const newEvents = [newSubOrchestrationCompletedEvent(0, JSON.stringify("sub-result"))];
335+
// Send completion for taskId 0
336+
const newEvents = [newSubOrchestrationCompletedEvent(0, JSON.stringify("sub-result-zero"))];
317337
const executor = new OrchestrationExecutor(registry, testLogger);
318338

319-
// Should not throw — taskId 0 is properly handled
320-
const result = await executor.execute(TEST_INSTANCE_ID, oldEvents, newEvents);
321-
// Orchestration should still be waiting (sub-orch task at id 1 was not completed)
322-
expect(result.actions.length).toEqual(0);
339+
await executor.execute(TEST_INSTANCE_ID, oldEvents, newEvents);
340+
341+
// With the fix: the lookup at _pendingTasks[0] finds the injected task and completes it.
342+
// With the old code: 0 is falsy, lookup is skipped, task never completed.
343+
expect(injectedTask.isComplete).toBe(true);
344+
expect(injectedTask.getResult()).toEqual("sub-result-zero");
323345
});
324346

325347
it("should test the non-determinism detection logic when callTimer is expected but some other method (callActivity) is called instead", async () => {

0 commit comments

Comments
 (0)