Skip to content

Commit bc67710

Browse files
authored
feat(examples): add simple invoke tests (#207)
*Issue #, if available:* *Description of changes:* Adding simple invoke tests with: - durable handler - non-durable handler - failing handler - named step with payload Additionally: - updating language SDK to accept calling invoke with no input, since the API supports it - enable registering non-durable functions in testing library for invoke, since the API supports it 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 d52d485 commit bc67710

File tree

13 files changed

+476
-40
lines changed

13 files changed

+476
-40
lines changed

packages/aws-durable-execution-sdk-js-examples/scripts/deploy-lambda.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
ResourceNotFoundException,
1717
ResourceConflictException,
1818
UpdateFunctionConfigurationCommandInput,
19+
DeleteFunctionCommand,
1920
} from "@aws-sdk/client-lambda";
2021
import { ExamplesWithConfig } from "../src/types";
2122
import catalog from "@aws/durable-execution-sdk-js-examples/catalog";
@@ -214,10 +215,13 @@ async function createFunction(
214215
Handler: exampleConfig.handler,
215216
Description: exampleConfig.description,
216217
Code: { ZipFile: zipBuffer },
217-
DurableConfig: {
218-
RetentionPeriodInDays: exampleConfig.durableConfig.RetentionPeriodInDays,
219-
ExecutionTimeout: exampleConfig.durableConfig.ExecutionTimeout,
220-
},
218+
DurableConfig: exampleConfig.durableConfig
219+
? {
220+
RetentionPeriodInDays:
221+
exampleConfig.durableConfig.RetentionPeriodInDays,
222+
ExecutionTimeout: exampleConfig.durableConfig.ExecutionTimeout,
223+
}
224+
: undefined,
221225
Timeout: 60,
222226
MemorySize: 128,
223227
Environment: {
@@ -243,12 +247,10 @@ async function updateFunction(
243247
): Promise<void> {
244248
console.log(`Deploying function: ${functionName} (updating existing)`);
245249

246-
const currentRetention =
247-
currentConfig.DurableConfig?.RetentionPeriodInDays || "none";
248-
const currentTimeout =
249-
currentConfig.DurableConfig?.ExecutionTimeout || "none";
250-
const targetRetention = exampleConfig.durableConfig.RetentionPeriodInDays;
251-
const targetTimeout = exampleConfig.durableConfig.ExecutionTimeout;
250+
const currentRetention = currentConfig.DurableConfig?.RetentionPeriodInDays;
251+
const currentTimeout = currentConfig.DurableConfig?.ExecutionTimeout;
252+
const targetRetention = exampleConfig.durableConfig?.RetentionPeriodInDays;
253+
const targetTimeout = exampleConfig.durableConfig?.ExecutionTimeout;
252254

253255
console.log("Function exists with current DurableConfig:");
254256
console.log(` Current Retention: ${currentRetention} days`);
@@ -330,10 +332,10 @@ async function main(): Promise<void> {
330332
console.log(` Runtime: ${runtime}`);
331333
}
332334
console.log(
333-
` Retention: ${exampleConfig.durableConfig.RetentionPeriodInDays} days`,
335+
` Retention: ${exampleConfig.durableConfig?.RetentionPeriodInDays} days`,
334336
);
335337
console.log(
336-
` Timeout: ${exampleConfig.durableConfig.ExecutionTimeout} seconds`,
338+
` Timeout: ${exampleConfig.durableConfig?.ExecutionTimeout} seconds`,
337339
);
338340

339341
// Validate zip file exists
@@ -346,27 +348,34 @@ async function main(): Promise<void> {
346348
});
347349

348350
console.log("Checking if function exists...");
349-
const functionExists = await checkFunctionExists(
350-
lambdaClient,
351-
functionName,
352-
);
351+
let functionExists = await checkFunctionExists(lambdaClient, functionName);
352+
let currentConfig: GetFunctionConfigurationCommandOutput;
353353

354354
const zipFile = `${example}.zip`;
355355

356356
const selectedRuntime = mapRuntimeToEnum(runtime);
357357

358358
if (functionExists) {
359-
const currentConfig = await getCurrentConfiguration(
360-
lambdaClient,
361-
functionName,
362-
);
359+
currentConfig = await getCurrentConfiguration(lambdaClient, functionName);
360+
if (!!currentConfig.DurableConfig !== !!exampleConfig.durableConfig) {
361+
console.log("Deleting function since durable changed");
362+
await lambdaClient.send(
363+
new DeleteFunctionCommand({
364+
FunctionName: functionName,
365+
}),
366+
);
367+
functionExists = false;
368+
}
369+
}
370+
371+
if (functionExists) {
363372
await updateFunction(
364373
lambdaClient,
365374
functionName,
366375
exampleConfig,
367376
zipFile,
368377
env,
369-
currentConfig,
378+
currentConfig!,
370379
selectedRuntime,
371380
);
372381
} else {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { LocalDurableTestRunner } from "@aws/durable-execution-sdk-js-testing";
2+
import { createTests } from "../../../utils/test-helper";
3+
import { handler } from "./invoke-simple";
4+
import { handler as namedWaitHandler } from "../../wait/named/wait-named";
5+
import { handler as handlerErrorHandler } from "../../handler-error/handler-error";
6+
import { handler as nonDurableHandler } from "../../non-durable/non-durable";
7+
import { handler as namedStepHandler } from "../../step/named/step-named";
8+
9+
createTests({
10+
name: "invoke-simple",
11+
functionName: "invoke-simple",
12+
handler,
13+
tests: function (runner, _, functionNameMap) {
14+
it("should run invoke with basic wait state", async () => {
15+
if (runner instanceof LocalDurableTestRunner) {
16+
runner.registerDurableFunction(
17+
functionNameMap.getFunctionName("wait-named"),
18+
namedWaitHandler,
19+
);
20+
}
21+
22+
const result = await runner.run({
23+
payload: {
24+
functionName: functionNameMap.getFunctionName("wait-named"),
25+
},
26+
});
27+
expect(result.getResult()).toBe("wait finished");
28+
});
29+
30+
it("should run invoke with step and payload", async () => {
31+
if (runner instanceof LocalDurableTestRunner) {
32+
runner.registerDurableFunction(
33+
functionNameMap.getFunctionName("step-named"),
34+
namedStepHandler,
35+
);
36+
}
37+
38+
const result = await runner.run({
39+
payload: {
40+
functionName: functionNameMap.getFunctionName("step-named"),
41+
payload: {
42+
data: "data from parent",
43+
},
44+
},
45+
});
46+
expect(result.getResult()).toEqual("processed: data from parent");
47+
});
48+
49+
it("should run invoke with child function failure", async () => {
50+
if (runner instanceof LocalDurableTestRunner) {
51+
runner.registerDurableFunction(
52+
functionNameMap.getFunctionName("handler-error"),
53+
handlerErrorHandler,
54+
);
55+
}
56+
57+
const result = await runner.run({
58+
payload: {
59+
functionName: functionNameMap.getFunctionName("handler-error"),
60+
},
61+
});
62+
expect(result.getError()).toEqual({
63+
errorMessage: "Intentional handler failure",
64+
errorType: "InvokeError",
65+
});
66+
});
67+
68+
it("should run invoke with non-durable function success", async () => {
69+
if (runner instanceof LocalDurableTestRunner) {
70+
runner.registerFunction(
71+
functionNameMap.getFunctionName("non-durable"),
72+
nonDurableHandler,
73+
);
74+
}
75+
76+
const result = await runner.run({
77+
payload: {
78+
functionName: functionNameMap.getFunctionName("non-durable"),
79+
},
80+
});
81+
expect(result.getResult()).toEqual({
82+
status: 200,
83+
body: JSON.stringify({
84+
message: "Hello from Lambda!",
85+
}),
86+
});
87+
});
88+
89+
it("should run invoke with non-durable function failure", async () => {
90+
if (runner instanceof LocalDurableTestRunner) {
91+
runner.registerFunction(
92+
functionNameMap.getFunctionName("non-durable"),
93+
nonDurableHandler,
94+
);
95+
}
96+
97+
const result = await runner.run({
98+
payload: {
99+
functionName: functionNameMap.getFunctionName("non-durable"),
100+
payload: {
101+
failure: true,
102+
},
103+
},
104+
});
105+
expect(result.getError()).toEqual({
106+
errorMessage: "This is a failure",
107+
errorType: "InvokeError",
108+
});
109+
});
110+
},
111+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {
2+
DurableContext,
3+
withDurableExecution,
4+
} from "@aws/durable-execution-sdk-js";
5+
import { ExampleConfig } from "../../../types";
6+
7+
export const config: ExampleConfig = {
8+
name: "Invoke Simple",
9+
description:
10+
"Demonstrates a simple invoke, returning the result of the invoke",
11+
};
12+
13+
export const handler = withDurableExecution(
14+
async (
15+
event: {
16+
functionName: string;
17+
payload: Record<string, any>;
18+
},
19+
context: DurableContext,
20+
) => {
21+
const result = await context.invoke(event.functionName, event.payload);
22+
return result;
23+
},
24+
);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ExampleConfig } from "../../types";
2+
3+
export const config: ExampleConfig = {
4+
name: "Non-Durable",
5+
description: "A simple non-durable function used for testing.",
6+
durableConfig: null,
7+
};
8+
9+
export const handler = async (event: { failure: boolean }) => {
10+
if (event.failure) {
11+
throw new Error("This is a failure");
12+
}
13+
14+
await new Promise((resolve) => setTimeout(resolve, 1000));
15+
16+
return {
17+
status: 200,
18+
body: JSON.stringify({ message: "Hello from Lambda!" }),
19+
};
20+
};

packages/aws-durable-execution-sdk-js-examples/src/types/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ export interface ExampleConfig {
55
description?: string;
66
/**
77
* The durable config of the function. By default, RetentionPeriodInDays will be set to 7 days
8-
* and ExecutionTimeout will be set to 60 seconds.
8+
* and ExecutionTimeout will be set to 60 seconds. Null if function is not durable.
99
*/
10-
durableConfig?: DurableConfig;
10+
durableConfig?: DurableConfig | null;
1111
}
1212

1313
export type ExamplesWithConfig = ExampleConfig & {
14-
durableConfig: DurableConfig;
1514
path: string;
1615
handler: string;
1716
};

packages/aws-durable-execution-sdk-js-examples/src/utils/examples.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,18 @@ export class Examples {
4646
? config.description
4747
: undefined;
4848

49+
const durableConfig =
50+
"durableConfig" in config && typeof config.durableConfig === "object"
51+
? config.durableConfig
52+
: undefined;
53+
4954
return {
5055
name: config.name,
5156
description,
5257
path: examplePath,
5358
handler: handlerName,
54-
durableConfig: DEFAULT_DURABLE_CONFIG,
59+
durableConfig:
60+
durableConfig === undefined ? DEFAULT_DURABLE_CONFIG : durableConfig,
5561
};
5662
}
5763

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

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,41 @@ import {
77
InvocationType,
88
} from "@aws/durable-execution-sdk-js-testing";
99

10+
type TestCallback<ResultType> = (
11+
runner: DurableTestRunner<DurableOperation<unknown>, ResultType>,
12+
isCloud: boolean,
13+
functionNameMap: FunctionNameMap,
14+
) => void;
15+
1016
export interface TestDefinition<ResultType> {
1117
name: string;
1218
functionName: string;
1319
handler: LocalTestRunnerHandlerFunction;
14-
tests: (
15-
runner: DurableTestRunner<DurableOperation<unknown>, ResultType>,
16-
isCloud: boolean,
17-
) => void;
20+
tests: TestCallback<ResultType>;
1821
invocationType?: InvocationType;
1922
localRunnerConfig?: {
2023
skipTime?: boolean;
2124
};
2225
}
2326

27+
export interface FunctionNameMap {
28+
getFunctionName(functionName: string): string;
29+
}
30+
31+
class CloudFunctionNameMap implements FunctionNameMap {
32+
constructor(private readonly functionNameMap: Record<string, string>) {}
33+
34+
getFunctionName(functionName: string): string {
35+
return this.functionNameMap[functionName];
36+
}
37+
}
38+
39+
class LocalFunctionNameMap implements FunctionNameMap {
40+
getFunctionName(functionName: string): string {
41+
return functionName;
42+
}
43+
}
44+
2445
/**
2546
* Creates tests that automatically run with the appropriate test runner
2647
* based on the environment variables passed in.
@@ -32,7 +53,10 @@ export function createTests<ResultType>(testDef: TestDefinition<ResultType>) {
3253
throw new Error("FUNCTION_NAME_MAP is not set for integration tests");
3354
}
3455

35-
const functionNames = JSON.parse(process.env.FUNCTION_NAME_MAP!);
56+
const functionNames = JSON.parse(process.env.FUNCTION_NAME_MAP!) as Record<
57+
string,
58+
string
59+
>;
3660
const functionName = functionNames[testDef.functionName];
3761
if (!functionName) {
3862
throw new Error(
@@ -57,7 +81,7 @@ export function createTests<ResultType>(testDef: TestDefinition<ResultType>) {
5781
});
5882

5983
describe(`${testDef.name} (cloud)`, () => {
60-
testDef.tests(runner, true);
84+
testDef.tests(runner, true, new CloudFunctionNameMap(functionNames));
6185
});
6286
return;
6387
}
@@ -81,6 +105,6 @@ export function createTests<ResultType>(testDef: TestDefinition<ResultType>) {
81105
runner.reset();
82106
});
83107

84-
testDef.tests(runner, false);
108+
testDef.tests(runner, false, new LocalFunctionNameMap());
85109
});
86110
}

0 commit comments

Comments
 (0)