Skip to content

Commit 99e4a67

Browse files
committed
ci: enable history tests for all remaining examples and update readme
1 parent 2af8afe commit 99e4a67

File tree

82 files changed

+8588
-95
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+8588
-95
lines changed

packages/aws-durable-execution-sdk-js-examples/ADDING_EXAMPLES.md

Lines changed: 162 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -87,21 +87,20 @@ Create a test file in the same directory:
8787

8888
```typescript
8989
import { handler } from "./{example-name}";
90-
import { createTests } from "../../shared/test-helper"; // For nested: "../../../shared/test-helper"
90+
import { createTests } from "../../../utils/test-helper"; // For standalone: "../../utils/test-helper"
9191

9292
createTests({
93-
name: "my-example test",
94-
functionName: "{example-name}",
9593
handler,
96-
tests: (runner) => {
97-
it("should return expected result", async () => {
94+
tests: (runner, { assertEventSignatures }) => {
95+
it("should execute successfully with expected result and operations", async () => {
9896
const execution = await runner.run();
99-
expect(execution.getResult()).toEqual("example result");
100-
});
10197

102-
it("should execute correct number of operations", async () => {
103-
const execution = await runner.run();
98+
// Multiple assertions on the same execution
99+
expect(execution.getResult()).toEqual("example result");
104100
expect(execution.getOperations()).toHaveLength(2); // adjust based on your example
101+
102+
// REQUIRED: Must call assertEventSignatures for every test
103+
assertEventSignatures(execution);
105104
});
106105
},
107106
});
@@ -172,38 +171,147 @@ The `createTests` helper provides a unified interface:
172171

173172
```typescript
174173
createTests({
175-
name: string; // Test suite name
176-
functionName: string; // Must match handler filename (without .ts)
177-
handler: Function; // The handler function to test
178-
invocationType?: string; // Optional: 'RequestResponse' | 'Event'
179-
tests: (runner, isCloud) => void; // Test definitions
174+
handler: DurableLambdaHandler; // The handler function to test
175+
tests: TestCallback<ResultType>; // Test definitions
176+
invocationType?: InvocationType; // Optional: 'RequestResponse' | 'Event'
177+
localRunnerConfig?: LocalDurableTestRunnerSetupParameters; // Optional local test config
180178
});
181179
```
182180

183181
Inside `tests`, you have access to:
184182

185183
- `runner`: Either `LocalDurableTestRunner` or `CloudDurableTestRunner`
186-
- `isCloud`: Boolean indicating if running against real Lambda
184+
- `testHelper`: Object containing:
185+
- `assertEventSignatures`: **Required** function to validate execution history
186+
- `isTimeSkipping`: Boolean indicating if time is being skipped in tests
187+
- `isCloud`: Boolean indicating if running against real Lambda
188+
- `functionNameMap`: Helper for resolving function names in tests
189+
190+
## Event Signature Validation with `assertEventSignatures`
191+
192+
**IMPORTANT**: Every test **MUST** call `assertEventSignatures(execution)` at the end. This validates that the execution produces the expected sequence of durable execution events.
193+
194+
### How it Works
195+
196+
1. **First Run**: When you first create a test, run it with `GENERATE_HISTORY=true` to create the history file:
197+
198+
```bash
199+
GENERATE_HISTORY=true npm test
200+
```
201+
202+
2. **History File Creation**: This generates a `.history.json` file next to your test containing the expected event signatures.
203+
204+
3. **Subsequent Runs**: Normal test runs compare the actual events against the stored history file.
205+
206+
### Example Usage
207+
208+
```typescript
209+
createTests({
210+
handler,
211+
tests: (runner, { assertEventSignatures }) => {
212+
it("should complete workflow successfully", async () => {
213+
const execution = await runner.run();
214+
215+
// Your test assertions
216+
expect(execution.getResult()).toEqual("completed");
217+
expect(execution.getOperations()).toHaveLength(3);
218+
219+
// REQUIRED: Validate event signatures
220+
assertEventSignatures(execution);
221+
});
222+
223+
it("should handle callback operations", async () => {
224+
const callbackOp = runner.getOperation("my-callback");
225+
const executionPromise = runner.run();
226+
227+
await callbackOp.waitForData();
228+
await callbackOp.sendCallbackSuccess("result");
229+
230+
const execution = await executionPromise;
231+
expect(execution.getResult()).toEqual("result");
232+
233+
// REQUIRED: Validate event signatures
234+
assertEventSignatures(execution);
235+
});
236+
},
237+
});
238+
```
239+
240+
### Multiple History Files
241+
242+
For tests with multiple scenarios, you can create separate history files:
243+
244+
```typescript
245+
it("should handle success case", async () => {
246+
const execution = await runner.run({ scenario: "success" });
247+
expect(execution.getResult()).toBe("success");
248+
249+
// Creates/uses example-name-success.history.json
250+
assertEventSignatures(execution, "success");
251+
});
252+
253+
it("should handle failure case", async () => {
254+
const execution = await runner.run({ scenario: "failure" });
255+
expect(execution.getError()).toBeDefined();
256+
257+
// Creates/uses example-name-failure.history.json
258+
assertEventSignatures(execution, "failure");
259+
});
260+
```
187261

188262
### Common Test Patterns
189263

190264
```typescript
191-
tests: (runner, isCloud) => {
192-
it("should return expected result", async () => {
265+
tests: (runner, { assertEventSignatures, isCloud, isTimeSkipping }) => {
266+
// Combine tests with identical setup (same runner.run() call)
267+
it("should execute successfully with expected result and operations", async () => {
193268
const execution = await runner.run();
269+
270+
// Multiple assertions on the same execution
194271
expect(execution.getResult()).toEqual(expectedValue);
195-
});
272+
expect(execution.getOperations()).toHaveLength(3);
196273

197-
it("should execute operations in order", async () => {
198-
const execution = await runner.run();
274+
// Check operations in order
199275
const ops = execution.getOperations();
200-
expect(ops[0].name).toBe("step-1");
201-
expect(ops[1].name).toBe("step-2");
276+
expect(ops[0].getName()).toBe("step-1");
277+
expect(ops[1].getName()).toBe("step-2");
278+
279+
// REQUIRED
280+
assertEventSignatures(execution);
281+
});
282+
283+
// Separate test only when setup is different (different parameters, callbacks, etc.)
284+
it("should handle callback operations", async () => {
285+
const callbackOp = runner.getOperation("my-callback");
286+
const executionPromise = runner.run();
287+
288+
// Wait for callback to start
289+
await callbackOp.waitForData();
290+
291+
// Send callback result
292+
await callbackOp.sendCallbackSuccess("callback-result");
293+
294+
const execution = await executionPromise;
295+
expect(execution.getResult()).toContain("callback-result");
296+
297+
// REQUIRED
298+
assertEventSignatures(execution);
202299
});
203300

204-
it("should execute correct number of operations", async () => {
301+
// Environment-specific tests with different setups
302+
it("should behave differently in cloud vs local", async () => {
205303
const execution = await runner.run();
206-
expect(execution.getOperations()).toHaveLength(3);
304+
305+
if (isCloud) {
306+
// Cloud-specific assertions
307+
expect(execution.getInvocations().length).toBeGreaterThan(1);
308+
} else {
309+
// Local-specific assertions
310+
expect(isTimeSkipping).toBe(true);
311+
}
312+
313+
// REQUIRED
314+
assertEventSignatures(execution);
207315
});
208316
};
209317
```
@@ -213,6 +321,9 @@ tests: (runner, isCloud) => {
213321
- [ ] Created example file in appropriate directory structure
214322
- [ ] Created test file in same directory
215323
- [ ] Used correct import paths for test-helper and types
324+
- [ ] Added `assertEventSignatures` parameter to test callback
325+
- [ ] Called `assertEventSignatures(execution)` in every test
326+
- [ ] Generated history files with `GENERATE_HISTORY=true npm test`
216327
- [ ] Local tests pass (`npm test`)
217328
- [ ] Integration tests pass in CI/CD
218329

@@ -231,6 +342,31 @@ sam local execution history $DURABLE_EXECUTION_ARN
231342

232343
## Troubleshooting
233344

234-
**Test not found in integration run:**
345+
### assertEventSignatures Issues
346+
347+
**Error: "assertEventSignature was not called for test [name]"**
348+
349+
- You forgot to call `assertEventSignatures(execution)` in one or more of your tests
350+
- Make sure every `it()` test calls this function
351+
352+
**Error: "History file [...].history.json does not exist"**
353+
354+
- Run the test with `GENERATE_HISTORY=true npm test` to create the history file
355+
- Make sure the file is committed to your repository
356+
357+
**Error: Event signature mismatch**
358+
359+
- The execution produced different events than expected
360+
- If this is intentional (you changed the function), regenerate the history with `GENERATE_HISTORY=true npm test`
361+
- If not intentional, check your function logic for unintended changes
362+
363+
**TypeError: testResult.getHistoryEvents is not a function**
364+
365+
- You're passing the wrong variable to `assertEventSignatures`
366+
- Pass the `execution` result from `runner.run()`, not `execution.getResult()`
367+
368+
### Test Setup Issues
369+
370+
**Tests timing out:**
235371

236-
- Verify `functionName` in test matches the example name
372+
- For local tests with time skipping disabled: make sure step retries are not longer than the timeout
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
[
2+
{
3+
"EventType": "ExecutionStarted",
4+
"EventId": 1,
5+
"Id": "baad178f-c918-474e-932a-7d0f8868e4be",
6+
"EventTimestamp": "2025-12-10T00:15:09.988Z",
7+
"ExecutionStartedDetails": {
8+
"Input": {
9+
"Payload": "{}"
10+
}
11+
}
12+
},
13+
{
14+
"EventType": "CallbackStarted",
15+
"SubType": "Callback",
16+
"EventId": 2,
17+
"Id": "c4ca4238a0b92382",
18+
"Name": "api-call-1",
19+
"EventTimestamp": "2025-12-10T00:15:10.018Z",
20+
"CallbackStartedDetails": {
21+
"CallbackId": "eyJleGVjdXRpb25JZCI6IjNlZWIyMjRlLWM2ZGMtNDMzZC04NjFhLWI2YzlhZTIzMjg3NSIsIm9wZXJhdGlvbklkIjoiYzRjYTQyMzhhMGI5MjM4MiIsInRva2VuIjoiM2RjNGQ0ZjAtZGM4OS00YThiLTgxZDgtMmExZDE2MTVhMGRkIn0=",
22+
"Timeout": 300,
23+
"Input": {}
24+
}
25+
},
26+
{
27+
"EventType": "CallbackStarted",
28+
"SubType": "Callback",
29+
"EventId": 3,
30+
"Id": "c81e728d9d4c2f63",
31+
"Name": "api-call-2",
32+
"EventTimestamp": "2025-12-10T00:15:10.039Z",
33+
"CallbackStartedDetails": {
34+
"CallbackId": "eyJleGVjdXRpb25JZCI6IjNlZWIyMjRlLWM2ZGMtNDMzZC04NjFhLWI2YzlhZTIzMjg3NSIsIm9wZXJhdGlvbklkIjoiYzgxZTcyOGQ5ZDRjMmY2MyIsInRva2VuIjoiMjAwMjczNzMtNWM0Zi00OGY2LTg0NTEtODc4MmM5Njg4ZTNiIn0=",
35+
"Timeout": 300,
36+
"Input": {}
37+
}
38+
},
39+
{
40+
"EventType": "CallbackStarted",
41+
"SubType": "Callback",
42+
"EventId": 4,
43+
"Id": "eccbc87e4b5ce2fe",
44+
"Name": "api-call-3",
45+
"EventTimestamp": "2025-12-10T00:15:10.067Z",
46+
"CallbackStartedDetails": {
47+
"CallbackId": "eyJleGVjdXRpb25JZCI6IjNlZWIyMjRlLWM2ZGMtNDMzZC04NjFhLWI2YzlhZTIzMjg3NSIsIm9wZXJhdGlvbklkIjoiZWNjYmM4N2U0YjVjZTJmZSIsInRva2VuIjoiZTAyMTdlNWItMDQyZS00MmRjLWEwNmEtZGFkMzQ4ZjgzNzQwIn0=",
48+
"Timeout": 300,
49+
"Input": {}
50+
}
51+
},
52+
{
53+
"EventType": "CallbackSucceeded",
54+
"SubType": "Callback",
55+
"EventId": 5,
56+
"Id": "c81e728d9d4c2f63",
57+
"Name": "api-call-2",
58+
"EventTimestamp": "2025-12-10T00:15:10.070Z",
59+
"CallbackSucceededDetails": {
60+
"Result": {
61+
"Payload": "{\"id\":2,\"data\":\"second\"}"
62+
}
63+
}
64+
},
65+
{
66+
"EventType": "CallbackSucceeded",
67+
"SubType": "Callback",
68+
"EventId": 6,
69+
"Id": "c4ca4238a0b92382",
70+
"Name": "api-call-1",
71+
"EventTimestamp": "2025-12-10T00:15:10.071Z",
72+
"CallbackSucceededDetails": {
73+
"Result": {
74+
"Payload": "{\"id\":1,\"data\":\"first\"}"
75+
}
76+
}
77+
},
78+
{
79+
"EventType": "CallbackSucceeded",
80+
"SubType": "Callback",
81+
"EventId": 7,
82+
"Id": "eccbc87e4b5ce2fe",
83+
"Name": "api-call-3",
84+
"EventTimestamp": "2025-12-10T00:15:10.075Z",
85+
"CallbackSucceededDetails": {
86+
"Result": {
87+
"Payload": "{\"id\":3,\"data\":\"third\"}"
88+
}
89+
}
90+
},
91+
{
92+
"EventType": "InvocationCompleted",
93+
"EventId": 8,
94+
"EventTimestamp": "2025-12-10T00:15:10.126Z",
95+
"InvocationCompletedDetails": {
96+
"StartTimestamp": "2025-12-10T00:15:09.987Z",
97+
"EndTimestamp": "2025-12-10T00:15:10.126Z",
98+
"Error": {},
99+
"RequestId": "6f2132e4-4618-4e08-88c0-9819e79685ca"
100+
}
101+
},
102+
{
103+
"EventType": "InvocationCompleted",
104+
"EventId": 9,
105+
"EventTimestamp": "2025-12-10T00:15:10.146Z",
106+
"InvocationCompletedDetails": {
107+
"StartTimestamp": "2025-12-10T00:15:10.146Z",
108+
"EndTimestamp": "2025-12-10T00:15:10.146Z",
109+
"Error": {},
110+
"RequestId": "7b23a50d-1b08-47d5-96ed-473f84c01ffd"
111+
}
112+
},
113+
{
114+
"EventType": "ExecutionSucceeded",
115+
"EventId": 10,
116+
"Id": "baad178f-c918-474e-932a-7d0f8868e4be",
117+
"EventTimestamp": "2025-12-10T00:15:10.147Z",
118+
"ExecutionSucceededDetails": {
119+
"Result": {
120+
"Payload": "{\"results\":[\"{\\\"id\\\":1,\\\"data\\\":\\\"first\\\"}\",\"{\\\"id\\\":2,\\\"data\\\":\\\"second\\\"}\",\"{\\\"id\\\":3,\\\"data\\\":\\\"third\\\"}\"],\"allCompleted\":true}"
121+
}
122+
}
123+
}
124+
]

packages/aws-durable-execution-sdk-js-examples/src/examples/create-callback/concurrent/create-callback-concurrent.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { createTests } from "../../../utils/test-helper";
99
createTests({
1010
handler,
1111
invocationType: InvocationType.Event,
12-
tests: (runner) => {
12+
tests: (runner, { assertEventSignatures }) => {
1313
it("should handle multiple concurrent callback operations", async () => {
1414
// Get all callback operations
1515
const callback1 = runner.getOperation("api-call-1");
@@ -57,6 +57,8 @@ createTests({
5757
(op) => op.getType() === OperationType.CALLBACK,
5858
),
5959
).toBe(true);
60+
61+
assertEventSignatures(result);
6062
});
6163
},
6264
});

0 commit comments

Comments
 (0)