Skip to content

Commit 5a73fa5

Browse files
restrict the asyncComplete test to v5 so it doesn't introduce the only weird v4-vs-v5-branching-inside-one-test in the entire test suite
1 parent 545a924 commit 5a73fa5

1 file changed

Lines changed: 92 additions & 125 deletions

File tree

src/integration-tests/WorkflowExecutor.test.ts

Lines changed: 92 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -169,142 +169,109 @@ describe("WorkflowExecutor", () => {
169169
expect(workflowStatus?.status).toBeTruthy();
170170
expect(workflowStatus?.tasks?.length).toBe(1);
171171
});
172-
test("Should run workflow with http task with asyncComplete true", async () => {
173-
const client = await clientPromise;
174-
const executor = new WorkflowExecutor(client);
175-
const workflowName = `jsSdkTest-wf_with_asyncComplete_http_task-${Date.now()}`;
176-
const taskName = `jsSdkTest-http_task_with_asyncComplete_true-${Date.now()}`;
177-
178-
// Both v4 and v5 target the same in-cluster httpbin service. (An earlier note
179-
// here claimed v4 couldn't reach httpbin-server and fell back to a public URL;
180-
// that was a misdiagnosis. The HTTP call to httpbin actually succeeds on v4 with
181-
// a 200 - the task simply never *surfaces* as IN_PROGRESS. See the version-specific
182-
// readiness check below for the real reason.)
183-
const asyncHttpUri = `${HTTPBIN_BASE_URL}/api/hello?name=test1`;
184-
185-
await executor.registerWorkflow(true, {
186-
name: workflowName,
187-
version: 1,
188-
ownerEmail: "developers@orkes.io",
189-
tasks: [
190-
httpTask(taskName, { uri: asyncHttpUri, method: "GET" }, true),
191-
],
192-
inputParameters: [],
193-
outputParameters: {},
194-
timeoutSeconds: 30,
195-
});
172+
// Gated to v5 only. An asyncComplete HTTP task settles differently depending on
173+
// whether HTTP is a registered in-server *system task*:
174+
// - v5 ships the api-orchestration module, so HTTP is a system task
175+
// (isSystemTask("HTTP") === true). An asyncComplete HTTP task genuinely
176+
// *settles* as IN_PROGRESS, awaiting external completion. That is a stable,
177+
// observable state we can assert on.
178+
// - v4 has no api-orchestration module, so HTTP is a plain external worker task
179+
// (isSystemTask("HTTP") === false). When the worker returns IN_PROGRESS with
180+
// callbackAfterSeconds = Integer.MAX_VALUE, OrkesWorkflowExecutor.updateTask
181+
// rewrites it to SCHEDULED and parks it ~68 years out. It therefore never
182+
// *settles* as IN_PROGRESS.
183+
//
184+
// We deliberately do NOT run this on v4. The old (yahoo.com) version of this test
185+
// only ever "passed" on v4 by catching the task in the transient IN_PROGRESS window
186+
// between the worker polling it (server sets IN_PROGRESS) and the worker returning
187+
// its result (server rewrites to SCHEDULED) - i.e. it was exploiting a race, not
188+
// checking the settled state of this task type on v4. Rather than assert two
189+
// different shapes in one test, we scope this to the version where IN_PROGRESS is
190+
// the real, settled outcome.
191+
describeForOrkesV5("asyncComplete HTTP task (v5 system-task semantics)", () => {
192+
test("Should run workflow with http task with asyncComplete true", async () => {
193+
const client = await clientPromise;
194+
const executor = new WorkflowExecutor(client);
195+
const workflowName = `jsSdkTest-wf_with_asyncComplete_http_task-${Date.now()}`;
196+
const taskName = `jsSdkTest-http_task_with_asyncComplete_true-${Date.now()}`;
197+
198+
const asyncHttpUri = `${HTTPBIN_BASE_URL}/api/hello?name=test1`;
199+
200+
await executor.registerWorkflow(true, {
201+
name: workflowName,
202+
version: 1,
203+
ownerEmail: "developers@orkes.io",
204+
tasks: [
205+
httpTask(taskName, { uri: asyncHttpUri, method: "GET" }, true),
206+
],
207+
inputParameters: [],
208+
outputParameters: {},
209+
timeoutSeconds: 30,
210+
});
196211

197-
const executionId = await executor.startWorkflow({
198-
name: workflowName,
199-
input: {},
200-
version: 1,
201-
});
212+
const executionId = await executor.startWorkflow({
213+
name: workflowName,
214+
input: {},
215+
version: 1,
216+
});
202217

203-
if (!executionId) {
204-
throw new Error("Execution ID is undefined");
205-
}
218+
if (!executionId) {
219+
throw new Error("Execution ID is undefined");
220+
}
206221

207-
// Log the id so a failed/incomplete run can be inspected in the Conductor UI.
208-
console.log(
209-
`[asyncComplete http] workflowId=${executionId} workflow=${workflowName} task=${taskName}`
210-
);
222+
// Log the id so a failed/incomplete run can be inspected in the Conductor UI.
223+
console.log(
224+
`[asyncComplete http] workflowId=${executionId} workflow=${workflowName} task=${taskName}`
225+
);
211226

212-
await waitForWorkflowStatus(executor, executionId, "RUNNING");
213-
214-
// Wait for the async HTTP task to finish executing and sit "open", awaiting an
215-
// external completion. CRUCIALLY, the *observable* shape of that open state
216-
// differs between server versions:
217-
//
218-
// - v5: the task reports status === "IN_PROGRESS".
219-
// - v4: the task reports status === "SCHEDULED", with callbackAfterSeconds ===
220-
// Integer.MAX_VALUE (2147483647, ~68 years). An HTTP task literally
221-
// cannot be made to show IN_PROGRESS on a v4 server.
222-
//
223-
// Why: when a worker returns an IN_PROGRESS result, OrkesWorkflowExecutor.updateTask
224-
// treats *system tasks* and *worker tasks* differently:
225-
// if (!systemTaskRegistry.isSystemTask(type) && result == IN_PROGRESS)
226-
// task.setStatus(SCHEDULED); // worker task -> re-queued / deferred
227-
// else
228-
// task.setStatus(result); // system task -> IN_PROGRESS preserved
229-
// For asyncComplete=true the HTTP worker returns IN_PROGRESS with
230-
// callbackAfterSeconds = Integer.MAX_VALUE, so a *worker-task* HTTP gets rewritten
231-
// to SCHEDULED and parked ~68 years out.
232-
//
233-
// The deciding factor is whether HTTP is a registered in-server system task:
234-
// - v5 ships the api-orchestration module, which registers HTTP as a
235-
// WorkflowSystemTask -> isSystemTask("HTTP") === true -> IN_PROGRESS sticks.
236-
// - v4 has no api-orchestration module, so HTTP is purely an external
237-
// orkes-workers task -> isSystemTask("HTTP") === false -> the worker's
238-
// IN_PROGRESS is rewritten to SCHEDULED.
239-
// Note: the updateTask branch itself is identical in v4 and v5; the behavioral
240-
// difference comes entirely from that system-task registration, not from a change
241-
// in the status-handling code.
242-
const isV5 = Number(process.env.ORKES_BACKEND_VERSION) >= 5;
243-
const INTEGER_MAX_VALUE = 2147483647; // Integer.MAX_VALUE; v4's "park forever" callbackAfterSeconds
244-
const taskReadyTimeout = 30000;
245-
const pollInterval = 3000;
246-
const taskReadyStart = Date.now();
247-
let taskStatus: string | undefined;
248-
let taskCallbackAfterSeconds: number | undefined;
249-
let taskPollCount: number | undefined;
250-
while (Date.now() - taskReadyStart < taskReadyTimeout) {
251-
const wf = await executor.getWorkflow(executionId, true);
252-
const task = wf?.tasks?.[0];
253-
taskStatus = task?.status;
254-
taskCallbackAfterSeconds = task?.callbackAfterSeconds;
255-
taskPollCount = task?.pollCount;
256-
// v5 surfaces IN_PROGRESS directly; on v4 the only observable "open" signal is
257-
// a SCHEDULED task that has been polled (executed) and parked indefinitely.
258-
const taskReady = isV5
259-
? taskStatus === "IN_PROGRESS"
260-
: taskStatus === "SCHEDULED" &&
261-
(taskPollCount ?? 0) > 0 &&
262-
taskCallbackAfterSeconds === INTEGER_MAX_VALUE;
263-
if (taskReady) break;
264-
if (taskStatus === "FAILED" || taskStatus === "COMPLETED") {
265-
throw new Error(
266-
`Task ended in unexpected state: ${taskStatus} ` +
267-
`(workflowId=${executionId}, workflow=${workflowName}, task=${taskName}` +
268-
(task?.reasonForIncompletion
269-
? `, reason=${task.reasonForIncompletion}`
270-
: "") +
271-
")"
272-
);
227+
await waitForWorkflowStatus(executor, executionId, "RUNNING");
228+
229+
// Wait for the async HTTP task to finish executing and settle as IN_PROGRESS,
230+
// awaiting external completion (see the block comment above for why this is the
231+
// stable, settled state on v5).
232+
const taskReadyTimeout = 30000;
233+
const pollInterval = 3000;
234+
const taskReadyStart = Date.now();
235+
let taskStatus: string | undefined;
236+
while (Date.now() - taskReadyStart < taskReadyTimeout) {
237+
const wf = await executor.getWorkflow(executionId, true);
238+
const task = wf?.tasks?.[0];
239+
taskStatus = task?.status;
240+
if (taskStatus === "IN_PROGRESS") break;
241+
if (taskStatus === "FAILED" || taskStatus === "COMPLETED") {
242+
throw new Error(
243+
`Task ended in unexpected state: ${taskStatus} ` +
244+
`(workflowId=${executionId}, workflow=${workflowName}, task=${taskName}` +
245+
(task?.reasonForIncompletion
246+
? `, reason=${task.reasonForIncompletion}`
247+
: "") +
248+
")"
249+
);
250+
}
251+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
273252
}
274-
await new Promise((resolve) => setTimeout(resolve, pollInterval));
275-
}
276253

277-
// Assert the version-specific "open" shape explicitly, so the divergence stays
278-
// documented and this fails loudly if v4 ever starts reporting IN_PROGRESS (e.g.
279-
// if HTTP becomes a registered system task there too).
280-
if (isV5) {
281-
console.log("V5: expecting HTTP task to be IN_PROGRESS");
282254
expect(taskStatus).toEqual("IN_PROGRESS");
283-
} else {
284-
console.log("V4: expecting HTTP task to be SCHEDULED");
285-
expect(taskStatus).toEqual("SCHEDULED");
286-
expect(taskPollCount ?? 0).toBeGreaterThan(0);
287-
expect(taskCallbackAfterSeconds).toEqual(INTEGER_MAX_VALUE);
288-
}
289255

290-
const taskClient = new TaskClient(client);
291-
await taskClient.updateTaskResult(executionId, taskName, "COMPLETED", {
292-
hello: "From manuall api call updating task result",
293-
});
256+
const taskClient = new TaskClient(client);
257+
await taskClient.updateTaskResult(executionId, taskName, "COMPLETED", {
258+
hello: "From manuall api call updating task result",
259+
});
294260

295-
const workflowStatusAfter = await waitForWorkflowStatus(
296-
executor,
297-
executionId,
298-
"COMPLETED",
299-
30000,
300-
5000
301-
);
261+
const workflowStatusAfter = await waitForWorkflowStatus(
262+
executor,
263+
executionId,
264+
"COMPLETED",
265+
30000,
266+
5000
267+
);
302268

303-
expect(workflowStatusAfter.tasks?.[0]?.status).toEqual("COMPLETED");
269+
expect(workflowStatusAfter.tasks?.[0]?.status).toEqual("COMPLETED");
304270

305-
await cleanupWorkflowsAndTasks(metadataClient, {
306-
workflows: [{ name: workflowName, version: 1 }],
307-
tasks: [taskName],
271+
await cleanupWorkflowsAndTasks(metadataClient, {
272+
workflows: [{ name: workflowName, version: 1 }],
273+
tasks: [taskName],
274+
});
308275
});
309276
});
310277

0 commit comments

Comments
 (0)