├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bin └── step-functions-testing.ts ├── cdk.json ├── data └── sample │ └── eventhistory.json ├── jest.config.js ├── lib ├── sales-leads-state-machine-construct.ts ├── state-machine-construct.ts └── step-functions-testing-stack.ts ├── package-lock.json ├── package.json ├── test ├── helpers │ ├── stepfunctions.ts │ └── stepfunctions │ │ ├── cdk-to-asl.ts │ │ ├── client-wrapper.ts │ │ ├── execution-history-data.ts │ │ ├── execution-history.test.ts │ │ ├── execution-history.ts │ │ └── local-container.ts └── stepfunctions │ ├── __snapshots__ │ ├── lambda-sqs-integration.test.ts.snap │ └── sales-lead-state-machine.test.ts.snap │ ├── lambda-sqs-integration.test.ts │ └── sales-lead-state-machine.test.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ken Fukuyama 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PoC Step Functions integration testing with Step Functions Local 2 | 3 | Credits on extracting the ASL from CDK go to the following repository: 4 | 5 | https://github.com/nathanagez/aws-cdk-state-machine-asl 6 | 7 | There is also an excellent blog post and recommended to read: 8 | 9 | https://nathanagez.com/blog/mocking-service-integration-step-functions-local-cdk/ 10 | 11 | This repository takes a step further and creates the CDK within Jest and sends the ASL to Step Functions Local via the AWS SDK. 12 | 13 | ## How to run the test 14 | 15 | ``` 16 | # install the dependencies 17 | npm install # whatsoever 18 | 19 | # run the test(make sure you have docker installed) 20 | npm test 21 | ``` 22 | 23 | ## Reference 24 | 25 | - [Using Mocked Service Integrations - AWS Step Functions](https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-test-sm-exec.html) 26 | - [Mocking service integrations with AWS Step Functions Local | AWS Compute Blog](https://aws.amazon.com/jp/blogs/compute/mocking-service-integrations-with-aws-step-functions-local/) 27 | - [aws-stepfunctions-examples/sam/app-local-testing-mock-config at main · aws-samples/aws-stepfunctions-examples](https://github.com/aws-samples/aws-stepfunctions-examples/tree/main/sam/app-local-testing-mock-config/) -------------------------------------------------------------------------------- /bin/step-functions-testing.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from 'aws-cdk-lib'; 4 | import { StepFunctionsTestingStack } from '../lib/step-functions-testing-stack'; 5 | 6 | const app = new cdk.App(); 7 | new StepFunctionsTestingStack(app, 'StepFunctionsTestingStack', { 8 | /* If you don't specify 'env', this stack will be environment-agnostic. 9 | * Account/Region-dependent features and context lookups will not work, 10 | * but a single synthesized template can be deployed anywhere. */ 11 | 12 | /* Uncomment the next line to specialize this stack for the AWS Account 13 | * and Region that are implied by the current CLI configuration. */ 14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, 15 | 16 | /* Uncomment the next line if you know exactly what Account and Region you 17 | * want to deploy the stack to. */ 18 | // env: { account: '123456789012', region: 'us-east-1' }, 19 | 20 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ 21 | }); -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/step-functions-testing.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 25 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 26 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 27 | "@aws-cdk/core:target-partitions": [ 28 | "aws", 29 | "aws-cn" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /data/sample/eventhistory.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "executionStartedEventDetails": { 4 | "input": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"}}", 5 | "inputDetails": { "truncated": false }, 6 | "roleArn": "arn:aws:iam::123456789012:role/service-role/LambdaSQSIntegration" 7 | }, 8 | "id": 1, 9 | "previousEventId": 0, 10 | "timestamp": "2022-02-19T00:27:48.354Z", 11 | "type": "ExecutionStarted" 12 | }, 13 | { 14 | "id": 2, 15 | "previousEventId": 0, 16 | "stateEnteredEventDetails": { 17 | "input": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"}}", 18 | "inputDetails": { "truncated": false }, 19 | "name": "Validation" 20 | }, 21 | "timestamp": "2022-02-19T00:27:48.355Z", 22 | "type": "ParallelStateEntered" 23 | }, 24 | { 25 | "id": 3, 26 | "previousEventId": 2, 27 | "timestamp": "2022-02-19T00:27:48.358Z", 28 | "type": "ParallelStateStarted" 29 | }, 30 | { 31 | "id": 4, 32 | "previousEventId": 3, 33 | "stateEnteredEventDetails": { 34 | "input": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"}}", 35 | "inputDetails": { "truncated": false }, 36 | "name": "Check Identity" 37 | }, 38 | "timestamp": "2022-02-19T00:27:48.360Z", 39 | "type": "TaskStateEntered" 40 | }, 41 | { 42 | "id": 5, 43 | "previousEventId": 4, 44 | "taskScheduledEventDetails": { 45 | "parameters": "{\"FunctionName\":\"arn:aws:lambda:us-west-2:123456789012:function:check-identity-function\",\"Payload\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"}}", 46 | "region": "us-east-1", 47 | "resource": "invoke", 48 | "resourceType": "lambda" 49 | }, 50 | "timestamp": "2022-02-19T00:27:48.368Z", 51 | "type": "TaskScheduled" 52 | }, 53 | { 54 | "id": 6, 55 | "previousEventId": 3, 56 | "stateEnteredEventDetails": { 57 | "input": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"}}", 58 | "inputDetails": { "truncated": false }, 59 | "name": "Check Address" 60 | }, 61 | "timestamp": "2022-02-19T00:27:48.369Z", 62 | "type": "TaskStateEntered" 63 | }, 64 | { 65 | "id": 7, 66 | "previousEventId": 6, 67 | "taskScheduledEventDetails": { 68 | "parameters": "{\"FunctionName\":\"arn:aws:lambda:us-west-2:123456789012:function:check-address-function\",\"Payload\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"}}", 69 | "region": "us-east-1", 70 | "resource": "invoke", 71 | "resourceType": "lambda" 72 | }, 73 | "timestamp": "2022-02-19T00:27:48.370Z", 74 | "type": "TaskScheduled" 75 | }, 76 | { 77 | "id": 8, 78 | "previousEventId": 7, 79 | "taskStartedEventDetails": { 80 | "resource": "invoke", 81 | "resourceType": "lambda" 82 | }, 83 | "timestamp": "2022-02-19T00:27:48.373Z", 84 | "type": "TaskStarted" 85 | }, 86 | { 87 | "id": 9, 88 | "previousEventId": 5, 89 | "taskStartedEventDetails": { 90 | "resource": "invoke", 91 | "resourceType": "lambda" 92 | }, 93 | "timestamp": "2022-02-19T00:27:48.374Z", 94 | "type": "TaskStarted" 95 | }, 96 | { 97 | "id": 10, 98 | "previousEventId": 9, 99 | "taskSucceededEventDetails": { 100 | "output": "{\"identity\":{\"approved\":true,\"message\":\"identity validation passed\"}}", 101 | "outputDetails": { "truncated": false }, 102 | "resource": "invoke", 103 | "resourceType": "lambda" 104 | }, 105 | "timestamp": "2022-02-19T00:27:48.375Z", 106 | "type": "TaskSucceeded" 107 | }, 108 | { 109 | "id": 11, 110 | "previousEventId": 10, 111 | "stateExitedEventDetails": { 112 | "name": "Check Identity", 113 | "output": "{\"identity\":{\"approved\":true,\"message\":\"identity validation passed\"}}", 114 | "outputDetails": { "truncated": false } 115 | }, 116 | "timestamp": "2022-02-19T00:27:48.375Z", 117 | "type": "TaskStateExited" 118 | }, 119 | { 120 | "id": 12, 121 | "previousEventId": 8, 122 | "taskSucceededEventDetails": { 123 | "output": "{\"address\":{\"approved\":true,\"message\":\"address validation passed\"}}", 124 | "outputDetails": { "truncated": false }, 125 | "resource": "invoke", 126 | "resourceType": "lambda" 127 | }, 128 | "timestamp": "2022-02-19T00:27:48.376Z", 129 | "type": "TaskSucceeded" 130 | }, 131 | { 132 | "id": 13, 133 | "previousEventId": 12, 134 | "stateExitedEventDetails": { 135 | "name": "Check Address", 136 | "output": "{\"address\":{\"approved\":true,\"message\":\"address validation passed\"}}", 137 | "outputDetails": { "truncated": false } 138 | }, 139 | "timestamp": "2022-02-19T00:27:48.376Z", 140 | "type": "TaskStateExited" 141 | }, 142 | { 143 | "id": 14, 144 | "previousEventId": 13, 145 | "timestamp": "2022-02-19T00:27:48.393Z", 146 | "type": "ParallelStateSucceeded" 147 | }, 148 | { 149 | "id": 15, 150 | "previousEventId": 14, 151 | "stateExitedEventDetails": { 152 | "name": "Validation", 153 | "output": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"},\"results\":{\"addressResult\":{\"approved\":true,\"message\":\"address validation passed\"},\"identityResult\":{\"approved\":true,\"message\":\"identity validation passed\"}}}", 154 | "outputDetails": { "truncated": false } 155 | }, 156 | "timestamp": "2022-02-19T00:27:48.395Z", 157 | "type": "ParallelStateExited" 158 | }, 159 | { 160 | "id": 16, 161 | "previousEventId": 15, 162 | "stateEnteredEventDetails": { 163 | "input": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"},\"results\":{\"addressResult\":{\"approved\":true,\"message\":\"address validation passed\"},\"identityResult\":{\"approved\":true,\"message\":\"identity validation passed\"}}}", 164 | "inputDetails": { "truncated": false }, 165 | "name": "DetectSentiment" 166 | }, 167 | "timestamp": "2022-02-19T00:27:48.398Z", 168 | "type": "TaskStateEntered" 169 | }, 170 | { 171 | "id": 17, 172 | "previousEventId": 16, 173 | "taskScheduledEventDetails": { 174 | "parameters": "{\"LanguageCode\":\"en\",\"Text\":\"I am glad to sign-up for this service. Looking forward to different options.\"}", 175 | "region": "us-east-1", 176 | "resource": "comprehend:detectSentiment", 177 | "resourceType": "aws-sdk" 178 | }, 179 | "timestamp": "2022-02-19T00:27:48.399Z", 180 | "type": "TaskScheduled" 181 | }, 182 | { 183 | "id": 18, 184 | "previousEventId": 17, 185 | "taskStartedEventDetails": { 186 | "resource": "comprehend:detectSentiment", 187 | "resourceType": "aws-sdk" 188 | }, 189 | "timestamp": "2022-02-19T00:27:48.399Z", 190 | "type": "TaskStarted" 191 | }, 192 | { 193 | "id": 19, 194 | "previousEventId": 18, 195 | "taskSucceededEventDetails": { 196 | "output": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"},\"results\":{\"sentimentAnalysis\":{\"Sentiment\":\"POSITIVE\",\"SentimentScore\":{\"Mixed\":1.2647535E-4,\"Negative\":8.031699E-5,\"Neutral\":0.0051454515,\"Positive\":0.9946478}}}}", 197 | "outputDetails": { "truncated": false }, 198 | "resource": "comprehend:detectSentiment", 199 | "resourceType": "aws-sdk" 200 | }, 201 | "timestamp": "2022-02-19T00:27:48.401Z", 202 | "type": "TaskSucceeded" 203 | }, 204 | { 205 | "id": 20, 206 | "previousEventId": 19, 207 | "stateExitedEventDetails": { 208 | "name": "DetectSentiment", 209 | "output": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"},\"results\":{\"sentimentAnalysis\":{\"Sentiment\":\"POSITIVE\",\"SentimentScore\":{\"Mixed\":1.2647535E-4,\"Negative\":8.031699E-5,\"Neutral\":0.0051454515,\"Positive\":0.9946478}}}}", 210 | "outputDetails": { "truncated": false } 211 | }, 212 | "timestamp": "2022-02-19T00:27:48.401Z", 213 | "type": "TaskStateExited" 214 | }, 215 | { 216 | "id": 21, 217 | "previousEventId": 20, 218 | "stateEnteredEventDetails": { 219 | "input": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"},\"results\":{\"sentimentAnalysis\":{\"Sentiment\":\"POSITIVE\",\"SentimentScore\":{\"Mixed\":1.2647535E-4,\"Negative\":8.031699E-5,\"Neutral\":0.0051454515,\"Positive\":0.9946478}}}}", 220 | "inputDetails": { "truncated": false }, 221 | "name": "Is Positive Sentiment?" 222 | }, 223 | "timestamp": "2022-02-19T00:27:48.402Z", 224 | "type": "ChoiceStateEntered" 225 | }, 226 | { 227 | "id": 22, 228 | "previousEventId": 21, 229 | "stateExitedEventDetails": { 230 | "name": "Is Positive Sentiment?", 231 | "output": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"},\"results\":{\"sentimentAnalysis\":{\"Sentiment\":\"POSITIVE\",\"SentimentScore\":{\"Mixed\":1.2647535E-4,\"Negative\":8.031699E-5,\"Neutral\":0.0051454515,\"Positive\":0.9946478}}}}", 232 | "outputDetails": { "truncated": false } 233 | }, 234 | "timestamp": "2022-02-19T00:27:48.403Z", 235 | "type": "ChoiceStateExited" 236 | }, 237 | { 238 | "id": 23, 239 | "previousEventId": 22, 240 | "stateEnteredEventDetails": { 241 | "input": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"},\"results\":{\"sentimentAnalysis\":{\"Sentiment\":\"POSITIVE\",\"SentimentScore\":{\"Mixed\":1.2647535E-4,\"Negative\":8.031699E-5,\"Neutral\":0.0051454515,\"Positive\":0.9946478}}}}", 242 | "inputDetails": { "truncated": false }, 243 | "name": "Add to FollowUp" 244 | }, 245 | "timestamp": "2022-02-19T00:27:48.403Z", 246 | "type": "TaskStateEntered" 247 | }, 248 | { 249 | "id": 24, 250 | "previousEventId": 23, 251 | "taskScheduledEventDetails": { 252 | "parameters": "{\"Item\":{\"PK\":{\"S\":\"jdoe@example.com\"}},\"TableName\":\"FollowUpTable\"}", 253 | "region": "us-east-1", 254 | "resource": "putItem", 255 | "resourceType": "dynamodb" 256 | }, 257 | "timestamp": "2022-02-19T00:27:48.404Z", 258 | "type": "TaskScheduled" 259 | }, 260 | { 261 | "id": 25, 262 | "previousEventId": 24, 263 | "taskStartedEventDetails": { 264 | "resource": "putItem", 265 | "resourceType": "dynamodb" 266 | }, 267 | "timestamp": "2022-02-19T00:27:48.404Z", 268 | "type": "TaskStarted" 269 | }, 270 | { 271 | "id": 26, 272 | "previousEventId": 25, 273 | "taskSucceededEventDetails": { 274 | "output": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"},\"results\":{\"dbUpdateStatusCode\":200}}", 275 | "outputDetails": { "truncated": false }, 276 | "resource": "putItem", 277 | "resourceType": "dynamodb" 278 | }, 279 | "timestamp": "2022-02-19T00:27:48.405Z", 280 | "type": "TaskSucceeded" 281 | }, 282 | { 283 | "id": 27, 284 | "previousEventId": 26, 285 | "stateExitedEventDetails": { 286 | "name": "Add to FollowUp", 287 | "output": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"},\"results\":{\"dbUpdateStatusCode\":200}}", 288 | "outputDetails": { "truncated": false } 289 | }, 290 | "timestamp": "2022-02-19T00:27:48.406Z", 291 | "type": "TaskStateExited" 292 | }, 293 | { 294 | "id": 28, 295 | "previousEventId": 27, 296 | "stateEnteredEventDetails": { 297 | "input": "{\"data\":{\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"identity\":{\"email\":\"jdoe@example.com\",\"ssn\":\"123-45-6789\"},\"address\":{\"street\":\"123 Main St\",\"city\":\"Columbus\",\"state\":\"OH\",\"zip\":\"43219\"},\"comments\":\"I am glad to sign-up for this service. Looking forward to different options.\"},\"results\":{\"dbUpdateStatusCode\":200}}", 298 | "inputDetails": { "truncated": false }, 299 | "name": "CustomerAddedToFollowup" 300 | }, 301 | "timestamp": "2022-02-19T00:27:48.406Z", 302 | "type": "TaskStateEntered" 303 | }, 304 | { 305 | "id": 29, 306 | "previousEventId": 28, 307 | "taskScheduledEventDetails": { 308 | "parameters": "{\"Entries\":[{\"Detail\":{\"Message\":\"Customer Added for follow up\",\"EmailAddress\":\"jdoe@example.com\"},\"DetailType\":\"CustomerAdded\",\"Source\":\"LocalTestingSource\"}]}", 309 | "region": "us-east-1", 310 | "resource": "putEvents", 311 | "resourceType": "events" 312 | }, 313 | "timestamp": "2022-02-19T00:27:48.407Z", 314 | "type": "TaskScheduled" 315 | }, 316 | { 317 | "id": 30, 318 | "previousEventId": 29, 319 | "taskStartedEventDetails": { 320 | "resource": "putEvents", 321 | "resourceType": "events" 322 | }, 323 | "timestamp": "2022-02-19T00:27:48.407Z", 324 | "type": "TaskStarted" 325 | }, 326 | { 327 | "id": 31, 328 | "previousEventId": 30, 329 | "taskSucceededEventDetails": { 330 | "output": "{\"StatusCode\":200,\"Payload\":{\"statusCode\":200}}", 331 | "outputDetails": { "truncated": false }, 332 | "resource": "putEvents", 333 | "resourceType": "events" 334 | }, 335 | "timestamp": "2022-02-19T00:27:48.407Z", 336 | "type": "TaskSucceeded" 337 | }, 338 | { 339 | "id": 32, 340 | "previousEventId": 31, 341 | "stateExitedEventDetails": { 342 | "name": "CustomerAddedToFollowup", 343 | "output": "{\"StatusCode\":200,\"Payload\":{\"statusCode\":200}}", 344 | "outputDetails": { "truncated": false } 345 | }, 346 | "timestamp": "2022-02-19T00:27:48.407Z", 347 | "type": "TaskStateExited" 348 | }, 349 | { 350 | "executionSucceededEventDetails": { 351 | "output": "{\"StatusCode\":200,\"Payload\":{\"statusCode\":200}}", 352 | "outputDetails": { "truncated": false } 353 | }, 354 | "id": 33, 355 | "previousEventId": 32, 356 | "timestamp": "2022-02-19T00:27:48.408Z", 357 | "type": "ExecutionSucceeded" 358 | } 359 | ] 360 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest', 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /lib/sales-leads-state-machine-construct.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; 3 | import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; 4 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 5 | import { JsonPath, TaskInput } from 'aws-cdk-lib/aws-stepfunctions'; 6 | import * as ddb from 'aws-cdk-lib/aws-dynamodb'; 7 | import { Duration } from 'aws-cdk-lib'; 8 | import { DynamoAttributeValue } from 'aws-cdk-lib/aws-stepfunctions-tasks'; 9 | 10 | type StateMachineProps = { 11 | stateMachineName?: string; 12 | validationStateName?: string; 13 | checkIdentityStateName?: string; 14 | checkAddressStateName?: string; 15 | customValidationFailedStateName?: string; 16 | validationExceptionStateName?: string; 17 | detectSentimentStateName?: string; 18 | isPositiveSentimentStateName?: string; 19 | negativeSentimentDetectedStateName?: string; 20 | addToFollowUpStateName?: string; 21 | customerAddedToFollowupStateName?: string; 22 | retryInterval?: Duration; 23 | }; 24 | 25 | export class SalesLeadsStateMachineConstruct extends Construct { 26 | constructor(scope: Construct, id: string, props: StateMachineProps = {}) { 27 | super(scope, id); 28 | 29 | const { 30 | validationStateName = 'Validation', 31 | checkIdentityStateName = 'Check Identity', 32 | checkAddressStateName = 'Check Address', 33 | customValidationFailedStateName = 'CustomValidationFailed', 34 | validationExceptionStateName = 'ValidationException', 35 | detectSentimentStateName = 'DetectSentiment', 36 | isPositiveSentimentStateName = 'Is Positive Sentiment?', 37 | negativeSentimentDetectedStateName = 'NegativeSentimentDetected', 38 | addToFollowUpStateName = 'Add to FollowUp', 39 | customerAddedToFollowupStateName = 'CustomerAddedToFollowup', 40 | stateMachineName = 'SalesLeadStateMachine', 41 | retryInterval = Duration.seconds(1), 42 | } = props; 43 | 44 | const checkIdentityState = new tasks.LambdaInvoke( 45 | this, 46 | checkIdentityStateName, 47 | { 48 | lambdaFunction: lambda.Function.fromFunctionArn( 49 | this, 50 | 'CheckIdentityFunction', 51 | 'arn:aws:lambda:us-west-2:123456789012:function:check-identity-function' 52 | ), 53 | inputPath: JsonPath.stringAt('$.data.identity'), 54 | payload: TaskInput.fromJsonPathAt('$'), 55 | resultSelector: { 56 | identity: JsonPath.stringToJson(JsonPath.stringAt('$.Payload.body')), 57 | }, 58 | retryOnServiceExceptions: false, 59 | } 60 | ).addRetry({ 61 | errors: [ 62 | 'Lambda.ServiceException', 63 | 'Lambda.AWSLambdaException', 64 | 'Lambda.SdkClientException', 65 | 'CustomValidationError', 66 | ], 67 | backoffRate: 1, 68 | maxAttempts: 3, 69 | interval: retryInterval, 70 | }); 71 | 72 | const checkAddressState = new tasks.LambdaInvoke( 73 | this, 74 | checkAddressStateName, 75 | { 76 | lambdaFunction: lambda.Function.fromFunctionArn( 77 | this, 78 | 'CheckAddressFunction', 79 | 'arn:aws:lambda:us-west-2:123456789012:function:check-address-function' 80 | ), 81 | inputPath: '$.data.address', 82 | payload: TaskInput.fromJsonPathAt('$'), 83 | resultSelector: { 84 | address: JsonPath.stringToJson(JsonPath.stringAt('$.Payload.body')), 85 | }, 86 | retryOnServiceExceptions: false, 87 | } 88 | ).addRetry({ 89 | errors: [ 90 | 'Lambda.ServiceException', 91 | 'Lambda.AWSLambdaException', 92 | 'Lambda.SdkClientException', 93 | 'CustomValidationError', 94 | ], 95 | backoffRate: 1, 96 | maxAttempts: 3, 97 | interval: retryInterval, 98 | }); 99 | 100 | const customValidationFailedState = new tasks.EventBridgePutEvents( 101 | this, 102 | customValidationFailedStateName, 103 | { 104 | entries: [ 105 | { 106 | detail: TaskInput.fromObject({ 107 | Message: 'Validation Failed', 108 | }), 109 | detailType: 'ValidationFailed', 110 | source: 'LocalTestingSource', 111 | }, 112 | ], 113 | } 114 | ); 115 | 116 | const validationExceptionState = new tasks.EventBridgePutEvents( 117 | this, 118 | validationExceptionStateName, 119 | { 120 | entries: [ 121 | { 122 | detail: TaskInput.fromObject({ 123 | Message: 'Validation Exception', 124 | }), 125 | detailType: 'ValidationException', 126 | source: 'LocalTestingSource', 127 | }, 128 | ], 129 | } 130 | ); 131 | 132 | const validation = new sfn.Parallel(this, validationStateName, { 133 | resultSelector: { 134 | identityResult: JsonPath.stringAt('$[0].identity'), 135 | addressResult: JsonPath.stringAt('$[1].address'), 136 | }, 137 | resultPath: JsonPath.stringAt('$.results'), 138 | }); 139 | 140 | const detectSentimentState = new sfn.CustomState( 141 | this, 142 | detectSentimentStateName, 143 | { 144 | stateJson: { 145 | Type: 'Task', 146 | Resource: 'arn:aws:states:::aws-sdk:comprehend:detectSentiment', 147 | Parameters: { 148 | LanguageCode: 'en', 149 | 'Text.$': JsonPath.stringAt('$.data.comments'), 150 | }, 151 | ResultSelector: { 152 | 'sentimentAnalysis.$': JsonPath.stringAt('$'), 153 | }, 154 | ResultPath: JsonPath.stringAt('$.results'), 155 | Retry: [ 156 | { 157 | ErrorEquals: ['InternalServerException'], 158 | IntervalSeconds: 1, 159 | MaxAttempts: 3, 160 | BackoffRate: 1, 161 | }, 162 | ], 163 | }, 164 | } 165 | ); 166 | 167 | const negativeSentimentDetectedState = new tasks.EventBridgePutEvents( 168 | this, 169 | negativeSentimentDetectedStateName, 170 | { 171 | entries: [ 172 | { 173 | detail: TaskInput.fromObject({ 174 | Message: 'Negative Sentiment Detected', 175 | Data: JsonPath.stringAt('$.data'), 176 | }), 177 | detailType: 'NegativeSentiment', 178 | source: 'LocalTestingSource', 179 | }, 180 | ], 181 | } 182 | ); 183 | 184 | const addToFollowUpState = new tasks.DynamoPutItem( 185 | this, 186 | addToFollowUpStateName, 187 | { 188 | inputPath: '$.data', 189 | table: ddb.Table.fromTableName(this, 'FollowUpTable', 'FollowUpTable'), 190 | item: { 191 | PK: DynamoAttributeValue.fromString( 192 | JsonPath.stringAt('$.identity.email') 193 | ), 194 | }, 195 | resultSelector: { 196 | dbUpdateStatusCode: JsonPath.stringAt( 197 | '$.SdkHttpMetadata.HttpStatusCode' 198 | ), 199 | }, 200 | resultPath: JsonPath.stringAt('$.results'), 201 | } 202 | ); 203 | 204 | const customerAddedToFollowupState = new tasks.EventBridgePutEvents( 205 | this, 206 | customerAddedToFollowupStateName, 207 | { 208 | inputPath: JsonPath.stringAt('$.data'), 209 | entries: [ 210 | { 211 | detail: TaskInput.fromObject({ 212 | Message: 'Customer Added for follow up', 213 | EmailAddress: JsonPath.stringAt('$.identity.email'), 214 | }), 215 | detailType: 'CustomerAdded', 216 | source: 'LocalTestingSource', 217 | }, 218 | ], 219 | } 220 | ); 221 | 222 | const isPositiveSentimentState = new sfn.Choice( 223 | this, 224 | isPositiveSentimentStateName 225 | ); 226 | 227 | const definition = validation 228 | .branch(checkIdentityState, checkAddressState) 229 | .addCatch(customValidationFailedState, { 230 | errors: ['CustomValidationError'], 231 | }) 232 | .addCatch(validationExceptionState, { errors: ['States.ALL'] }) 233 | .next(detectSentimentState) 234 | .next( 235 | isPositiveSentimentState 236 | .when( 237 | sfn.Condition.stringEquals( 238 | JsonPath.stringAt('$.results.sentimentAnalysis.Sentiment'), 239 | 'POSITIVE' 240 | ), 241 | addToFollowUpState.next(customerAddedToFollowupState) 242 | ) 243 | .otherwise(negativeSentimentDetectedState) 244 | ); 245 | 246 | const sm = new sfn.StateMachine(this, stateMachineName, { 247 | stateMachineName, 248 | definition, 249 | }); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /lib/state-machine-construct.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; 3 | import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; 4 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 5 | import { TaskInput } from 'aws-cdk-lib/aws-stepfunctions'; 6 | import * as sqs from 'aws-cdk-lib/aws-sqs'; 7 | import { Duration } from 'aws-cdk-lib'; 8 | 9 | type StateMachineProps = { 10 | stateMachineName?: string; 11 | lambdaStateName?: string; 12 | sqsStateName?: string; 13 | retryInterval?: Duration; 14 | }; 15 | 16 | export class StateMachineConstruct extends Construct { 17 | constructor(scope: Construct, id: string, props: StateMachineProps = {}) { 18 | super(scope, id); 19 | 20 | const { 21 | lambdaStateName = 'LambdaState', 22 | sqsStateName = 'SQSState', 23 | stateMachineName, 24 | retryInterval = Duration.seconds(2), 25 | } = props; 26 | 27 | const lambdaState = new tasks.LambdaInvoke(this, lambdaStateName, { 28 | lambdaFunction: lambda.Function.fromFunctionArn( 29 | this, 30 | 'lambdaInvoke', 31 | 'arn:aws:lambda:us-west-2:123456789012:function:my-function' 32 | ), 33 | payload: TaskInput.fromObject({ 34 | 'Payload.$': '$', 35 | FunctionName: 'HelloWorldFunction', 36 | }), 37 | retryOnServiceExceptions: false, 38 | }).addRetry({ 39 | errors: ['States.ALL'], 40 | backoffRate: 2, 41 | maxAttempts: 3, 42 | interval: retryInterval, 43 | }); 44 | 45 | const sqsState = new tasks.SqsSendMessage(this, sqsStateName, { 46 | queue: sqs.Queue.fromQueueArn( 47 | this, 48 | 'sqs', 49 | 'arn:aws:sqs:us-west-2:444455556666:queue1' 50 | ), 51 | messageBody: TaskInput.fromJsonPathAt('$'), 52 | }); 53 | 54 | const definition = lambdaState.next(sqsState); 55 | 56 | new sfn.StateMachine(this, 'SampleStateMachine', { 57 | stateMachineName, 58 | definition, 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/step-functions-testing-stack.ts: -------------------------------------------------------------------------------- 1 | import { Stack, StackProps } from 'aws-cdk-lib'; 2 | import { Construct } from 'constructs'; 3 | import { StateMachineConstruct } from './state-machine-construct'; 4 | // import * as sqs from 'aws-cdk-lib/aws-sqs'; 5 | 6 | export class StepFunctionsTestingStack extends Stack { 7 | constructor(scope: Construct, id: string, props?: StackProps) { 8 | super(scope, id, props); 9 | 10 | // The code that defines your stack goes here 11 | new StateMachineConstruct(this, 'StateMachine'); 12 | 13 | // example resource 14 | // const queue = new sqs.Queue(this, 'StepFunctionsTestingQueue', { 15 | // visibilityTimeout: cdk.Duration.seconds(300) 16 | // }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step-functions-testing", 3 | "version": "0.1.0", 4 | "bin": { 5 | "step-functions-testing": "bin/step-functions-testing.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^26.0.10", 15 | "@types/node": "10.17.27", 16 | "aws-cdk": "2.10.0", 17 | "jest": "^26.4.2", 18 | "stepfunctions-testing": "^0.1.0", 19 | "testcontainers": "^8.2.0", 20 | "tmp-promise": "^3.0.3", 21 | "ts-jest": "^26.2.0", 22 | "ts-node": "^9.0.0", 23 | "typescript": "~3.9.7" 24 | }, 25 | "dependencies": { 26 | "@aws-sdk/client-sfn": "^3.49.0", 27 | "aws-cdk-lib": "2.10.0", 28 | "constructs": "^10.0.0", 29 | "source-map-support": "^0.5.16" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/helpers/stepfunctions.ts: -------------------------------------------------------------------------------- 1 | import { HistoryEvent } from '@aws-sdk/client-sfn'; 2 | import { StepFunctionsMockConfig } from 'stepfunctions-testing'; 3 | import * as tmp from 'tmp-promise'; 4 | import { promises as fs } from 'fs'; 5 | import { StartedTestContainer } from 'testcontainers'; 6 | import { SFNClientWrapper } from './stepfunctions/client-wrapper'; 7 | import { createStepFunctionsLocalContainer } from './stepfunctions/local-container'; 8 | import { optimizeExecutionHistory } from './stepfunctions/execution-history'; 9 | 10 | export const createJestTestFromMockConfig = ( 11 | config: StepFunctionsMockConfig, 12 | aslDefinition: string 13 | ) => { 14 | return () => { 15 | for (const testDefinition of config.collectStateMachineTestDefinitions()) { 16 | describe(testDefinition.stateMachineName, () => { 17 | let tmpDir: tmp.DirectoryResult; 18 | let container: StartedTestContainer | undefined; 19 | 20 | let client: SFNClientWrapper; 21 | let stateMachineArn = ''; 22 | 23 | beforeAll(async () => { 24 | const mockConfigPath = await tmp.tmpName(); 25 | await fs.writeFile(mockConfigPath, JSON.stringify(config.toJson())); 26 | container = await createStepFunctionsLocalContainer(mockConfigPath); 27 | 28 | // create the state machine 29 | client = new SFNClientWrapper({ 30 | endpoint: `http://localhost:${container?.getMappedPort(8083)}`, 31 | }); 32 | const result = await client.createStateMachine( 33 | testDefinition.stateMachineName, 34 | aslDefinition 35 | ); 36 | 37 | // save for later usage 38 | stateMachineArn = result.stateMachineArn!; 39 | }, 30_000); 40 | 41 | afterAll(async () => { 42 | await container?.stop(); 43 | await tmpDir.cleanup(); 44 | }); 45 | 46 | for (const testCase of testDefinition.collectTestCase()) { 47 | test( 48 | testCase.name, 49 | async () => { 50 | // Arrange 51 | // Act 52 | const startExecutionResponse = 53 | await client.startExecutionFromTestCase( 54 | stateMachineArn, 55 | testCase 56 | ); 57 | await client.waitExecutionStop( 58 | startExecutionResponse.executionArn! 59 | ); 60 | 61 | // Assert 62 | const { events: rawEvents = [] } = 63 | await client.getExecutionHistory( 64 | startExecutionResponse.executionArn! 65 | ); 66 | console.log(JSON.stringify(rawEvents)); 67 | const results = await optimizeExecutionHistory(rawEvents); 68 | 69 | console.log(JSON.stringify(results)); 70 | }, 71 | 30_000 72 | ); 73 | } 74 | }); 75 | } 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /test/helpers/stepfunctions/cdk-to-asl.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import { Template } from 'aws-cdk-lib/assertions'; 3 | 4 | const AWS_PARTITION = process.env.AWS_PARTITION || 'aws'; 5 | const AWS_URL_SUFFIX = process.env.AWS_URL_SUFFIX || 'amazonaws.com'; 6 | 7 | // 99% copied from 8 | // https://github.com/nathanagez/aws-cdk-state-machine-asl 9 | export const extractStateMachineAsls = (stack: cdk.Stack) => { 10 | const { Resources: resources = {} } = Template.fromStack(stack).toJSON(); 11 | 12 | const stateMachineResources = Object.keys(resources) 13 | .filter((resourceKey) => { 14 | const resource = resources[resourceKey]; 15 | return resource && resource.Type === 'AWS::StepFunctions::StateMachine'; 16 | }) 17 | .map((resource) => resources[resource]); 18 | 19 | return stateMachineResources.map((resource) => { 20 | const definitionString = resource.Properties.DefinitionString; 21 | const [delimiter, values] = definitionString['Fn::Join']; 22 | const resolvedExpressions = resolveExpressions(values); 23 | return resolvedExpressions.join(delimiter); 24 | }); 25 | }; 26 | 27 | const resolveExpressions = (expressions: any) => { 28 | const resolvers = [ 29 | { 30 | name: 'Ref', 31 | resolve: ref, 32 | }, 33 | ]; 34 | return expressions.map((expression: any) => { 35 | if (typeof expression === 'string') return expression; 36 | 37 | for (const resolver of resolvers) { 38 | if (expression.hasOwnProperty(resolver.name)) { 39 | return resolver.resolve(expression[resolver.name]); 40 | } 41 | } 42 | return expression; 43 | }); 44 | }; 45 | 46 | function ref(value: any) { 47 | switch (value) { 48 | case 'AWS::Partition': 49 | return AWS_PARTITION; 50 | case 'AWS::URLSuffix': 51 | return AWS_URL_SUFFIX; 52 | default: 53 | console.warn(`Could not resolve expression: ${value}`); 54 | return value; // unresolved 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/helpers/stepfunctions/client-wrapper.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CreateStateMachineCommand, 3 | DescribeExecutionCommand, 4 | GetExecutionHistoryCommand, 5 | SFNClient, 6 | SFNClientConfig, 7 | StartExecutionCommand, 8 | } from '@aws-sdk/client-sfn'; 9 | import { StateMachineTestCase } from 'stepfunctions-testing'; 10 | 11 | type SFNClientWrapperProps = {} & SFNClientConfig; 12 | export class SFNClientWrapper { 13 | client: SFNClient; 14 | constructor(props: SFNClientWrapperProps) { 15 | this.client = new SFNClient(props); 16 | } 17 | 18 | async createStateMachine(stateMachineName: string, definition: string) { 19 | const dummyRoleArn = 20 | 'arn:aws:iam::123456789012:role/service-role/LambdaSQSIntegration'; 21 | 22 | const createStateMachineCommand = new CreateStateMachineCommand({ 23 | name: stateMachineName, 24 | definition, 25 | roleArn: dummyRoleArn, 26 | }); 27 | return await this.client.send(createStateMachineCommand); 28 | } 29 | 30 | async startExecutionFromTestCase( 31 | stateMachineArn: string, 32 | testCase: StateMachineTestCase 33 | ) { 34 | const startExecutionCmd = new StartExecutionCommand({ 35 | name: `executionWith${testCase.name}`, 36 | stateMachineArn: `${stateMachineArn}#${testCase.name}`, 37 | input: JSON.stringify(testCase.input), 38 | }); 39 | return await this.client.send(startExecutionCmd); 40 | } 41 | 42 | async waitExecutionStop(executionArn: string, retry: number = 30) { 43 | const describeExecCmd = new DescribeExecutionCommand({ 44 | executionArn: executionArn, 45 | }); 46 | 47 | for (let i = 0; i < retry; i++) { 48 | const result = await this.client.send(describeExecCmd); 49 | if (result.stopDate) break; 50 | await this.wait(1); 51 | } 52 | } 53 | 54 | async getExecutionHistory(executionArn: string) { 55 | const getExecutionHistoryCmd = new GetExecutionHistoryCommand({ 56 | executionArn, 57 | maxResults: 1000, 58 | }); 59 | return await this.client.send(getExecutionHistoryCmd); 60 | } 61 | 62 | private async wait(seconds: number) { 63 | return new Promise((resolve) => { 64 | setTimeout(() => { 65 | resolve(null); 66 | }, seconds * 1000); 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/helpers/stepfunctions/execution-history.test.ts: -------------------------------------------------------------------------------- 1 | import { optimizeExecutionHistory } from './execution-history'; 2 | import { testCases } from './execution-history-data'; 3 | describe('Optimize Execution History', () => { 4 | test.each(testCases.map((v) => [v.input, v.output]))( 5 | '', 6 | (input, expected) => { 7 | // act 8 | const actual = optimizeExecutionHistory(input as any); 9 | 10 | expect(actual).toEqual(expected); 11 | } 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /test/helpers/stepfunctions/execution-history.ts: -------------------------------------------------------------------------------- 1 | import { HistoryEvent } from '@aws-sdk/client-sfn'; 2 | 3 | const typeMaps = { 4 | ActivityFailed: 'activityFailedEventDetails', 5 | ActivityScheduled: 'activityScheduledEventDetails', 6 | ActivityScheduleFailed: 'activityScheduleFailedEventDetails', 7 | ActivityStarted: 'activityStartedEventDetails', 8 | ActivitySucceeded: 'activitySucceededEventDetails', 9 | ActivityTimedOut: 'activityTimedOutEventDetails', 10 | ChoiceStateEntered: 'stateEnteredEventDetails', 11 | ChoiceStateExited: 'stateExitedEventDetails', 12 | ExecutionAborted: 'executionAbortedEventDetails', 13 | ExecutionFailed: 'executionFailedEventDetails', 14 | ExecutionStarted: 'executionStartedEventDetails', 15 | ExecutionSucceeded: 'executionSucceededEventDetails', 16 | ExecutionTimedOut: 'executionTimedOutEventDetails', 17 | FailStateEntered: '', 18 | LambdaFunctionFailed: 'lambdaFunctionFailedEventDetails', 19 | LambdaFunctionScheduled: 'lambdaFunctionScheduledEventDetails', 20 | LambdaFunctionScheduleFailed: 'lambdaFunctionScheduleFailedEventDetails', 21 | LambdaFunctionStarted: '', 22 | LambdaFunctionStartFailed: 'lambdaFunctionStartFailedEventDetails', 23 | LambdaFunctionSucceeded: 'lambdaFunctionSucceededEventDetails', 24 | LambdaFunctionTimedOut: 'lambdaFunctionTimedOutEventDetails', 25 | MapIterationAborted: 'mapIterationAbortedEventDetails', 26 | MapIterationFailed: 'mapIterationFailedEventDetails', 27 | MapIterationStarted: 'mapIterationStartedEventDetails', 28 | MapIterationSucceeded: 'mapIterationSucceededEventDetails', 29 | MapStateAborted: '', 30 | MapStateEntered: '', // TODO: probably maps to stateEnteredEventDetails 31 | MapStateExited: '', // TODO: probably maps to stateExitedEventDetails 32 | MapStateFailed: '', 33 | MapStateStarted: 'mapStateStartedEventDetails', 34 | MapStateSucceeded: '', 35 | ParallelStateAborted: '', 36 | ParallelStateEntered: 'stateEnteredEventDetails', 37 | ParallelStateExited: 'stateExitedEventDetails', 38 | ParallelStateFailed: '', 39 | ParallelStateStarted: '', 40 | ParallelStateSucceeded: '', 41 | PassStateEntered: '', 42 | PassStateExited: '', 43 | SucceedStateEntered: '', 44 | SucceedStateExited: '', 45 | TaskFailed: 'taskFailedEventDetails', 46 | TaskScheduled: 'taskScheduledEventDetails', 47 | TaskStarted: 'taskStartedEventDetails', 48 | TaskStartFailed: 'taskStartFailedEventDetails', 49 | TaskStateAborted: '', 50 | TaskStateEntered: 'stateEnteredEventDetails', 51 | TaskStateExited: 'stateExitedEventDetails', 52 | TaskSubmitFailed: 'taskSubmitFailedEventDetails', 53 | TaskSubmitted: 'taskSubmittedEventDetails', 54 | TaskSucceeded: 'taskSucceededEventDetails', 55 | TaskTimedOut: 'taskTimedOutEventDetails', 56 | WaitStateAborted: '', 57 | WaitStateEntered: '', 58 | WaitStateExited: '', 59 | } as { [key: string]: string }; 60 | 61 | type NextEventIdsMap = { 62 | [key: string]: string[]; 63 | }; 64 | 65 | type EventsById = { 66 | [eventId: string]: HistoryEvent; 67 | }; 68 | 69 | export const optimizeExecutionHistory = (rawEvents: HistoryEvent[]) => { 70 | // TODO: think of an efficient way to do this 71 | const { nextEventIds: nextEventIdsMap, eventsById: normalizedEvents } = 72 | rawEvents.reduce( 73 | (obj, e) => { 74 | const { nextEventIds, eventsById: normalizedEvents } = obj; 75 | 76 | const id = e.id!.toString(); 77 | const previousId = e.previousEventId!.toString(); 78 | 79 | if (nextEventIds[previousId]) { 80 | nextEventIds[previousId].push(id); 81 | } else { 82 | nextEventIds[previousId] = [id]; 83 | } 84 | 85 | normalizedEvents[id] = e; 86 | 87 | return obj; 88 | }, 89 | { nextEventIds: {}, eventsById: {} } as { 90 | nextEventIds: NextEventIdsMap; 91 | eventsById: EventsById; 92 | } 93 | ); 94 | 95 | // console.log('next events', JSON.stringify(nextEvents)); 96 | 97 | let [results, nextKey] = findNext('0', normalizedEvents, nextEventIdsMap); 98 | while (nextKey) { 99 | const [res, next] = findNext(nextKey, normalizedEvents, nextEventIdsMap); 100 | results = results.concat(res); 101 | nextKey = next; 102 | } 103 | 104 | // console.log('results', JSON.stringify(results)); 105 | return results; 106 | }; 107 | 108 | function findNext( 109 | key: string, 110 | eventsById: EventsById, 111 | nextEventIdsMap: NextEventIdsMap 112 | ): [any[], string?] { 113 | const events = [] as any[]; 114 | const initialEvent = eventsById[key]; 115 | if (initialEvent) { 116 | const detail = getEventDetail(initialEvent); 117 | events.push({ type: initialEvent.type!, ...(detail && { detail }) }); 118 | } 119 | 120 | let nextEventIds = nextEventIdsMap[key]; 121 | while (nextEventIds && nextEventIds.length > 0) { 122 | if (nextEventIds.length > 1) { 123 | // if there are more than 2 events next, it is a parallel. 124 | // parallel events are described as objects 125 | const results = {} as { [key: string]: any[] }; 126 | let continueKey = ''; // only one continue key should happen. I think... 127 | for (let nextEventId of nextEventIds) { 128 | let [subsequentEvents, nextKey] = findNext( 129 | nextEventId, 130 | eventsById, 131 | nextEventIdsMap 132 | ); 133 | if (nextKey) { 134 | continueKey = nextKey; 135 | } 136 | 137 | const targetEvent = eventsById[nextEventId]; 138 | const name = 139 | targetEvent.stateEnteredEventDetails?.name || 140 | targetEvent.stateExitedEventDetails?.name; 141 | 142 | results[`${targetEvent.type}${name ? ` ${name}` : ''}`] = 143 | subsequentEvents; // TODO: not sure if this naming convention has collisions or not 144 | } 145 | events.push(results); 146 | 147 | if (continueKey) return [events, continueKey]; // if there was a continueKey. End the array here. 148 | 149 | break; 150 | } 151 | 152 | const nextEventId = nextEventIds[0]; 153 | const targetEvent = eventsById[nextEventId]; 154 | if (targetEvent.type === 'ParallelStateSucceeded') { 155 | // Parallel screws things( from snapshot perspectives) because of inconsistent order. 156 | // Break the sequence here with a nextEventId 157 | return [events, nextEventId]; 158 | } 159 | 160 | const detail = getEventDetail(targetEvent); 161 | events.push({ type: targetEvent.type, ...(detail && { detail }) }); 162 | nextEventIds = nextEventIdsMap[nextEventId]; 163 | } 164 | return [events]; 165 | } 166 | 167 | function getEventDetail(event: HistoryEvent): any { 168 | return (event as any)[typeMaps[event.type!]]; 169 | } 170 | -------------------------------------------------------------------------------- /test/helpers/stepfunctions/local-container.ts: -------------------------------------------------------------------------------- 1 | import { GenericContainer } from 'testcontainers'; 2 | 3 | export const createStepFunctionsLocalContainer = async ( 4 | mockConfigPath: string 5 | ) => { 6 | const container = await new GenericContainer('amazon/aws-stepfunctions-local') 7 | .withExposedPorts(8083) 8 | .withBindMount( 9 | mockConfigPath, 10 | '/home/StepFunctionsLocal/MockConfigFile.json', 11 | 'ro' 12 | ) 13 | .withEnv('SFN_MOCK_CONFIG', '/home/StepFunctionsLocal/MockConfigFile.json') 14 | // .withEnv('AWS_ACCESS_KEY_ID', process.env.AWS_ACCESS_KEY_ID as string) 15 | // .withEnv( 16 | // 'AWS_SECRET_ACCESS_KEY', 17 | // process.env.AWS_SECRET_ACCESS_KEY as string 18 | // ) 19 | // For federated credentials (for example, SSO), this environment variable is required. 20 | // .withEnv('AWS_SESSION_TOKEN', process.env.AWS_SESSION_TOKEN as string) 21 | // .withEnv('AWS_DEFAULT_REGION', process.env.AWS_REGION) 22 | .start(); 23 | return container; 24 | }; 25 | -------------------------------------------------------------------------------- /test/stepfunctions/__snapshots__/lambda-sqs-integration.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Lambda SQS integration LambdaSQSIntegration HappyPath 1`] = ` 4 | Array [ 5 | Object { 6 | "ExecutionStarted": Array [ 7 | Object { 8 | "detail": Object { 9 | "input": "{}", 10 | "inputDetails": Object { 11 | "truncated": false, 12 | }, 13 | "roleArn": "arn:aws:iam::123456789012:role/service-role/LambdaSQSIntegration", 14 | }, 15 | "type": "ExecutionStarted", 16 | }, 17 | ], 18 | "TaskStateEnteredLambdaState": Array [ 19 | Object { 20 | "detail": Object { 21 | "input": "{}", 22 | "inputDetails": Object { 23 | "truncated": false, 24 | }, 25 | "name": "LambdaState", 26 | }, 27 | "type": "TaskStateEntered", 28 | }, 29 | Object { 30 | "detail": Object { 31 | "heartbeatInSeconds": undefined, 32 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:my-function\\",\\"Payload\\":{\\"FunctionName\\":\\"HelloWorldFunction\\",\\"Payload\\":{}}}", 33 | "region": "us-east-1", 34 | "resource": "invoke", 35 | "resourceType": "lambda", 36 | "timeoutInSeconds": undefined, 37 | }, 38 | "type": "TaskScheduled", 39 | }, 40 | Object { 41 | "detail": Object { 42 | "resource": "invoke", 43 | "resourceType": "lambda", 44 | }, 45 | "type": "TaskStarted", 46 | }, 47 | Object { 48 | "detail": Object { 49 | "output": "{\\"StatusCode\\":200,\\"Payload\\":{\\"StatusCode\\":200,\\"body\\":\\"Hello from Lambda!\\"}}", 50 | "outputDetails": Object { 51 | "truncated": false, 52 | }, 53 | "resource": "invoke", 54 | "resourceType": "lambda", 55 | }, 56 | "type": "TaskSucceeded", 57 | }, 58 | Object { 59 | "detail": Object { 60 | "name": "LambdaState", 61 | "output": "{\\"StatusCode\\":200,\\"Payload\\":{\\"StatusCode\\":200,\\"body\\":\\"Hello from Lambda!\\"}}", 62 | "outputDetails": Object { 63 | "truncated": false, 64 | }, 65 | }, 66 | "type": "TaskStateExited", 67 | }, 68 | Object { 69 | "detail": Object { 70 | "input": "{\\"StatusCode\\":200,\\"Payload\\":{\\"StatusCode\\":200,\\"body\\":\\"Hello from Lambda!\\"}}", 71 | "inputDetails": Object { 72 | "truncated": false, 73 | }, 74 | "name": "SqsState", 75 | }, 76 | "type": "TaskStateEntered", 77 | }, 78 | Object { 79 | "detail": Object { 80 | "heartbeatInSeconds": undefined, 81 | "parameters": "{\\"QueueUrl\\":\\"https://sqs.us-west-2.amazonaws.com/444455556666/queue1\\",\\"MessageBody\\":{\\"StatusCode\\":200,\\"Payload\\":{\\"StatusCode\\":200,\\"body\\":\\"Hello from Lambda!\\"}}}", 82 | "region": "us-east-1", 83 | "resource": "sendMessage", 84 | "resourceType": "sqs", 85 | "timeoutInSeconds": undefined, 86 | }, 87 | "type": "TaskScheduled", 88 | }, 89 | Object { 90 | "detail": Object { 91 | "resource": "sendMessage", 92 | "resourceType": "sqs", 93 | }, 94 | "type": "TaskStarted", 95 | }, 96 | Object { 97 | "detail": Object { 98 | "output": "{\\"MD5OfMessageBody\\":\\"3bcb6e8e-7h85-4375-b0bc-1a59812c6e51\\",\\"MessageId\\":\\"3bcb6e8e-8b51-4375-b0bc-1a59812c6e51\\"}", 99 | "outputDetails": Object { 100 | "truncated": false, 101 | }, 102 | "resource": "sendMessage", 103 | "resourceType": "sqs", 104 | }, 105 | "type": "TaskSucceeded", 106 | }, 107 | Object { 108 | "detail": Object { 109 | "name": "SqsState", 110 | "output": "{\\"MD5OfMessageBody\\":\\"3bcb6e8e-7h85-4375-b0bc-1a59812c6e51\\",\\"MessageId\\":\\"3bcb6e8e-8b51-4375-b0bc-1a59812c6e51\\"}", 111 | "outputDetails": Object { 112 | "truncated": false, 113 | }, 114 | }, 115 | "type": "TaskStateExited", 116 | }, 117 | Object { 118 | "detail": Object { 119 | "output": "{\\"MD5OfMessageBody\\":\\"3bcb6e8e-7h85-4375-b0bc-1a59812c6e51\\",\\"MessageId\\":\\"3bcb6e8e-8b51-4375-b0bc-1a59812c6e51\\"}", 120 | "outputDetails": Object { 121 | "truncated": false, 122 | }, 123 | }, 124 | "type": "ExecutionSucceeded", 125 | }, 126 | ], 127 | }, 128 | ] 129 | `; 130 | 131 | exports[`Lambda SQS integration LambdaSQSIntegration RetryPath 1`] = ` 132 | Array [ 133 | Object { 134 | "ExecutionStarted": Array [ 135 | Object { 136 | "detail": Object { 137 | "input": "{}", 138 | "inputDetails": Object { 139 | "truncated": false, 140 | }, 141 | "roleArn": "arn:aws:iam::123456789012:role/service-role/LambdaSQSIntegration", 142 | }, 143 | "type": "ExecutionStarted", 144 | }, 145 | ], 146 | "TaskStateEnteredLambdaState": Array [ 147 | Object { 148 | "detail": Object { 149 | "input": "{}", 150 | "inputDetails": Object { 151 | "truncated": false, 152 | }, 153 | "name": "LambdaState", 154 | }, 155 | "type": "TaskStateEntered", 156 | }, 157 | Object { 158 | "detail": Object { 159 | "heartbeatInSeconds": undefined, 160 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:my-function\\",\\"Payload\\":{\\"FunctionName\\":\\"HelloWorldFunction\\",\\"Payload\\":{}}}", 161 | "region": "us-east-1", 162 | "resource": "invoke", 163 | "resourceType": "lambda", 164 | "timeoutInSeconds": undefined, 165 | }, 166 | "type": "TaskScheduled", 167 | }, 168 | Object { 169 | "detail": Object { 170 | "resource": "invoke", 171 | "resourceType": "lambda", 172 | }, 173 | "type": "TaskStarted", 174 | }, 175 | Object { 176 | "detail": Object { 177 | "cause": "Lambda resource is not ready.", 178 | "error": "Lambda.ResourceNotReadyException", 179 | "resource": "invoke", 180 | "resourceType": "lambda", 181 | }, 182 | "type": "TaskFailed", 183 | }, 184 | Object { 185 | "detail": Object { 186 | "heartbeatInSeconds": undefined, 187 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:my-function\\",\\"Payload\\":{\\"FunctionName\\":\\"HelloWorldFunction\\",\\"Payload\\":{}}}", 188 | "region": "us-east-1", 189 | "resource": "invoke", 190 | "resourceType": "lambda", 191 | "timeoutInSeconds": undefined, 192 | }, 193 | "type": "TaskScheduled", 194 | }, 195 | Object { 196 | "detail": Object { 197 | "resource": "invoke", 198 | "resourceType": "lambda", 199 | }, 200 | "type": "TaskStarted", 201 | }, 202 | Object { 203 | "detail": Object { 204 | "cause": "Lambda timed out.", 205 | "error": "Lambda.TimeoutException", 206 | "resource": "invoke", 207 | "resourceType": "lambda", 208 | }, 209 | "type": "TaskFailed", 210 | }, 211 | Object { 212 | "detail": Object { 213 | "heartbeatInSeconds": undefined, 214 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:my-function\\",\\"Payload\\":{\\"FunctionName\\":\\"HelloWorldFunction\\",\\"Payload\\":{}}}", 215 | "region": "us-east-1", 216 | "resource": "invoke", 217 | "resourceType": "lambda", 218 | "timeoutInSeconds": undefined, 219 | }, 220 | "type": "TaskScheduled", 221 | }, 222 | Object { 223 | "detail": Object { 224 | "resource": "invoke", 225 | "resourceType": "lambda", 226 | }, 227 | "type": "TaskStarted", 228 | }, 229 | Object { 230 | "detail": Object { 231 | "cause": "Lambda timed out.", 232 | "error": "Lambda.TimeoutException", 233 | "resource": "invoke", 234 | "resourceType": "lambda", 235 | }, 236 | "type": "TaskFailed", 237 | }, 238 | Object { 239 | "detail": Object { 240 | "heartbeatInSeconds": undefined, 241 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:my-function\\",\\"Payload\\":{\\"FunctionName\\":\\"HelloWorldFunction\\",\\"Payload\\":{}}}", 242 | "region": "us-east-1", 243 | "resource": "invoke", 244 | "resourceType": "lambda", 245 | "timeoutInSeconds": undefined, 246 | }, 247 | "type": "TaskScheduled", 248 | }, 249 | Object { 250 | "detail": Object { 251 | "resource": "invoke", 252 | "resourceType": "lambda", 253 | }, 254 | "type": "TaskStarted", 255 | }, 256 | Object { 257 | "detail": Object { 258 | "output": "{\\"StatusCode\\":200,\\"Payload\\":{\\"StatusCode\\":200,\\"body\\":\\"Hello from Lambda!\\"}}", 259 | "outputDetails": Object { 260 | "truncated": false, 261 | }, 262 | "resource": "invoke", 263 | "resourceType": "lambda", 264 | }, 265 | "type": "TaskSucceeded", 266 | }, 267 | Object { 268 | "detail": Object { 269 | "name": "LambdaState", 270 | "output": "{\\"StatusCode\\":200,\\"Payload\\":{\\"StatusCode\\":200,\\"body\\":\\"Hello from Lambda!\\"}}", 271 | "outputDetails": Object { 272 | "truncated": false, 273 | }, 274 | }, 275 | "type": "TaskStateExited", 276 | }, 277 | Object { 278 | "detail": Object { 279 | "input": "{\\"StatusCode\\":200,\\"Payload\\":{\\"StatusCode\\":200,\\"body\\":\\"Hello from Lambda!\\"}}", 280 | "inputDetails": Object { 281 | "truncated": false, 282 | }, 283 | "name": "SqsState", 284 | }, 285 | "type": "TaskStateEntered", 286 | }, 287 | Object { 288 | "detail": Object { 289 | "heartbeatInSeconds": undefined, 290 | "parameters": "{\\"QueueUrl\\":\\"https://sqs.us-west-2.amazonaws.com/444455556666/queue1\\",\\"MessageBody\\":{\\"StatusCode\\":200,\\"Payload\\":{\\"StatusCode\\":200,\\"body\\":\\"Hello from Lambda!\\"}}}", 291 | "region": "us-east-1", 292 | "resource": "sendMessage", 293 | "resourceType": "sqs", 294 | "timeoutInSeconds": undefined, 295 | }, 296 | "type": "TaskScheduled", 297 | }, 298 | Object { 299 | "detail": Object { 300 | "resource": "sendMessage", 301 | "resourceType": "sqs", 302 | }, 303 | "type": "TaskStarted", 304 | }, 305 | Object { 306 | "detail": Object { 307 | "output": "{\\"MD5OfMessageBody\\":\\"3bcb6e8e-7h85-4375-b0bc-1a59812c6e51\\",\\"MessageId\\":\\"3bcb6e8e-8b51-4375-b0bc-1a59812c6e51\\"}", 308 | "outputDetails": Object { 309 | "truncated": false, 310 | }, 311 | "resource": "sendMessage", 312 | "resourceType": "sqs", 313 | }, 314 | "type": "TaskSucceeded", 315 | }, 316 | Object { 317 | "detail": Object { 318 | "name": "SqsState", 319 | "output": "{\\"MD5OfMessageBody\\":\\"3bcb6e8e-7h85-4375-b0bc-1a59812c6e51\\",\\"MessageId\\":\\"3bcb6e8e-8b51-4375-b0bc-1a59812c6e51\\"}", 320 | "outputDetails": Object { 321 | "truncated": false, 322 | }, 323 | }, 324 | "type": "TaskStateExited", 325 | }, 326 | Object { 327 | "detail": Object { 328 | "output": "{\\"MD5OfMessageBody\\":\\"3bcb6e8e-7h85-4375-b0bc-1a59812c6e51\\",\\"MessageId\\":\\"3bcb6e8e-8b51-4375-b0bc-1a59812c6e51\\"}", 329 | "outputDetails": Object { 330 | "truncated": false, 331 | }, 332 | }, 333 | "type": "ExecutionSucceeded", 334 | }, 335 | ], 336 | }, 337 | ] 338 | `; 339 | -------------------------------------------------------------------------------- /test/stepfunctions/__snapshots__/sales-lead-state-machine.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`SalesLeadsStateMachine LocalTesting CustomValidationFailedCatchTest 1`] = ` 4 | Array [ 5 | Object { 6 | "ExecutionStarted": Array [ 7 | Object { 8 | "detail": Object { 9 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 10 | "inputDetails": Object { 11 | "truncated": false, 12 | }, 13 | "roleArn": "arn:aws:iam::123456789012:role/service-role/LambdaSQSIntegration", 14 | }, 15 | "type": "ExecutionStarted", 16 | }, 17 | ], 18 | "ParallelStateEnteredValidation": Array [ 19 | Object { 20 | "detail": Object { 21 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 22 | "inputDetails": Object { 23 | "truncated": false, 24 | }, 25 | "name": "Validation", 26 | }, 27 | "type": "ParallelStateEntered", 28 | }, 29 | Object { 30 | "ParallelStateStarted": Array [ 31 | Object { 32 | "detail": undefined, 33 | "type": "ParallelStateStarted", 34 | }, 35 | Object { 36 | "TaskStateEnteredCheck Address": Array [ 37 | Object { 38 | "detail": Object { 39 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 40 | "inputDetails": Object { 41 | "truncated": false, 42 | }, 43 | "name": "Check Address", 44 | }, 45 | "type": "TaskStateEntered", 46 | }, 47 | Object { 48 | "detail": Object { 49 | "heartbeatInSeconds": undefined, 50 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-address-function\\",\\"Payload\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"}}", 51 | "region": "us-east-1", 52 | "resource": "invoke", 53 | "resourceType": "lambda", 54 | "timeoutInSeconds": undefined, 55 | }, 56 | "type": "TaskScheduled", 57 | }, 58 | Object { 59 | "detail": Object { 60 | "resource": "invoke", 61 | "resourceType": "lambda", 62 | }, 63 | "type": "TaskStarted", 64 | }, 65 | Object { 66 | "detail": Object { 67 | "output": "{\\"address\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"}}", 68 | "outputDetails": Object { 69 | "truncated": false, 70 | }, 71 | "resource": "invoke", 72 | "resourceType": "lambda", 73 | }, 74 | "type": "TaskSucceeded", 75 | }, 76 | Object { 77 | "detail": Object { 78 | "name": "Check Address", 79 | "output": "{\\"address\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"}}", 80 | "outputDetails": Object { 81 | "truncated": false, 82 | }, 83 | }, 84 | "type": "TaskStateExited", 85 | }, 86 | ], 87 | "TaskStateEnteredCheck Identity": Array [ 88 | Object { 89 | "detail": Object { 90 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 91 | "inputDetails": Object { 92 | "truncated": false, 93 | }, 94 | "name": "Check Identity", 95 | }, 96 | "type": "TaskStateEntered", 97 | }, 98 | Object { 99 | "detail": Object { 100 | "heartbeatInSeconds": undefined, 101 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-identity-function\\",\\"Payload\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"}}", 102 | "region": "us-east-1", 103 | "resource": "invoke", 104 | "resourceType": "lambda", 105 | "timeoutInSeconds": undefined, 106 | }, 107 | "type": "TaskScheduled", 108 | }, 109 | Object { 110 | "detail": Object { 111 | "resource": "invoke", 112 | "resourceType": "lambda", 113 | }, 114 | "type": "TaskStarted", 115 | }, 116 | Object { 117 | "detail": Object { 118 | "cause": "Check Identity Validation Failed", 119 | "error": "CustomValidationError", 120 | "resource": "invoke", 121 | "resourceType": "lambda", 122 | }, 123 | "type": "TaskFailed", 124 | }, 125 | Object { 126 | "detail": Object { 127 | "heartbeatInSeconds": undefined, 128 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-identity-function\\",\\"Payload\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"}}", 129 | "region": "us-east-1", 130 | "resource": "invoke", 131 | "resourceType": "lambda", 132 | "timeoutInSeconds": undefined, 133 | }, 134 | "type": "TaskScheduled", 135 | }, 136 | Object { 137 | "detail": Object { 138 | "resource": "invoke", 139 | "resourceType": "lambda", 140 | }, 141 | "type": "TaskStarted", 142 | }, 143 | Object { 144 | "detail": Object { 145 | "cause": "Check Identity Validation Failed", 146 | "error": "CustomValidationError", 147 | "resource": "invoke", 148 | "resourceType": "lambda", 149 | }, 150 | "type": "TaskFailed", 151 | }, 152 | Object { 153 | "detail": Object { 154 | "heartbeatInSeconds": undefined, 155 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-identity-function\\",\\"Payload\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"}}", 156 | "region": "us-east-1", 157 | "resource": "invoke", 158 | "resourceType": "lambda", 159 | "timeoutInSeconds": undefined, 160 | }, 161 | "type": "TaskScheduled", 162 | }, 163 | Object { 164 | "detail": Object { 165 | "resource": "invoke", 166 | "resourceType": "lambda", 167 | }, 168 | "type": "TaskStarted", 169 | }, 170 | Object { 171 | "detail": Object { 172 | "cause": "Check Identity Validation Failed", 173 | "error": "CustomValidationError", 174 | "resource": "invoke", 175 | "resourceType": "lambda", 176 | }, 177 | "type": "TaskFailed", 178 | }, 179 | Object { 180 | "detail": Object { 181 | "heartbeatInSeconds": undefined, 182 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-identity-function\\",\\"Payload\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"}}", 183 | "region": "us-east-1", 184 | "resource": "invoke", 185 | "resourceType": "lambda", 186 | "timeoutInSeconds": undefined, 187 | }, 188 | "type": "TaskScheduled", 189 | }, 190 | Object { 191 | "detail": Object { 192 | "resource": "invoke", 193 | "resourceType": "lambda", 194 | }, 195 | "type": "TaskStarted", 196 | }, 197 | Object { 198 | "detail": Object { 199 | "cause": "Check Identity Validation Failed", 200 | "error": "CustomValidationError", 201 | "resource": "invoke", 202 | "resourceType": "lambda", 203 | }, 204 | "type": "TaskFailed", 205 | }, 206 | Object { 207 | "detail": undefined, 208 | "type": "ParallelStateFailed", 209 | }, 210 | ], 211 | }, 212 | ], 213 | "TaskStateExitedValidation": Array [ 214 | Object { 215 | "detail": Object { 216 | "name": "Validation", 217 | "output": "{\\"Error\\":\\"CustomValidationError\\",\\"Cause\\":\\"Check Identity Validation Failed\\"}", 218 | "outputDetails": Object { 219 | "truncated": false, 220 | }, 221 | }, 222 | "type": "TaskStateExited", 223 | }, 224 | Object { 225 | "detail": Object { 226 | "input": "{\\"Error\\":\\"CustomValidationError\\",\\"Cause\\":\\"Check Identity Validation Failed\\"}", 227 | "inputDetails": Object { 228 | "truncated": false, 229 | }, 230 | "name": "CustomValidationFailed", 231 | }, 232 | "type": "TaskStateEntered", 233 | }, 234 | Object { 235 | "detail": Object { 236 | "heartbeatInSeconds": undefined, 237 | "parameters": "{\\"Entries\\":[{\\"Detail\\":{\\"Message\\":\\"Validation Failed\\"},\\"DetailType\\":\\"ValidationFailed\\",\\"Source\\":\\"LocalTestingSource\\"}]}", 238 | "region": "us-east-1", 239 | "resource": "putEvents", 240 | "resourceType": "events", 241 | "timeoutInSeconds": undefined, 242 | }, 243 | "type": "TaskScheduled", 244 | }, 245 | Object { 246 | "detail": Object { 247 | "resource": "putEvents", 248 | "resourceType": "events", 249 | }, 250 | "type": "TaskStarted", 251 | }, 252 | Object { 253 | "detail": Object { 254 | "output": "{\\"Payload\\":{\\"Entries\\":[{\\"EventId\\":\\"abc123\\"}],\\"FailedEntryCount\\":0}}", 255 | "outputDetails": Object { 256 | "truncated": false, 257 | }, 258 | "resource": "putEvents", 259 | "resourceType": "events", 260 | }, 261 | "type": "TaskSucceeded", 262 | }, 263 | Object { 264 | "detail": Object { 265 | "name": "CustomValidationFailed", 266 | "output": "{\\"Payload\\":{\\"Entries\\":[{\\"EventId\\":\\"abc123\\"}],\\"FailedEntryCount\\":0}}", 267 | "outputDetails": Object { 268 | "truncated": false, 269 | }, 270 | }, 271 | "type": "TaskStateExited", 272 | }, 273 | Object { 274 | "detail": Object { 275 | "output": "{\\"Payload\\":{\\"Entries\\":[{\\"EventId\\":\\"abc123\\"}],\\"FailedEntryCount\\":0}}", 276 | "outputDetails": Object { 277 | "truncated": false, 278 | }, 279 | }, 280 | "type": "ExecutionSucceeded", 281 | }, 282 | ], 283 | }, 284 | ], 285 | }, 286 | ] 287 | `; 288 | 289 | exports[`SalesLeadsStateMachine LocalTesting HappyPathTest 1`] = ` 290 | Array [ 291 | Object { 292 | "ExecutionStarted": Array [ 293 | Object { 294 | "detail": Object { 295 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 296 | "inputDetails": Object { 297 | "truncated": false, 298 | }, 299 | "roleArn": "arn:aws:iam::123456789012:role/service-role/LambdaSQSIntegration", 300 | }, 301 | "type": "ExecutionStarted", 302 | }, 303 | ], 304 | "ParallelStateEnteredValidation": Array [ 305 | Object { 306 | "detail": Object { 307 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 308 | "inputDetails": Object { 309 | "truncated": false, 310 | }, 311 | "name": "Validation", 312 | }, 313 | "type": "ParallelStateEntered", 314 | }, 315 | Object { 316 | "detail": undefined, 317 | "type": "ParallelStateStarted", 318 | }, 319 | Object { 320 | "TaskStateEnteredCheck Address": Array [ 321 | Object { 322 | "detail": Object { 323 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 324 | "inputDetails": Object { 325 | "truncated": false, 326 | }, 327 | "name": "Check Address", 328 | }, 329 | "type": "TaskStateEntered", 330 | }, 331 | Object { 332 | "detail": Object { 333 | "heartbeatInSeconds": undefined, 334 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-address-function\\",\\"Payload\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"}}", 335 | "region": "us-east-1", 336 | "resource": "invoke", 337 | "resourceType": "lambda", 338 | "timeoutInSeconds": undefined, 339 | }, 340 | "type": "TaskScheduled", 341 | }, 342 | Object { 343 | "detail": Object { 344 | "resource": "invoke", 345 | "resourceType": "lambda", 346 | }, 347 | "type": "TaskStarted", 348 | }, 349 | Object { 350 | "detail": Object { 351 | "output": "{\\"address\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"}}", 352 | "outputDetails": Object { 353 | "truncated": false, 354 | }, 355 | "resource": "invoke", 356 | "resourceType": "lambda", 357 | }, 358 | "type": "TaskSucceeded", 359 | }, 360 | Object { 361 | "detail": Object { 362 | "name": "Check Address", 363 | "output": "{\\"address\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"}}", 364 | "outputDetails": Object { 365 | "truncated": false, 366 | }, 367 | }, 368 | "type": "TaskStateExited", 369 | }, 370 | ], 371 | "TaskStateEnteredCheck Identity": Array [ 372 | Object { 373 | "detail": Object { 374 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 375 | "inputDetails": Object { 376 | "truncated": false, 377 | }, 378 | "name": "Check Identity", 379 | }, 380 | "type": "TaskStateEntered", 381 | }, 382 | Object { 383 | "detail": Object { 384 | "heartbeatInSeconds": undefined, 385 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-identity-function\\",\\"Payload\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"}}", 386 | "region": "us-east-1", 387 | "resource": "invoke", 388 | "resourceType": "lambda", 389 | "timeoutInSeconds": undefined, 390 | }, 391 | "type": "TaskScheduled", 392 | }, 393 | Object { 394 | "detail": Object { 395 | "resource": "invoke", 396 | "resourceType": "lambda", 397 | }, 398 | "type": "TaskStarted", 399 | }, 400 | Object { 401 | "detail": Object { 402 | "output": "{\\"identity\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}", 403 | "outputDetails": Object { 404 | "truncated": false, 405 | }, 406 | "resource": "invoke", 407 | "resourceType": "lambda", 408 | }, 409 | "type": "TaskSucceeded", 410 | }, 411 | Object { 412 | "detail": Object { 413 | "name": "Check Identity", 414 | "output": "{\\"identity\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}", 415 | "outputDetails": Object { 416 | "truncated": false, 417 | }, 418 | }, 419 | "type": "TaskStateExited", 420 | }, 421 | ], 422 | }, 423 | ], 424 | }, 425 | Object { 426 | "detail": undefined, 427 | "type": "ParallelStateSucceeded", 428 | }, 429 | Object { 430 | "detail": Object { 431 | "name": "Validation", 432 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"addressResult\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"},\\"identityResult\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}}", 433 | "outputDetails": Object { 434 | "truncated": false, 435 | }, 436 | }, 437 | "type": "ParallelStateExited", 438 | }, 439 | Object { 440 | "detail": Object { 441 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"addressResult\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"},\\"identityResult\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}}", 442 | "inputDetails": Object { 443 | "truncated": false, 444 | }, 445 | "name": "DetectSentiment", 446 | }, 447 | "type": "TaskStateEntered", 448 | }, 449 | Object { 450 | "detail": Object { 451 | "heartbeatInSeconds": undefined, 452 | "parameters": "{\\"LanguageCode\\":\\"en\\",\\"Text\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}", 453 | "region": "us-east-1", 454 | "resource": "comprehend:detectSentiment", 455 | "resourceType": "aws-sdk", 456 | "timeoutInSeconds": undefined, 457 | }, 458 | "type": "TaskScheduled", 459 | }, 460 | Object { 461 | "detail": Object { 462 | "resource": "comprehend:detectSentiment", 463 | "resourceType": "aws-sdk", 464 | }, 465 | "type": "TaskStarted", 466 | }, 467 | Object { 468 | "detail": Object { 469 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"POSITIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Negative\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Positive\\":0.9946478}}}}", 470 | "outputDetails": Object { 471 | "truncated": false, 472 | }, 473 | "resource": "comprehend:detectSentiment", 474 | "resourceType": "aws-sdk", 475 | }, 476 | "type": "TaskSucceeded", 477 | }, 478 | Object { 479 | "detail": Object { 480 | "name": "DetectSentiment", 481 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"POSITIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Negative\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Positive\\":0.9946478}}}}", 482 | "outputDetails": Object { 483 | "truncated": false, 484 | }, 485 | }, 486 | "type": "TaskStateExited", 487 | }, 488 | Object { 489 | "detail": Object { 490 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"POSITIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Negative\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Positive\\":0.9946478}}}}", 491 | "inputDetails": Object { 492 | "truncated": false, 493 | }, 494 | "name": "Is Positive Sentiment?", 495 | }, 496 | "type": "ChoiceStateEntered", 497 | }, 498 | Object { 499 | "detail": Object { 500 | "name": "Is Positive Sentiment?", 501 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"POSITIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Negative\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Positive\\":0.9946478}}}}", 502 | "outputDetails": Object { 503 | "truncated": false, 504 | }, 505 | }, 506 | "type": "ChoiceStateExited", 507 | }, 508 | Object { 509 | "detail": Object { 510 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"POSITIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Negative\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Positive\\":0.9946478}}}}", 511 | "inputDetails": Object { 512 | "truncated": false, 513 | }, 514 | "name": "Add to FollowUp", 515 | }, 516 | "type": "TaskStateEntered", 517 | }, 518 | Object { 519 | "detail": Object { 520 | "heartbeatInSeconds": undefined, 521 | "parameters": "{\\"Item\\":{\\"PK\\":{\\"S\\":\\"jdoe@example.com\\"}},\\"TableName\\":\\"FollowUpTable\\"}", 522 | "region": "us-east-1", 523 | "resource": "putItem", 524 | "resourceType": "dynamodb", 525 | "timeoutInSeconds": undefined, 526 | }, 527 | "type": "TaskScheduled", 528 | }, 529 | Object { 530 | "detail": Object { 531 | "resource": "putItem", 532 | "resourceType": "dynamodb", 533 | }, 534 | "type": "TaskStarted", 535 | }, 536 | Object { 537 | "detail": Object { 538 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"dbUpdateStatusCode\\":200}}", 539 | "outputDetails": Object { 540 | "truncated": false, 541 | }, 542 | "resource": "putItem", 543 | "resourceType": "dynamodb", 544 | }, 545 | "type": "TaskSucceeded", 546 | }, 547 | Object { 548 | "detail": Object { 549 | "name": "Add to FollowUp", 550 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"dbUpdateStatusCode\\":200}}", 551 | "outputDetails": Object { 552 | "truncated": false, 553 | }, 554 | }, 555 | "type": "TaskStateExited", 556 | }, 557 | Object { 558 | "detail": Object { 559 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"dbUpdateStatusCode\\":200}}", 560 | "inputDetails": Object { 561 | "truncated": false, 562 | }, 563 | "name": "CustomerAddedToFollowup", 564 | }, 565 | "type": "TaskStateEntered", 566 | }, 567 | Object { 568 | "detail": Object { 569 | "heartbeatInSeconds": undefined, 570 | "parameters": "{\\"Entries\\":[{\\"Detail\\":{\\"Message\\":\\"Customer Added for follow up\\",\\"EmailAddress\\":\\"jdoe@example.com\\"},\\"DetailType\\":\\"CustomerAdded\\",\\"Source\\":\\"LocalTestingSource\\"}]}", 571 | "region": "us-east-1", 572 | "resource": "putEvents", 573 | "resourceType": "events", 574 | "timeoutInSeconds": undefined, 575 | }, 576 | "type": "TaskScheduled", 577 | }, 578 | Object { 579 | "detail": Object { 580 | "resource": "putEvents", 581 | "resourceType": "events", 582 | }, 583 | "type": "TaskStarted", 584 | }, 585 | Object { 586 | "detail": Object { 587 | "output": "{\\"StatusCode\\":200,\\"Payload\\":{\\"statusCode\\":200}}", 588 | "outputDetails": Object { 589 | "truncated": false, 590 | }, 591 | "resource": "putEvents", 592 | "resourceType": "events", 593 | }, 594 | "type": "TaskSucceeded", 595 | }, 596 | Object { 597 | "detail": Object { 598 | "name": "CustomerAddedToFollowup", 599 | "output": "{\\"StatusCode\\":200,\\"Payload\\":{\\"statusCode\\":200}}", 600 | "outputDetails": Object { 601 | "truncated": false, 602 | }, 603 | }, 604 | "type": "TaskStateExited", 605 | }, 606 | Object { 607 | "detail": Object { 608 | "output": "{\\"StatusCode\\":200,\\"Payload\\":{\\"statusCode\\":200}}", 609 | "outputDetails": Object { 610 | "truncated": false, 611 | }, 612 | }, 613 | "type": "ExecutionSucceeded", 614 | }, 615 | ] 616 | `; 617 | 618 | exports[`SalesLeadsStateMachine LocalTesting NegativeSentimentTest 1`] = ` 619 | Array [ 620 | Object { 621 | "ExecutionStarted": Array [ 622 | Object { 623 | "detail": Object { 624 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 625 | "inputDetails": Object { 626 | "truncated": false, 627 | }, 628 | "roleArn": "arn:aws:iam::123456789012:role/service-role/LambdaSQSIntegration", 629 | }, 630 | "type": "ExecutionStarted", 631 | }, 632 | ], 633 | "ParallelStateEnteredValidation": Array [ 634 | Object { 635 | "detail": Object { 636 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 637 | "inputDetails": Object { 638 | "truncated": false, 639 | }, 640 | "name": "Validation", 641 | }, 642 | "type": "ParallelStateEntered", 643 | }, 644 | Object { 645 | "detail": undefined, 646 | "type": "ParallelStateStarted", 647 | }, 648 | Object { 649 | "TaskStateEnteredCheck Address": Array [ 650 | Object { 651 | "detail": Object { 652 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 653 | "inputDetails": Object { 654 | "truncated": false, 655 | }, 656 | "name": "Check Address", 657 | }, 658 | "type": "TaskStateEntered", 659 | }, 660 | Object { 661 | "detail": Object { 662 | "heartbeatInSeconds": undefined, 663 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-address-function\\",\\"Payload\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"}}", 664 | "region": "us-east-1", 665 | "resource": "invoke", 666 | "resourceType": "lambda", 667 | "timeoutInSeconds": undefined, 668 | }, 669 | "type": "TaskScheduled", 670 | }, 671 | Object { 672 | "detail": Object { 673 | "resource": "invoke", 674 | "resourceType": "lambda", 675 | }, 676 | "type": "TaskStarted", 677 | }, 678 | Object { 679 | "detail": Object { 680 | "output": "{\\"address\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"}}", 681 | "outputDetails": Object { 682 | "truncated": false, 683 | }, 684 | "resource": "invoke", 685 | "resourceType": "lambda", 686 | }, 687 | "type": "TaskSucceeded", 688 | }, 689 | Object { 690 | "detail": Object { 691 | "name": "Check Address", 692 | "output": "{\\"address\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"}}", 693 | "outputDetails": Object { 694 | "truncated": false, 695 | }, 696 | }, 697 | "type": "TaskStateExited", 698 | }, 699 | ], 700 | "TaskStateEnteredCheck Identity": Array [ 701 | Object { 702 | "detail": Object { 703 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 704 | "inputDetails": Object { 705 | "truncated": false, 706 | }, 707 | "name": "Check Identity", 708 | }, 709 | "type": "TaskStateEntered", 710 | }, 711 | Object { 712 | "detail": Object { 713 | "heartbeatInSeconds": undefined, 714 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-identity-function\\",\\"Payload\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"}}", 715 | "region": "us-east-1", 716 | "resource": "invoke", 717 | "resourceType": "lambda", 718 | "timeoutInSeconds": undefined, 719 | }, 720 | "type": "TaskScheduled", 721 | }, 722 | Object { 723 | "detail": Object { 724 | "resource": "invoke", 725 | "resourceType": "lambda", 726 | }, 727 | "type": "TaskStarted", 728 | }, 729 | Object { 730 | "detail": Object { 731 | "output": "{\\"identity\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}", 732 | "outputDetails": Object { 733 | "truncated": false, 734 | }, 735 | "resource": "invoke", 736 | "resourceType": "lambda", 737 | }, 738 | "type": "TaskSucceeded", 739 | }, 740 | Object { 741 | "detail": Object { 742 | "name": "Check Identity", 743 | "output": "{\\"identity\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}", 744 | "outputDetails": Object { 745 | "truncated": false, 746 | }, 747 | }, 748 | "type": "TaskStateExited", 749 | }, 750 | ], 751 | }, 752 | ], 753 | }, 754 | Object { 755 | "detail": undefined, 756 | "type": "ParallelStateSucceeded", 757 | }, 758 | Object { 759 | "detail": Object { 760 | "name": "Validation", 761 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"addressResult\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"},\\"identityResult\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}}", 762 | "outputDetails": Object { 763 | "truncated": false, 764 | }, 765 | }, 766 | "type": "ParallelStateExited", 767 | }, 768 | Object { 769 | "detail": Object { 770 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"addressResult\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"},\\"identityResult\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}}", 771 | "inputDetails": Object { 772 | "truncated": false, 773 | }, 774 | "name": "DetectSentiment", 775 | }, 776 | "type": "TaskStateEntered", 777 | }, 778 | Object { 779 | "detail": Object { 780 | "heartbeatInSeconds": undefined, 781 | "parameters": "{\\"LanguageCode\\":\\"en\\",\\"Text\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}", 782 | "region": "us-east-1", 783 | "resource": "comprehend:detectSentiment", 784 | "resourceType": "aws-sdk", 785 | "timeoutInSeconds": undefined, 786 | }, 787 | "type": "TaskScheduled", 788 | }, 789 | Object { 790 | "detail": Object { 791 | "resource": "comprehend:detectSentiment", 792 | "resourceType": "aws-sdk", 793 | }, 794 | "type": "TaskStarted", 795 | }, 796 | Object { 797 | "detail": Object { 798 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"NEGATIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Positive\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Negative\\":0.9946478}}}}", 799 | "outputDetails": Object { 800 | "truncated": false, 801 | }, 802 | "resource": "comprehend:detectSentiment", 803 | "resourceType": "aws-sdk", 804 | }, 805 | "type": "TaskSucceeded", 806 | }, 807 | Object { 808 | "detail": Object { 809 | "name": "DetectSentiment", 810 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"NEGATIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Positive\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Negative\\":0.9946478}}}}", 811 | "outputDetails": Object { 812 | "truncated": false, 813 | }, 814 | }, 815 | "type": "TaskStateExited", 816 | }, 817 | Object { 818 | "detail": Object { 819 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"NEGATIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Positive\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Negative\\":0.9946478}}}}", 820 | "inputDetails": Object { 821 | "truncated": false, 822 | }, 823 | "name": "Is Positive Sentiment?", 824 | }, 825 | "type": "ChoiceStateEntered", 826 | }, 827 | Object { 828 | "detail": Object { 829 | "name": "Is Positive Sentiment?", 830 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"NEGATIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Positive\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Negative\\":0.9946478}}}}", 831 | "outputDetails": Object { 832 | "truncated": false, 833 | }, 834 | }, 835 | "type": "ChoiceStateExited", 836 | }, 837 | Object { 838 | "detail": Object { 839 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"NEGATIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Positive\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Negative\\":0.9946478}}}}", 840 | "inputDetails": Object { 841 | "truncated": false, 842 | }, 843 | "name": "NegativeSentimentDetected", 844 | }, 845 | "type": "TaskStateEntered", 846 | }, 847 | Object { 848 | "detail": Object { 849 | "heartbeatInSeconds": undefined, 850 | "parameters": "{\\"Entries\\":[{\\"Detail\\":{\\"Message\\":\\"Negative Sentiment Detected\\",\\"Data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}},\\"DetailType\\":\\"NegativeSentiment\\",\\"Source\\":\\"LocalTestingSource\\"}]}", 851 | "region": "us-east-1", 852 | "resource": "putEvents", 853 | "resourceType": "events", 854 | "timeoutInSeconds": undefined, 855 | }, 856 | "type": "TaskScheduled", 857 | }, 858 | Object { 859 | "detail": Object { 860 | "resource": "putEvents", 861 | "resourceType": "events", 862 | }, 863 | "type": "TaskStarted", 864 | }, 865 | Object { 866 | "detail": Object { 867 | "output": "{\\"Payload\\":{\\"Entries\\":[{\\"EventId\\":\\"abc123\\"}],\\"FailedEntryCount\\":0}}", 868 | "outputDetails": Object { 869 | "truncated": false, 870 | }, 871 | "resource": "putEvents", 872 | "resourceType": "events", 873 | }, 874 | "type": "TaskSucceeded", 875 | }, 876 | Object { 877 | "detail": Object { 878 | "name": "NegativeSentimentDetected", 879 | "output": "{\\"Payload\\":{\\"Entries\\":[{\\"EventId\\":\\"abc123\\"}],\\"FailedEntryCount\\":0}}", 880 | "outputDetails": Object { 881 | "truncated": false, 882 | }, 883 | }, 884 | "type": "TaskStateExited", 885 | }, 886 | Object { 887 | "detail": Object { 888 | "output": "{\\"Payload\\":{\\"Entries\\":[{\\"EventId\\":\\"abc123\\"}],\\"FailedEntryCount\\":0}}", 889 | "outputDetails": Object { 890 | "truncated": false, 891 | }, 892 | }, 893 | "type": "ExecutionSucceeded", 894 | }, 895 | ] 896 | `; 897 | 898 | exports[`SalesLeadsStateMachine LocalTesting RetryOnServiceExceptionTest 1`] = ` 899 | Array [ 900 | Object { 901 | "ExecutionStarted": Array [ 902 | Object { 903 | "detail": Object { 904 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 905 | "inputDetails": Object { 906 | "truncated": false, 907 | }, 908 | "roleArn": "arn:aws:iam::123456789012:role/service-role/LambdaSQSIntegration", 909 | }, 910 | "type": "ExecutionStarted", 911 | }, 912 | ], 913 | "ParallelStateEnteredValidation": Array [ 914 | Object { 915 | "detail": Object { 916 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 917 | "inputDetails": Object { 918 | "truncated": false, 919 | }, 920 | "name": "Validation", 921 | }, 922 | "type": "ParallelStateEntered", 923 | }, 924 | Object { 925 | "detail": undefined, 926 | "type": "ParallelStateStarted", 927 | }, 928 | Object { 929 | "TaskStateEnteredCheck Address": Array [ 930 | Object { 931 | "detail": Object { 932 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 933 | "inputDetails": Object { 934 | "truncated": false, 935 | }, 936 | "name": "Check Address", 937 | }, 938 | "type": "TaskStateEntered", 939 | }, 940 | Object { 941 | "detail": Object { 942 | "heartbeatInSeconds": undefined, 943 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-address-function\\",\\"Payload\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"}}", 944 | "region": "us-east-1", 945 | "resource": "invoke", 946 | "resourceType": "lambda", 947 | "timeoutInSeconds": undefined, 948 | }, 949 | "type": "TaskScheduled", 950 | }, 951 | Object { 952 | "detail": Object { 953 | "resource": "invoke", 954 | "resourceType": "lambda", 955 | }, 956 | "type": "TaskStarted", 957 | }, 958 | Object { 959 | "detail": Object { 960 | "output": "{\\"address\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"}}", 961 | "outputDetails": Object { 962 | "truncated": false, 963 | }, 964 | "resource": "invoke", 965 | "resourceType": "lambda", 966 | }, 967 | "type": "TaskSucceeded", 968 | }, 969 | Object { 970 | "detail": Object { 971 | "name": "Check Address", 972 | "output": "{\\"address\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"}}", 973 | "outputDetails": Object { 974 | "truncated": false, 975 | }, 976 | }, 977 | "type": "TaskStateExited", 978 | }, 979 | ], 980 | "TaskStateEnteredCheck Identity": Array [ 981 | Object { 982 | "detail": Object { 983 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 984 | "inputDetails": Object { 985 | "truncated": false, 986 | }, 987 | "name": "Check Identity", 988 | }, 989 | "type": "TaskStateEntered", 990 | }, 991 | Object { 992 | "detail": Object { 993 | "heartbeatInSeconds": undefined, 994 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-identity-function\\",\\"Payload\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"}}", 995 | "region": "us-east-1", 996 | "resource": "invoke", 997 | "resourceType": "lambda", 998 | "timeoutInSeconds": undefined, 999 | }, 1000 | "type": "TaskScheduled", 1001 | }, 1002 | Object { 1003 | "detail": Object { 1004 | "resource": "invoke", 1005 | "resourceType": "lambda", 1006 | }, 1007 | "type": "TaskStarted", 1008 | }, 1009 | Object { 1010 | "detail": Object { 1011 | "output": "{\\"identity\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}", 1012 | "outputDetails": Object { 1013 | "truncated": false, 1014 | }, 1015 | "resource": "invoke", 1016 | "resourceType": "lambda", 1017 | }, 1018 | "type": "TaskSucceeded", 1019 | }, 1020 | Object { 1021 | "detail": Object { 1022 | "name": "Check Identity", 1023 | "output": "{\\"identity\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}", 1024 | "outputDetails": Object { 1025 | "truncated": false, 1026 | }, 1027 | }, 1028 | "type": "TaskStateExited", 1029 | }, 1030 | ], 1031 | }, 1032 | ], 1033 | }, 1034 | Object { 1035 | "detail": undefined, 1036 | "type": "ParallelStateSucceeded", 1037 | }, 1038 | Object { 1039 | "detail": Object { 1040 | "name": "Validation", 1041 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"addressResult\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"},\\"identityResult\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}}", 1042 | "outputDetails": Object { 1043 | "truncated": false, 1044 | }, 1045 | }, 1046 | "type": "ParallelStateExited", 1047 | }, 1048 | Object { 1049 | "detail": Object { 1050 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"addressResult\\":{\\"approved\\":true,\\"message\\":\\"address validation passed\\"},\\"identityResult\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}}", 1051 | "inputDetails": Object { 1052 | "truncated": false, 1053 | }, 1054 | "name": "DetectSentiment", 1055 | }, 1056 | "type": "TaskStateEntered", 1057 | }, 1058 | Object { 1059 | "detail": Object { 1060 | "heartbeatInSeconds": undefined, 1061 | "parameters": "{\\"LanguageCode\\":\\"en\\",\\"Text\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}", 1062 | "region": "us-east-1", 1063 | "resource": "comprehend:detectSentiment", 1064 | "resourceType": "aws-sdk", 1065 | "timeoutInSeconds": undefined, 1066 | }, 1067 | "type": "TaskScheduled", 1068 | }, 1069 | Object { 1070 | "detail": Object { 1071 | "resource": "comprehend:detectSentiment", 1072 | "resourceType": "aws-sdk", 1073 | }, 1074 | "type": "TaskStarted", 1075 | }, 1076 | Object { 1077 | "detail": Object { 1078 | "cause": "Server Exception while calling DetectSentiment API in Comprehend Service", 1079 | "error": "InternalServerException", 1080 | "resource": "comprehend:detectSentiment", 1081 | "resourceType": "aws-sdk", 1082 | }, 1083 | "type": "TaskFailed", 1084 | }, 1085 | Object { 1086 | "detail": Object { 1087 | "heartbeatInSeconds": undefined, 1088 | "parameters": "{\\"LanguageCode\\":\\"en\\",\\"Text\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}", 1089 | "region": "us-east-1", 1090 | "resource": "comprehend:detectSentiment", 1091 | "resourceType": "aws-sdk", 1092 | "timeoutInSeconds": undefined, 1093 | }, 1094 | "type": "TaskScheduled", 1095 | }, 1096 | Object { 1097 | "detail": Object { 1098 | "resource": "comprehend:detectSentiment", 1099 | "resourceType": "aws-sdk", 1100 | }, 1101 | "type": "TaskStarted", 1102 | }, 1103 | Object { 1104 | "detail": Object { 1105 | "cause": "Server Exception while calling DetectSentiment API in Comprehend Service", 1106 | "error": "InternalServerException", 1107 | "resource": "comprehend:detectSentiment", 1108 | "resourceType": "aws-sdk", 1109 | }, 1110 | "type": "TaskFailed", 1111 | }, 1112 | Object { 1113 | "detail": Object { 1114 | "heartbeatInSeconds": undefined, 1115 | "parameters": "{\\"LanguageCode\\":\\"en\\",\\"Text\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}", 1116 | "region": "us-east-1", 1117 | "resource": "comprehend:detectSentiment", 1118 | "resourceType": "aws-sdk", 1119 | "timeoutInSeconds": undefined, 1120 | }, 1121 | "type": "TaskScheduled", 1122 | }, 1123 | Object { 1124 | "detail": Object { 1125 | "resource": "comprehend:detectSentiment", 1126 | "resourceType": "aws-sdk", 1127 | }, 1128 | "type": "TaskStarted", 1129 | }, 1130 | Object { 1131 | "detail": Object { 1132 | "cause": "Server Exception while calling DetectSentiment API in Comprehend Service", 1133 | "error": "InternalServerException", 1134 | "resource": "comprehend:detectSentiment", 1135 | "resourceType": "aws-sdk", 1136 | }, 1137 | "type": "TaskFailed", 1138 | }, 1139 | Object { 1140 | "detail": Object { 1141 | "heartbeatInSeconds": undefined, 1142 | "parameters": "{\\"LanguageCode\\":\\"en\\",\\"Text\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}", 1143 | "region": "us-east-1", 1144 | "resource": "comprehend:detectSentiment", 1145 | "resourceType": "aws-sdk", 1146 | "timeoutInSeconds": undefined, 1147 | }, 1148 | "type": "TaskScheduled", 1149 | }, 1150 | Object { 1151 | "detail": Object { 1152 | "resource": "comprehend:detectSentiment", 1153 | "resourceType": "aws-sdk", 1154 | }, 1155 | "type": "TaskStarted", 1156 | }, 1157 | Object { 1158 | "detail": Object { 1159 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"POSITIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Negative\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Positive\\":0.9946478}}}}", 1160 | "outputDetails": Object { 1161 | "truncated": false, 1162 | }, 1163 | "resource": "comprehend:detectSentiment", 1164 | "resourceType": "aws-sdk", 1165 | }, 1166 | "type": "TaskSucceeded", 1167 | }, 1168 | Object { 1169 | "detail": Object { 1170 | "name": "DetectSentiment", 1171 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"POSITIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Negative\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Positive\\":0.9946478}}}}", 1172 | "outputDetails": Object { 1173 | "truncated": false, 1174 | }, 1175 | }, 1176 | "type": "TaskStateExited", 1177 | }, 1178 | Object { 1179 | "detail": Object { 1180 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"POSITIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Negative\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Positive\\":0.9946478}}}}", 1181 | "inputDetails": Object { 1182 | "truncated": false, 1183 | }, 1184 | "name": "Is Positive Sentiment?", 1185 | }, 1186 | "type": "ChoiceStateEntered", 1187 | }, 1188 | Object { 1189 | "detail": Object { 1190 | "name": "Is Positive Sentiment?", 1191 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"POSITIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Negative\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Positive\\":0.9946478}}}}", 1192 | "outputDetails": Object { 1193 | "truncated": false, 1194 | }, 1195 | }, 1196 | "type": "ChoiceStateExited", 1197 | }, 1198 | Object { 1199 | "detail": Object { 1200 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"sentimentAnalysis\\":{\\"Sentiment\\":\\"POSITIVE\\",\\"SentimentScore\\":{\\"Mixed\\":1.2647535E-4,\\"Negative\\":8.031699E-5,\\"Neutral\\":0.0051454515,\\"Positive\\":0.9946478}}}}", 1201 | "inputDetails": Object { 1202 | "truncated": false, 1203 | }, 1204 | "name": "Add to FollowUp", 1205 | }, 1206 | "type": "TaskStateEntered", 1207 | }, 1208 | Object { 1209 | "detail": Object { 1210 | "heartbeatInSeconds": undefined, 1211 | "parameters": "{\\"Item\\":{\\"PK\\":{\\"S\\":\\"jdoe@example.com\\"}},\\"TableName\\":\\"FollowUpTable\\"}", 1212 | "region": "us-east-1", 1213 | "resource": "putItem", 1214 | "resourceType": "dynamodb", 1215 | "timeoutInSeconds": undefined, 1216 | }, 1217 | "type": "TaskScheduled", 1218 | }, 1219 | Object { 1220 | "detail": Object { 1221 | "resource": "putItem", 1222 | "resourceType": "dynamodb", 1223 | }, 1224 | "type": "TaskStarted", 1225 | }, 1226 | Object { 1227 | "detail": Object { 1228 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"dbUpdateStatusCode\\":200}}", 1229 | "outputDetails": Object { 1230 | "truncated": false, 1231 | }, 1232 | "resource": "putItem", 1233 | "resourceType": "dynamodb", 1234 | }, 1235 | "type": "TaskSucceeded", 1236 | }, 1237 | Object { 1238 | "detail": Object { 1239 | "name": "Add to FollowUp", 1240 | "output": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"dbUpdateStatusCode\\":200}}", 1241 | "outputDetails": Object { 1242 | "truncated": false, 1243 | }, 1244 | }, 1245 | "type": "TaskStateExited", 1246 | }, 1247 | Object { 1248 | "detail": Object { 1249 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"},\\"results\\":{\\"dbUpdateStatusCode\\":200}}", 1250 | "inputDetails": Object { 1251 | "truncated": false, 1252 | }, 1253 | "name": "CustomerAddedToFollowup", 1254 | }, 1255 | "type": "TaskStateEntered", 1256 | }, 1257 | Object { 1258 | "detail": Object { 1259 | "heartbeatInSeconds": undefined, 1260 | "parameters": "{\\"Entries\\":[{\\"Detail\\":{\\"Message\\":\\"Customer Added for follow up\\",\\"EmailAddress\\":\\"jdoe@example.com\\"},\\"DetailType\\":\\"CustomerAdded\\",\\"Source\\":\\"LocalTestingSource\\"}]}", 1261 | "region": "us-east-1", 1262 | "resource": "putEvents", 1263 | "resourceType": "events", 1264 | "timeoutInSeconds": undefined, 1265 | }, 1266 | "type": "TaskScheduled", 1267 | }, 1268 | Object { 1269 | "detail": Object { 1270 | "resource": "putEvents", 1271 | "resourceType": "events", 1272 | }, 1273 | "type": "TaskStarted", 1274 | }, 1275 | Object { 1276 | "detail": Object { 1277 | "output": "{\\"StatusCode\\":200,\\"Payload\\":{\\"statusCode\\":200}}", 1278 | "outputDetails": Object { 1279 | "truncated": false, 1280 | }, 1281 | "resource": "putEvents", 1282 | "resourceType": "events", 1283 | }, 1284 | "type": "TaskSucceeded", 1285 | }, 1286 | Object { 1287 | "detail": Object { 1288 | "name": "CustomerAddedToFollowup", 1289 | "output": "{\\"StatusCode\\":200,\\"Payload\\":{\\"statusCode\\":200}}", 1290 | "outputDetails": Object { 1291 | "truncated": false, 1292 | }, 1293 | }, 1294 | "type": "TaskStateExited", 1295 | }, 1296 | Object { 1297 | "detail": Object { 1298 | "output": "{\\"StatusCode\\":200,\\"Payload\\":{\\"statusCode\\":200}}", 1299 | "outputDetails": Object { 1300 | "truncated": false, 1301 | }, 1302 | }, 1303 | "type": "ExecutionSucceeded", 1304 | }, 1305 | ] 1306 | `; 1307 | 1308 | exports[`SalesLeadsStateMachine LocalTesting ValidationExceptionCatchTest 1`] = ` 1309 | Array [ 1310 | Object { 1311 | "ExecutionStarted": Array [ 1312 | Object { 1313 | "detail": Object { 1314 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 1315 | "inputDetails": Object { 1316 | "truncated": false, 1317 | }, 1318 | "roleArn": "arn:aws:iam::123456789012:role/service-role/LambdaSQSIntegration", 1319 | }, 1320 | "type": "ExecutionStarted", 1321 | }, 1322 | ], 1323 | "ParallelStateEnteredValidation": Array [ 1324 | Object { 1325 | "detail": Object { 1326 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 1327 | "inputDetails": Object { 1328 | "truncated": false, 1329 | }, 1330 | "name": "Validation", 1331 | }, 1332 | "type": "ParallelStateEntered", 1333 | }, 1334 | Object { 1335 | "ParallelStateStarted": Array [ 1336 | Object { 1337 | "detail": undefined, 1338 | "type": "ParallelStateStarted", 1339 | }, 1340 | Object { 1341 | "TaskStateEnteredCheck Address": Array [ 1342 | Object { 1343 | "detail": Object { 1344 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 1345 | "inputDetails": Object { 1346 | "truncated": false, 1347 | }, 1348 | "name": "Check Address", 1349 | }, 1350 | "type": "TaskStateEntered", 1351 | }, 1352 | Object { 1353 | "detail": Object { 1354 | "heartbeatInSeconds": undefined, 1355 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-address-function\\",\\"Payload\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"}}", 1356 | "region": "us-east-1", 1357 | "resource": "invoke", 1358 | "resourceType": "lambda", 1359 | "timeoutInSeconds": undefined, 1360 | }, 1361 | "type": "TaskScheduled", 1362 | }, 1363 | Object { 1364 | "detail": Object { 1365 | "resource": "invoke", 1366 | "resourceType": "lambda", 1367 | }, 1368 | "type": "TaskStarted", 1369 | }, 1370 | Object { 1371 | "detail": Object { 1372 | "cause": "Address Validation Exception", 1373 | "error": "CustomAddressValidationError", 1374 | "resource": "invoke", 1375 | "resourceType": "lambda", 1376 | }, 1377 | "type": "TaskFailed", 1378 | }, 1379 | Object { 1380 | "detail": undefined, 1381 | "type": "ParallelStateFailed", 1382 | }, 1383 | ], 1384 | "TaskStateEnteredCheck Identity": Array [ 1385 | Object { 1386 | "detail": Object { 1387 | "input": "{\\"data\\":{\\"firstname\\":\\"Jane\\",\\"lastname\\":\\"Doe\\",\\"identity\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"},\\"address\\":{\\"street\\":\\"123 Main St\\",\\"city\\":\\"Columbus\\",\\"state\\":\\"OH\\",\\"zip\\":\\"43219\\"},\\"comments\\":\\"I am glad to sign-up for this service. Looking forward to different options.\\"}}", 1388 | "inputDetails": Object { 1389 | "truncated": false, 1390 | }, 1391 | "name": "Check Identity", 1392 | }, 1393 | "type": "TaskStateEntered", 1394 | }, 1395 | Object { 1396 | "detail": Object { 1397 | "heartbeatInSeconds": undefined, 1398 | "parameters": "{\\"FunctionName\\":\\"arn:aws:lambda:us-west-2:123456789012:function:check-identity-function\\",\\"Payload\\":{\\"email\\":\\"jdoe@example.com\\",\\"ssn\\":\\"123-45-6789\\"}}", 1399 | "region": "us-east-1", 1400 | "resource": "invoke", 1401 | "resourceType": "lambda", 1402 | "timeoutInSeconds": undefined, 1403 | }, 1404 | "type": "TaskScheduled", 1405 | }, 1406 | Object { 1407 | "detail": Object { 1408 | "resource": "invoke", 1409 | "resourceType": "lambda", 1410 | }, 1411 | "type": "TaskStarted", 1412 | }, 1413 | Object { 1414 | "detail": Object { 1415 | "output": "{\\"identity\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}", 1416 | "outputDetails": Object { 1417 | "truncated": false, 1418 | }, 1419 | "resource": "invoke", 1420 | "resourceType": "lambda", 1421 | }, 1422 | "type": "TaskSucceeded", 1423 | }, 1424 | Object { 1425 | "detail": Object { 1426 | "name": "Check Identity", 1427 | "output": "{\\"identity\\":{\\"approved\\":true,\\"message\\":\\"identity validation passed\\"}}", 1428 | "outputDetails": Object { 1429 | "truncated": false, 1430 | }, 1431 | }, 1432 | "type": "TaskStateExited", 1433 | }, 1434 | ], 1435 | }, 1436 | ], 1437 | "TaskStateExitedValidation": Array [ 1438 | Object { 1439 | "detail": Object { 1440 | "name": "Validation", 1441 | "output": "{\\"Error\\":\\"CustomAddressValidationError\\",\\"Cause\\":\\"Address Validation Exception\\"}", 1442 | "outputDetails": Object { 1443 | "truncated": false, 1444 | }, 1445 | }, 1446 | "type": "TaskStateExited", 1447 | }, 1448 | Object { 1449 | "detail": Object { 1450 | "input": "{\\"Error\\":\\"CustomAddressValidationError\\",\\"Cause\\":\\"Address Validation Exception\\"}", 1451 | "inputDetails": Object { 1452 | "truncated": false, 1453 | }, 1454 | "name": "ValidationException", 1455 | }, 1456 | "type": "TaskStateEntered", 1457 | }, 1458 | Object { 1459 | "detail": Object { 1460 | "heartbeatInSeconds": undefined, 1461 | "parameters": "{\\"Entries\\":[{\\"Detail\\":{\\"Message\\":\\"Validation Exception\\"},\\"DetailType\\":\\"ValidationException\\",\\"Source\\":\\"LocalTestingSource\\"}]}", 1462 | "region": "us-east-1", 1463 | "resource": "putEvents", 1464 | "resourceType": "events", 1465 | "timeoutInSeconds": undefined, 1466 | }, 1467 | "type": "TaskScheduled", 1468 | }, 1469 | Object { 1470 | "detail": Object { 1471 | "resource": "putEvents", 1472 | "resourceType": "events", 1473 | }, 1474 | "type": "TaskStarted", 1475 | }, 1476 | Object { 1477 | "detail": Object { 1478 | "output": "{\\"Payload\\":{\\"Entries\\":[{\\"EventId\\":\\"abc123\\"}],\\"FailedEntryCount\\":0}}", 1479 | "outputDetails": Object { 1480 | "truncated": false, 1481 | }, 1482 | "resource": "putEvents", 1483 | "resourceType": "events", 1484 | }, 1485 | "type": "TaskSucceeded", 1486 | }, 1487 | Object { 1488 | "detail": Object { 1489 | "name": "ValidationException", 1490 | "output": "{\\"Payload\\":{\\"Entries\\":[{\\"EventId\\":\\"abc123\\"}],\\"FailedEntryCount\\":0}}", 1491 | "outputDetails": Object { 1492 | "truncated": false, 1493 | }, 1494 | }, 1495 | "type": "TaskStateExited", 1496 | }, 1497 | Object { 1498 | "detail": Object { 1499 | "output": "{\\"Payload\\":{\\"Entries\\":[{\\"EventId\\":\\"abc123\\"}],\\"FailedEntryCount\\":0}}", 1500 | "outputDetails": Object { 1501 | "truncated": false, 1502 | }, 1503 | }, 1504 | "type": "ExecutionSucceeded", 1505 | }, 1506 | ], 1507 | }, 1508 | ], 1509 | }, 1510 | ] 1511 | `; 1512 | -------------------------------------------------------------------------------- /test/stepfunctions/lambda-sqs-integration.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MockedResponse, 3 | StateMachineTestCase, 4 | StateMachineTestDefinition, 5 | StepFunctionsMockConfig, 6 | } from 'stepfunctions-testing'; 7 | 8 | import * as cdk from 'aws-cdk-lib'; 9 | import { StateMachineConstruct } from '../../lib/state-machine-construct'; 10 | import { createJestTestFromMockConfig } from '../helpers/stepfunctions'; 11 | import { Duration } from 'aws-cdk-lib'; 12 | import { extractStateMachineAsls } from '../helpers/stepfunctions/cdk-to-asl'; 13 | 14 | // TODO: dummy code 15 | type LambdaResponse = { 16 | StatusCode: number; 17 | Payload: { 18 | StatusCode: number; 19 | body: any; 20 | }; 21 | }; 22 | 23 | export const stateNames = { 24 | LambdaStateName: 'LambdaState', 25 | SqsStateName: 'SqsState', 26 | } as const; 27 | 28 | type StateNameKeys = keyof typeof stateNames; 29 | type StateName = typeof stateNames[StateNameKeys]; 30 | 31 | export const stateMachineName = 'LambdaSQSIntegration'; 32 | 33 | const mockedLambdaSuccessResponse = new MockedResponse( 34 | 'MockedLambdaSuccess' 35 | ).return({ 36 | StatusCode: 200, 37 | Payload: { 38 | StatusCode: 200, 39 | body: 'Hello from Lambda!', 40 | }, 41 | }); 42 | 43 | const lambdaMockedResourceNotReadyResponse = new MockedResponse( 44 | 'LambdaMockedResourceNotReady' 45 | ).throw('Lambda.ResourceNotReadyException', 'Lambda resource is not ready.'); 46 | 47 | const mockedSqsSuccessResponse = new MockedResponse('MockedSQSSuccess').return({ 48 | MD5OfMessageBody: '3bcb6e8e-7h85-4375-b0bc-1a59812c6e51', 49 | MessageId: '3bcb6e8e-8b51-4375-b0bc-1a59812c6e51', 50 | }); 51 | 52 | const mockedLambdaRetryResponse = new MockedResponse('MockedLambdaRetry') 53 | .throw('Lambda.ResourceNotReadyException', 'Lambda resource is not ready.') 54 | .throw('Lambda.TimeoutException', 'Lambda timed out.', 1) 55 | .return({ 56 | StatusCode: 200, 57 | Payload: { 58 | StatusCode: 200, 59 | body: 'Hello from Lambda!', 60 | }, 61 | }); 62 | 63 | const stateMachineTestDefinition = new StateMachineTestDefinition( 64 | stateMachineName 65 | ) 66 | .addTestCase( 67 | new StateMachineTestCase('HappyPath') 68 | .addMockedState(stateNames.LambdaStateName, mockedLambdaSuccessResponse) 69 | .addMockedState(stateNames.SqsStateName, mockedSqsSuccessResponse) 70 | ) 71 | .addTestCase( 72 | new StateMachineTestCase('RetryPath') 73 | .addMockedState(stateNames.LambdaStateName, mockedLambdaRetryResponse) 74 | .addMockedState(stateNames.SqsStateName, mockedSqsSuccessResponse) 75 | ); 76 | 77 | const config = new StepFunctionsMockConfig(); 78 | config.addTestDefinition(stateMachineTestDefinition); 79 | 80 | const stack = new cdk.Stack(); 81 | new StateMachineConstruct(stack, 'StateMachine', { 82 | lambdaStateName: stateNames.LambdaStateName, 83 | sqsStateName: stateNames.SqsStateName, 84 | stateMachineName: stateMachineName, 85 | retryInterval: Duration.seconds(0), 86 | }); 87 | // extract the Asl part 88 | const asls = extractStateMachineAsls(stack); 89 | 90 | describe( 91 | 'Lambda SQS integration', 92 | createJestTestFromMockConfig(config, asls[0]) 93 | ); 94 | -------------------------------------------------------------------------------- /test/stepfunctions/sales-lead-state-machine.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MockedResponse, 3 | StateMachineTestCase, 4 | StateMachineTestDefinition, 5 | StepFunctionsMockConfig, 6 | } from 'stepfunctions-testing'; 7 | 8 | import * as cdk from 'aws-cdk-lib'; 9 | import { createJestTestFromMockConfig } from '../helpers/stepfunctions'; 10 | import { Duration } from 'aws-cdk-lib'; 11 | import { SalesLeadsStateMachineConstruct } from '../../lib/sales-leads-state-machine-construct'; 12 | import { extractStateMachineAsls } from '../helpers/stepfunctions/cdk-to-asl'; 13 | 14 | // TODO: dummy code 15 | type LambdaResponse = { 16 | StatusCode: number; 17 | Payload: { 18 | statusCode: number; 19 | body: any; 20 | }; 21 | }; 22 | type DynamoResponse = { 23 | SdkHttpMetadata: { 24 | HttpStatusCode: number; 25 | }; 26 | }; 27 | type EventBridgeResponse = { 28 | Payload: { 29 | Entries: { EventId: string }[]; 30 | FailedEntryCount: number; 31 | }; 32 | }; 33 | type ComprehendResponse = { 34 | Sentiment: 'POSITIVE' | 'NEGATIVE'; 35 | SentimentScore: { 36 | Mixed: number; 37 | Negative: number; 38 | Neutral: number; 39 | Positive: number; 40 | }; 41 | }; 42 | 43 | export const stateNames = { 44 | validationStateName: 'Validation', 45 | checkIdentityStateName: 'Check Identity', 46 | checkAddressStateName: 'Check Address', 47 | customValidationFailedStateName: 'CustomValidationFailed', 48 | validationExceptionStateName: 'ValidationException', 49 | detectSentimentStateName: 'DetectSentiment', 50 | isPositiveSentimentStateName: 'Is Positive Sentiment?', 51 | negativeSentimentDetectedStateName: 'NegativeSentimentDetected', 52 | addToFollowUpStateName: 'Add to FollowUp', 53 | customerAddedToFollowupStateName: 'CustomerAddedToFollowup', 54 | } as const; 55 | 56 | type StateNameKeys = keyof typeof stateNames; 57 | type StateName = typeof stateNames[StateNameKeys]; 58 | 59 | const stateMachineName = 'LocalTesting'; 60 | 61 | const checkIdentityLambdaMockedSuccess = new MockedResponse( 62 | 'CheckIdentityLambdaMockedSuccess' 63 | ).return({ 64 | StatusCode: 200, 65 | Payload: { 66 | statusCode: 200, 67 | body: JSON.stringify({ 68 | approved: true, 69 | message: 'identity validation passed', 70 | }), 71 | }, 72 | }); 73 | 74 | const checkAddressLambdaMockedSuccess = new MockedResponse( 75 | 'CheckAddressLambdaMockedSuccess' 76 | ).return({ 77 | StatusCode: 200, 78 | Payload: { 79 | statusCode: 200, 80 | body: JSON.stringify({ 81 | approved: true, 82 | message: 'address validation passed', 83 | }), 84 | }, 85 | }); 86 | 87 | const addToFollowUpSuccess = new MockedResponse( 88 | 'AddToFollowUpSuccess' 89 | ).return({ 90 | SdkHttpMetadata: { 91 | HttpStatusCode: 200, 92 | }, 93 | }); 94 | 95 | const customerAddedToFollowupSuccess = new MockedResponse( 96 | 'CustomerAddedToFollowupSuccess' 97 | ).return({ 98 | StatusCode: 200, 99 | Payload: { 100 | statusCode: 200, 101 | }, 102 | }); 103 | 104 | const checkIdentityLambdaMockedThrowError = new MockedResponse( 105 | 'CheckIdentityLambdaMockedThrowError' 106 | ).throw('CustomValidationError', 'Check Identity Validation Failed', 3); 107 | 108 | const checkAddressLambdaMockedThrowExceptionSuccess = new MockedResponse( 109 | 'CheckAddressLambdaMockedThrowExceptionSuccess' 110 | ).throw('CustomAddressValidationError', 'Address Validation Exception'); 111 | 112 | const customValidationFailedPutEventSuccess = new MockedResponse( 113 | 'CustomValidationFailedPutEventSuccess' 114 | ).return({ 115 | Payload: { 116 | Entries: [ 117 | { 118 | EventId: 'abc123', 119 | }, 120 | ], 121 | FailedEntryCount: 0, 122 | }, 123 | }); 124 | 125 | const validationExceptionPutEventSuccess = new MockedResponse( 126 | 'ValidationExceptionPutEventSuccess' 127 | ).return({ 128 | Payload: { 129 | Entries: [ 130 | { 131 | EventId: 'abc123', 132 | }, 133 | ], 134 | FailedEntryCount: 0, 135 | }, 136 | }); 137 | 138 | const detectSentimentPositive = new MockedResponse( 139 | 'DetectSentimentPositive' 140 | ).return({ 141 | Sentiment: 'POSITIVE', 142 | SentimentScore: { 143 | Mixed: 0.00012647535, 144 | Negative: 0.00008031699, 145 | Neutral: 0.0051454515, 146 | Positive: 0.9946478, 147 | }, 148 | }); 149 | 150 | const detectSentimentNegative = new MockedResponse( 151 | 'DetectSentimentNegative' 152 | ).return({ 153 | Sentiment: 'NEGATIVE', 154 | SentimentScore: { 155 | Mixed: 0.00012647535, 156 | Positive: 0.00008031699, 157 | Neutral: 0.0051454515, 158 | Negative: 0.9946478, 159 | }, 160 | }); 161 | 162 | const negativeSentimentDetectedSuccess = new MockedResponse( 163 | 'NegativeSentimentDetectedSuccess' 164 | ).return({ 165 | Payload: { 166 | Entries: [ 167 | { 168 | EventId: 'abc123', 169 | }, 170 | ], 171 | FailedEntryCount: 0, 172 | }, 173 | }); 174 | 175 | const detectSentimentRetryOnErrorWithSuccess = new MockedResponse( 176 | 'DetectSentimentRetryOnErrorWithSuccess' 177 | ) 178 | .throw( 179 | 'InternalServerException', 180 | 'Server Exception while calling DetectSentiment API in Comprehend Service', 181 | 2 182 | ) 183 | .return({ 184 | Sentiment: 'POSITIVE', 185 | SentimentScore: { 186 | Mixed: 0.00012647535, 187 | Negative: 0.00008031699, 188 | Neutral: 0.0051454515, 189 | Positive: 0.9946478, 190 | }, 191 | }); 192 | 193 | const input = { 194 | data: { 195 | firstname: 'Jane', 196 | lastname: 'Doe', 197 | identity: { 198 | email: 'jdoe@example.com', 199 | ssn: '123-45-6789', 200 | }, 201 | address: { 202 | street: '123 Main St', 203 | city: 'Columbus', 204 | state: 'OH', 205 | zip: '43219', 206 | }, 207 | comments: 208 | 'I am glad to sign-up for this service. Looking forward to different options.', 209 | }, 210 | }; 211 | 212 | const stateMachineTestDefinition = new StateMachineTestDefinition( 213 | stateMachineName 214 | ) 215 | .addTestCase( 216 | new StateMachineTestCase('HappyPathTest') 217 | .withInput(input) 218 | .addMockedState( 219 | stateNames.checkIdentityStateName, 220 | checkIdentityLambdaMockedSuccess 221 | ) 222 | .addMockedState( 223 | stateNames.checkAddressStateName, 224 | checkAddressLambdaMockedSuccess 225 | ) 226 | .addMockedState( 227 | stateNames.detectSentimentStateName, 228 | detectSentimentPositive 229 | ) 230 | .addMockedState(stateNames.addToFollowUpStateName, addToFollowUpSuccess) 231 | .addMockedState( 232 | stateNames.customerAddedToFollowupStateName, 233 | customerAddedToFollowupSuccess 234 | ) 235 | ) 236 | .addTestCase( 237 | new StateMachineTestCase('NegativeSentimentTest') 238 | .withInput(input) 239 | .addMockedState( 240 | stateNames.checkIdentityStateName, 241 | checkIdentityLambdaMockedSuccess 242 | ) 243 | .addMockedState( 244 | stateNames.checkAddressStateName, 245 | checkAddressLambdaMockedSuccess 246 | ) 247 | .addMockedState( 248 | stateNames.detectSentimentStateName, 249 | detectSentimentNegative 250 | ) 251 | .addMockedState( 252 | stateNames.negativeSentimentDetectedStateName, 253 | negativeSentimentDetectedSuccess 254 | ) 255 | ) 256 | .addTestCase( 257 | new StateMachineTestCase('CustomValidationFailedCatchTest') 258 | .withInput(input) 259 | .addMockedState( 260 | stateNames.checkIdentityStateName, 261 | checkIdentityLambdaMockedThrowError 262 | ) 263 | .addMockedState( 264 | stateNames.checkAddressStateName, 265 | checkAddressLambdaMockedSuccess 266 | ) 267 | .addMockedState( 268 | stateNames.customValidationFailedStateName, 269 | customValidationFailedPutEventSuccess 270 | ) 271 | ) 272 | .addTestCase( 273 | new StateMachineTestCase('ValidationExceptionCatchTest') 274 | .withInput(input) 275 | .addMockedState( 276 | stateNames.checkIdentityStateName, 277 | checkIdentityLambdaMockedSuccess 278 | ) 279 | .addMockedState( 280 | stateNames.checkAddressStateName, 281 | checkAddressLambdaMockedThrowExceptionSuccess 282 | ) 283 | .addMockedState( 284 | stateNames.validationExceptionStateName, 285 | validationExceptionPutEventSuccess 286 | ) 287 | ) 288 | .addTestCase( 289 | new StateMachineTestCase('RetryOnServiceExceptionTest') 290 | .withInput(input) 291 | .addMockedState( 292 | stateNames.checkIdentityStateName, 293 | checkIdentityLambdaMockedSuccess 294 | ) 295 | .addMockedState( 296 | stateNames.checkAddressStateName, 297 | checkAddressLambdaMockedSuccess 298 | ) 299 | .addMockedState( 300 | stateNames.detectSentimentStateName, 301 | detectSentimentRetryOnErrorWithSuccess 302 | ) 303 | .addMockedState(stateNames.addToFollowUpStateName, addToFollowUpSuccess) 304 | .addMockedState( 305 | stateNames.customerAddedToFollowupStateName, 306 | customerAddedToFollowupSuccess 307 | ) 308 | ); 309 | 310 | const config = new StepFunctionsMockConfig(); 311 | config.addTestDefinition(stateMachineTestDefinition); 312 | 313 | const stack = new cdk.Stack(); 314 | new SalesLeadsStateMachineConstruct(stack, 'SalesLeadsStateMachine', { 315 | validationStateName: 'Validation', 316 | checkIdentityStateName: 'Check Identity', 317 | checkAddressStateName: 'Check Address', 318 | customValidationFailedStateName: 'CustomValidationFailed', 319 | validationExceptionStateName: 'ValidationException', 320 | detectSentimentStateName: 'DetectSentiment', 321 | isPositiveSentimentStateName: 'Is Positive Sentiment?', 322 | negativeSentimentDetectedStateName: 'NegativeSentimentDetected', 323 | addToFollowUpStateName: 'Add to FollowUp', 324 | customerAddedToFollowupStateName: 'CustomerAddedToFollowup', 325 | stateMachineName: stateMachineName, 326 | retryInterval: Duration.seconds(0), 327 | }); 328 | // extract the Asl part 329 | const asls = extractStateMachineAsls(stack); 330 | 331 | describe( 332 | 'SalesLeadsStateMachine', 333 | createJestTestFromMockConfig(config, asls[0]) 334 | ); 335 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | --------------------------------------------------------------------------------