Skip to content

Commit 58120b8

Browse files
author
Jordi Macià
committed
Add waiting loop until execution completes and some refactoring
1 parent 5953051 commit 58120b8

File tree

3 files changed

+91
-83
lines changed

3 files changed

+91
-83
lines changed

typescript-test-samples/step-functions-local/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ current applications.
2727
- [Key Files in the Project](#key-files-in-the-project)
2828
- [Sample project description](#sample-project-description)
2929
- [Unit Test](#unit-test)
30-
- [Integration Test](#integration-test)
3130

3231
---
3332

typescript-test-samples/step-functions-local/metadata.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"language": "TypeScript",
66
"type": ["Unit"],
77
"diagram": "/images/stepfunctions_local_test.png",
8-
"framework": "SAM",
98
"services": ["sfn"],
109
"git_repo_url": "https://github.com/aws-samples/serverless-test-samples",
1110
"pattern_source": "AWS",

typescript-test-samples/step-functions-local/src/tests/sfnLocal/step-functions-local.test.ts

Lines changed: 91 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
StartExecutionCommandOutput,
1717
CreateStateMachineCommand,
1818
CreateStateMachineCommandOutput,
19-
GetExecutionHistoryCommand,
19+
GetExecutionHistoryCommand,
20+
GetExecutionHistoryCommandOutput,
2021
HistoryEvent
2122
} from "@aws-sdk/client-sfn";
2223
import { StepFunctionsConstants } from './step-functions-constants';
@@ -37,40 +38,10 @@ describe('lead-generation', () => {
3738

3839
let container: StartedTestContainer;
3940
let sfnClient: SFNClient;
40-
let createStateMachineResponse: CreateStateMachineCommandOutput;
4141
let stateMachineArn: string;
4242

4343
beforeAll(async () => {
44-
45-
// Launch StepFunctionLocal with testcontainers
46-
container = await new GenericContainer('amazon/aws-stepfunctions-local')
47-
.withExposedPorts(8083)
48-
.withCopyFilesToContainer([{
49-
source: StepFunctionsConstants.mockFileHostPath,
50-
target: StepFunctionsConstants.mockFileContainerPath
51-
}])
52-
.withEnvironment({ SFN_MOCK_CONFIG: StepFunctionsConstants.mockFileContainerPath })
53-
.withDefaultLogDriver()
54-
.withWaitStrategy(Wait.forLogMessage("Starting server on port 8083"))
55-
.start();
56-
57-
// Set up Step Function client with test container URL
58-
sfnClient = new SFNClient({
59-
endpoint: `http://${container.getHost()}:${container.getMappedPort(8083)}`
60-
});
61-
62-
// Important to avoid non-deterministic behavior, waiting for container to spin up
63-
await sleep(2);
64-
65-
// Create state machine
66-
createStateMachineResponse = await sfnClient.send(new CreateStateMachineCommand({
67-
name: StepFunctionsConstants.STATE_MACHINE_NAME,
68-
definition: StepFunctionsConstants.STATE_MACHINE_ASL,
69-
roleArn: StepFunctionsConstants.DUMMY_ROLE
70-
}));
71-
72-
stateMachineArn = createStateMachineResponse.stateMachineArn as string;
73-
44+
({ container, sfnClient, stateMachineArn } = await setupStepFunctionsLocal());
7445
});
7546

7647
afterAll(async () => {
@@ -91,48 +62,21 @@ describe('lead-generation', () => {
9162
});
9263

9364
it('Test Happy Path Scenario', async () => {
94-
const executionName: string = "happyPathExecution";
95-
const executionResponse: StartExecutionCommandOutput = await sfnClient.send(new StartExecutionCommand({
96-
name: executionName,
97-
stateMachineArn: `${stateMachineArn}#HappyPathTest`,
98-
input: StepFunctionsConstants.EVENT_JSON_STRING
99-
}));
100-
101-
expect(executionResponse.executionArn).not.toBeNull();
102-
103-
// IMP: Wait until above execution completes in docker
104-
await sleep(2);
105-
106-
const historyResponse = await sfnClient.send(new GetExecutionHistoryCommand({
107-
executionArn: executionResponse.executionArn
108-
}));
10965

66+
const historyResponse: GetExecutionHistoryCommandOutput = await manageExecution(sfnClient, stateMachineArn, "happyPathExecution", "HappyPathTest");
11067
expect(historyResponse).toBeDefined();
11168

11269
const results = historyResponse.events?.filter((event) => {
11370
return ((event.type == "TaskStateExited") && (event.stateExitedEventDetails?.name == "CustomerAddedToFollowup"))
11471
});
115-
11672
expect(results?.length).toBe(1);
11773

11874
});
11975

12076
it('Test Negative Sentiment Scenario', async () => {
121-
const executionName: string = "negativeSentimentExecution";
122-
const executionResponse: StartExecutionCommandOutput = await sfnClient.send(new StartExecutionCommand({
123-
name: executionName,
124-
stateMachineArn: `${stateMachineArn}#NegativeSentimentTest`,
125-
input: StepFunctionsConstants.EVENT_JSON_STRING
126-
}));
127-
128-
expect(executionResponse.executionArn).not.toBeNull();
129-
130-
// IMP: Wait until above execution completes in docker
131-
await sleep(2);
132-
133-
const historyResponse = await sfnClient.send(new GetExecutionHistoryCommand({
134-
executionArn: executionResponse.executionArn
135-
}));
77+
78+
const historyResponse: GetExecutionHistoryCommandOutput = await manageExecution(sfnClient, stateMachineArn, "negativeSentimentExecution", "NegativeSentimentTest");
79+
expect(historyResponse).toBeDefined();
13680

13781
const results = historyResponse.events?.filter((event) => {
13882
return ((event.type == "TaskStateExited") && (event.stateExitedEventDetails?.name == "NegativeSentimentDetected"))
@@ -143,21 +87,9 @@ describe('lead-generation', () => {
14387
});
14488

14589
it('Test Retry on Service Exception', async () => {
146-
const executionName: string = "retryExecution";
147-
const executionResponse: StartExecutionCommandOutput = await sfnClient.send(new StartExecutionCommand({
148-
name: executionName,
149-
stateMachineArn: `${stateMachineArn}#RetryOnServiceExceptionTest`,
150-
input: StepFunctionsConstants.EVENT_JSON_STRING
151-
}));
152-
153-
expect(executionResponse.executionArn).not.toBeNull();
154-
155-
// IMP: State Machine has retries with exponential backoff, therefore 4 seconds
156-
await sleep(4);
157-
158-
const historyResponse = await sfnClient.send(new GetExecutionHistoryCommand({
159-
executionArn: executionResponse.executionArn
160-
}))
90+
91+
const historyResponse: GetExecutionHistoryCommandOutput = await manageExecution(sfnClient, stateMachineArn, "retryExecution", "RetryOnServiceExceptionTest");
92+
expect(historyResponse).toBeDefined();
16193

16294
const results: HistoryEvent[] | undefined = historyResponse.events?.filter((event) => {
16395
return (
@@ -178,8 +110,86 @@ describe('lead-generation', () => {
178110
});
179111
});
180112

181-
function sleep(seconds) {
113+
async function setupStepFunctionsLocal() {
114+
115+
// Launch StepFunctionLocal with testcontainers
116+
const container: StartedTestContainer = await createTestContainer();
117+
118+
// Set up Step Function client with test container URL
119+
const sfnClient: SFNClient = new SFNClient({
120+
endpoint: `http://${container.getHost()}:${container.getMappedPort(8083)}`
121+
});
122+
123+
// Create state machine
124+
const createStateMachineResponse: CreateStateMachineCommandOutput = await sfnClient.send(new CreateStateMachineCommand({
125+
name: StepFunctionsConstants.STATE_MACHINE_NAME,
126+
definition: StepFunctionsConstants.STATE_MACHINE_ASL,
127+
roleArn: StepFunctionsConstants.DUMMY_ROLE
128+
}));
129+
130+
const stateMachineArn: string = createStateMachineResponse.stateMachineArn as string;
131+
132+
return { container, sfnClient, stateMachineArn }
133+
134+
}
135+
136+
async function createTestContainer(): Promise<StartedTestContainer> {
137+
138+
const container: StartedTestContainer = await new GenericContainer('amazon/aws-stepfunctions-local')
139+
.withExposedPorts(8083)
140+
.withCopyFilesToContainer([{
141+
source: StepFunctionsConstants.mockFileHostPath,
142+
target: StepFunctionsConstants.mockFileContainerPath
143+
}])
144+
.withEnvironment({ SFN_MOCK_CONFIG: StepFunctionsConstants.mockFileContainerPath })
145+
.withDefaultLogDriver()
146+
.withWaitStrategy(Wait.forLogMessage("Starting server on port 8083"))
147+
.start();
148+
149+
// Important to avoid non-deterministic behavior, waiting for container to spin up
150+
await sleep(2000);
151+
152+
return container;
153+
154+
}
155+
156+
async function manageExecution(sfnClient: SFNClient, stateMachineArn: string, executionName: string, test: string): Promise<GetExecutionHistoryCommandOutput> {
157+
158+
const executionResponse = await sfnClient.send(new StartExecutionCommand({
159+
name: executionName,
160+
stateMachineArn: `${stateMachineArn}#${test}`,
161+
input: StepFunctionsConstants.EVENT_JSON_STRING
162+
}));
163+
164+
return await untilExecutionCompletes(sfnClient, executionResponse);
165+
}
166+
167+
async function untilExecutionCompletes(sfnClient: SFNClient, executionResponse: StartExecutionCommandOutput): Promise<GetExecutionHistoryCommandOutput> {
168+
169+
let historyResponse: GetExecutionHistoryCommandOutput;
170+
171+
do {
172+
// IMP: allow some time for the execution to complete in docker
173+
await sleep(1000);
174+
175+
historyResponse = await sfnClient.send(new GetExecutionHistoryCommand({
176+
executionArn: executionResponse.executionArn
177+
}));
178+
179+
// cycle back if not yet completed
180+
} while (!executionSucceeded(historyResponse));
181+
182+
return historyResponse;
183+
}
184+
185+
function executionSucceeded(historyResponse: any): boolean {
186+
const succeeded = historyResponse.events.filter(event => event.type == 'ExecutionSucceeded');
187+
return succeeded.length == 1;
188+
}
189+
190+
function sleep(milliseconds: number) {
182191
return new Promise((resolve) => {
183-
setTimeout(() => { resolve('') }, seconds*1000)
192+
setTimeout(() => { resolve('') }, milliseconds)
184193
})
185-
}
194+
}
195+

0 commit comments

Comments
 (0)