@@ -16,7 +16,8 @@ import {
16
16
StartExecutionCommandOutput ,
17
17
CreateStateMachineCommand ,
18
18
CreateStateMachineCommandOutput ,
19
- GetExecutionHistoryCommand ,
19
+ GetExecutionHistoryCommand ,
20
+ GetExecutionHistoryCommandOutput ,
20
21
HistoryEvent
21
22
} from "@aws-sdk/client-sfn" ;
22
23
import { StepFunctionsConstants } from './step-functions-constants' ;
@@ -37,40 +38,10 @@ describe('lead-generation', () => {
37
38
38
39
let container : StartedTestContainer ;
39
40
let sfnClient : SFNClient ;
40
- let createStateMachineResponse : CreateStateMachineCommandOutput ;
41
41
let stateMachineArn : string ;
42
42
43
43
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 ( ) ) ;
74
45
} ) ;
75
46
76
47
afterAll ( async ( ) => {
@@ -91,48 +62,21 @@ describe('lead-generation', () => {
91
62
} ) ;
92
63
93
64
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
- } ) ) ;
109
65
66
+ const historyResponse : GetExecutionHistoryCommandOutput = await manageExecution ( sfnClient , stateMachineArn , "happyPathExecution" , "HappyPathTest" ) ;
110
67
expect ( historyResponse ) . toBeDefined ( ) ;
111
68
112
69
const results = historyResponse . events ?. filter ( ( event ) => {
113
70
return ( ( event . type == "TaskStateExited" ) && ( event . stateExitedEventDetails ?. name == "CustomerAddedToFollowup" ) )
114
71
} ) ;
115
-
116
72
expect ( results ?. length ) . toBe ( 1 ) ;
117
73
118
74
} ) ;
119
75
120
76
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 ( ) ;
136
80
137
81
const results = historyResponse . events ?. filter ( ( event ) => {
138
82
return ( ( event . type == "TaskStateExited" ) && ( event . stateExitedEventDetails ?. name == "NegativeSentimentDetected" ) )
@@ -143,21 +87,9 @@ describe('lead-generation', () => {
143
87
} ) ;
144
88
145
89
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 ( ) ;
161
93
162
94
const results : HistoryEvent [ ] | undefined = historyResponse . events ?. filter ( ( event ) => {
163
95
return (
@@ -178,8 +110,86 @@ describe('lead-generation', () => {
178
110
} ) ;
179
111
} ) ;
180
112
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 ) {
182
191
return new Promise ( ( resolve ) => {
183
- setTimeout ( ( ) => { resolve ( '' ) } , seconds * 1000 )
192
+ setTimeout ( ( ) => { resolve ( '' ) } , milliseconds )
184
193
} )
185
- }
194
+ }
195
+
0 commit comments