Skip to content

Commit ac6260e

Browse files
anthonytingParidelPooya
authored andcommitted
feat(testing-sdk): support running cloud tests asynchronously (#92)
*Issue #, if available:* DAR-SDK-305 *Description of changes:* Adding support for running cloud tests with `InvocationType.Event` (async invoke). The implementation uses a HistoryPoller class which will poll the execution history to get all events. Customers will be able to specify if they want to use async or sync executions using the `invocationType` parameter. By default, the runner will use sync executions only and `waitFor` will throw an error unless they use `InvocationType.Event`. Additionally: - Enabling wait-for-callback.test.ts to run on both cloud and local runners - Adjust integration-test.js script to allow running specific tests TODO: - Stop the execution and polling if the test fails. Currently it will run forever if the test fails. - Add a test that has many events (this will take time to run since it needs to generate a lot of events -> maybe run this only on push?) By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 61e111b commit ac6260e

File tree

14 files changed

+1947
-549
lines changed

14 files changed

+1947
-549
lines changed

.github/workflows/scripts/integration-test/integration-test.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ class IntegrationTestRunner {
219219
}
220220

221221
// Run Jest integration tests
222-
async runJestTests() {
222+
async runJestTests(/** @type {string | undefined} */ testPattern) {
223223
log.info("Running Jest integration tests...");
224224

225225
const examplesDir = CONFIG.EXAMPLES_PACKAGE_PATH;
@@ -235,7 +235,14 @@ class IntegrationTestRunner {
235235
console.log(JSON.stringify(this.getFunctionNameMap(), null, 2));
236236
log.info(`Lambda Endpoint: ${CONFIG.LAMBDA_ENDPOINT}`);
237237

238-
this.execCommand("npm run test:integration", {
238+
// Build test command with optional pattern
239+
let testCommand = "npm run test:integration";
240+
if (testPattern) {
241+
testCommand += ` -- ${testPattern}`;
242+
log.info(`Running tests with pattern: ${testPattern}`);
243+
}
244+
245+
this.execCommand(testCommand, {
239246
cwd: examplesDir,
240247
env,
241248
});
@@ -273,12 +280,14 @@ class IntegrationTestRunner {
273280
* @param {boolean} [options.deployOnly]
274281
* @param {boolean} [options.testOnly]
275282
* @param {boolean} [options.cleanupOnly]
283+
* @param {string} [options.testPattern]
276284
*/
277285
async run(options = {}) {
278286
const {
279287
deployOnly = false,
280288
testOnly = false,
281289
cleanupOnly = false,
290+
testPattern,
282291
} = options;
283292

284293
log.info("Starting integration test...");
@@ -297,7 +306,7 @@ class IntegrationTestRunner {
297306
}
298307

299308
if (!deployOnly) {
300-
await this.runJestTests();
309+
await this.runJestTests(testPattern);
301310
}
302311

303312
log.success("Integration test completed successfully!");
@@ -312,16 +321,23 @@ class IntegrationTestRunner {
312321

313322
// CLI interface
314323
function showUsage() {
315-
console.log("Usage: node integration-test.js [OPTIONS]");
324+
console.log("Usage: node integration-test.js [OPTIONS] [TEST_PATTERN]");
316325
console.log("");
317326
console.log("Options:");
318327
console.log(" --deploy-only Only deploy functions, don't run tests");
319328
console.log(
320-
" --test-only Only run tests (assumes functions are already deployed)"
329+
" --test-only [pattern] Only run tests (assumes functions are already deployed)"
330+
);
331+
console.log(
332+
" Optional test pattern to filter specific tests"
321333
);
322334
console.log(" --cleanup-only Only cleanup existing functions");
323335
console.log(" --help Show this help message");
324336
console.log("");
337+
console.log("Examples:");
338+
console.log(" node integration-test.js --test-only my-test-name");
339+
console.log(" node integration-test.js --test-only");
340+
console.log("");
325341
console.log("Environment Variables:");
326342
console.log(" AWS_REGION AWS region (default: us-west-2)");
327343
console.log(" LAMBDA_ENDPOINT Custom Lambda endpoint URL");
@@ -332,14 +348,18 @@ async function main() {
332348
const args = process.argv.slice(2);
333349

334350
// Parse command line arguments
351+
/** @type {{ cleanupOnExit: boolean, deployOnly: boolean, testOnly: boolean, cleanupOnly: boolean, testPattern?: string }} */
335352
const options = {
336353
cleanupOnExit: true,
337354
deployOnly: false,
338355
testOnly: false,
339356
cleanupOnly: false,
357+
testPattern: undefined,
340358
};
341359

342-
for (const arg of args) {
360+
for (let i = 0; i < args.length; i++) {
361+
const arg = args[i];
362+
343363
switch (arg) {
344364
case "--deploy-only":
345365
options.deployOnly = true;
@@ -348,6 +368,11 @@ async function main() {
348368
case "--test-only":
349369
options.testOnly = true;
350370
options.cleanupOnExit = false;
371+
// Check if the next argument is a test pattern (not another flag)
372+
if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
373+
options.testPattern = args[i + 1];
374+
i++; // Skip the next argument since we consumed it as test pattern
375+
}
351376
break;
352377
case "--cleanup-only":
353378
options.cleanupOnly = true;

packages/aws-durable-execution-sdk-js-examples/examples-catalog.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,10 @@
188188
"handler": "wait-for-callback.handler",
189189
"durableConfig": {
190190
"RetentionPeriodInDays": 7,
191-
"ExecutionTimeout": 300
191+
"ExecutionTimeout": 60
192192
},
193-
"path": "./src/examples/wait-for-callback.ts"
193+
"path": "./src/examples/wait-for-callback.ts",
194+
"integration": true
194195
},
195196
{
196197
"name": "Named Wait",

packages/aws-durable-execution-sdk-js-examples/src/examples/__tests__/shared/test-helper.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
DurableTestRunner,
55
DurableOperation,
66
LocalTestRunnerHandlerFunction,
7+
InvocationType,
78
} from "@aws/durable-execution-sdk-js-testing";
89

910
export interface TestDefinition<ResultType> {
@@ -13,6 +14,7 @@ export interface TestDefinition<ResultType> {
1314
tests: (
1415
runner: DurableTestRunner<DurableOperation<unknown>, ResultType>
1516
) => void;
17+
invocationType?: InvocationType;
1618
}
1719

1820
/**
@@ -36,9 +38,18 @@ export function createTests<ResultType>(testDef: TestDefinition<ResultType>) {
3638

3739
const runner = new CloudDurableTestRunner<ResultType>({
3840
functionName,
39-
config: {
41+
clientConfig: {
4042
endpoint: process.env.LAMBDA_ENDPOINT,
4143
},
44+
config: {
45+
invocationType: testDef.invocationType,
46+
},
47+
});
48+
49+
beforeEach(() => {
50+
// TODO: fix the testing library to allow the same runner to run multiple times on the same handler
51+
// instead of needing to reset the operation storage
52+
runner.reset();
4253
});
4354

4455
describe(`${testDef.name} (cloud)`, () => {
Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,41 @@
1-
import {LocalDurableTestRunner, WaitingOperationStatus} from "@aws/durable-execution-sdk-js-testing";
2-
import {handler} from '../wait-for-callback';
3-
4-
beforeAll(() => LocalDurableTestRunner.setupTestEnvironment());
5-
afterAll(() => LocalDurableTestRunner.teardownTestEnvironment());
6-
7-
let durableTestRunner: LocalDurableTestRunner<void>;
8-
9-
beforeEach(async () => {
10-
durableTestRunner = new LocalDurableTestRunner({
11-
handlerFunction: handler,
12-
skipTime: true,
13-
});
14-
})
15-
16-
describe("wait-for-callback test", () => {
1+
import {
2+
InvocationType,
3+
WaitingOperationStatus,
4+
} from "@aws/durable-execution-sdk-js-testing";
5+
import { handler } from "../wait-for-callback";
6+
import { createTests } from "./shared/test-helper";
7+
8+
createTests({
9+
name: "wait-for-callback test",
10+
functionName: "wait-for-callback",
11+
handler,
12+
invocationType: InvocationType.Event,
13+
tests: (runner) => {
1714
it("function completes when callback succeeds - happy case", async () => {
18-
const executionPromise = durableTestRunner.run();
15+
const executionPromise = runner.run();
1916

20-
const waitForCallbackOp = durableTestRunner.getOperationByIndex(0);
21-
await waitForCallbackOp.waitForData(WaitingOperationStatus.STARTED)
22-
await waitForCallbackOp.sendCallbackSuccess("succeeded");
17+
const waitForCallbackOp = runner.getOperationByIndex(0);
18+
await waitForCallbackOp.waitForData(WaitingOperationStatus.STARTED);
19+
await waitForCallbackOp.sendCallbackSuccess("succeeded");
2320

24-
const execution = await executionPromise;
21+
const execution = await executionPromise;
2522

26-
expect(execution.getResult()).toBeDefined()
23+
expect(execution.getResult()).toEqual("succeeded");
2724
});
2825

2926
it("function completes when callback fails - happy case", async () => {
30-
const executionPromise = durableTestRunner.run();
27+
const executionPromise = runner.run();
3128

32-
const waitForCallbackOp = durableTestRunner.getOperationByIndex(0);
33-
await waitForCallbackOp.waitForData(WaitingOperationStatus.STARTED)
34-
await waitForCallbackOp.sendCallbackFailure({ErrorMessage: "ERROR"});
29+
const waitForCallbackOp = runner.getOperationByIndex(0);
30+
await waitForCallbackOp.waitForData(WaitingOperationStatus.STARTED);
31+
await waitForCallbackOp.sendCallbackFailure({ ErrorMessage: "ERROR" });
3532

36-
const execution = await executionPromise;
33+
const execution = await executionPromise;
3734

38-
expect(execution.getError()).toBeDefined()
35+
expect(execution.getError()).toBeDefined();
3936
});
4037

4138
it("function times out when callback is not called - failure case", async () => {
42-
// TODO: add test when callback timeout is handled in TS SDK: https://tiny.amazon.com/1f5679y8l
4339
});
44-
});
40+
},
41+
});

packages/aws-durable-execution-sdk-js-examples/src/examples/wait-for-callback.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ const mySubmitterFunction = async (callbackId: string): Promise<void> => {
66

77
export const handler = withDurableFunctions(async (event: any, context: DurableContext) => {
88
console.log("Hello world before callback!");
9-
await context.waitForCallback("my callback function", mySubmitterFunction, {
9+
const result = await context.waitForCallback("my callback function", mySubmitterFunction, {
1010
timeout: 10000,
1111
});
1212
console.log("Hello world after callback!");
13+
14+
return result
1315
});

0 commit comments

Comments
 (0)