/* * Copyright 2011-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.amazonaws.services.stepfunctions.builder.internal.validation; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.and; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.branch; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.catcher; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.choice; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.choiceState; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.end; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.eq; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.failState; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.next; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.parallelState; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.passState; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.retrier; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.seconds; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.secondsPath; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.stateMachine; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.succeedState; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.taskState; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.timestamp; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.timestampPath; import static com.amazonaws.services.stepfunctions.builder.StepFunctionBuilder.waitState; import com.amazonaws.services.stepfunctions.builder.ErrorCodes; import com.amazonaws.services.stepfunctions.builder.conditions.NotCondition; import org.junit.Test; public class StateMachineValidatorTest { @Test(expected = ValidationException.class) public void nothingSet_IsNotValid() { stateMachine().build(); } @Test(expected = ValidationException.class) public void noStates_IsNotValid() { stateMachine().startAt("Foo").build(); } @Test(expected = ValidationException.class) public void startAtStateDoesNotExist_IsNotValid() { stateMachine() .startAt("Foo") .state("Initial", succeedState()) .build(); } @Test public void validMinimalStateMachine_IsValid() { stateMachine() .startAt("Initial") .state("Initial", succeedState()) .build(); } @Test(expected = ValidationException.class) public void missingResourceInTaskState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end())) .build(); } @Test(expected = ValidationException.class) public void missingTransitionInTaskState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void invalidTransitionInTaskState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(next("NoSuchState")) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void negativeTimeoutSecondsInTaskState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .timeoutSeconds(-1) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void zeroTimeoutSecondsInTaskState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .timeoutSeconds(0) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void negativeHeartbeatSecondsInTaskState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .heartbeatSeconds(-1) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void zeroHeartbeatSecondsInTaskState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .heartbeatSeconds(0) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void heartbeatSecondsGreaterThanTimeoutSecondsInTaskState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .heartbeatSeconds(60) .timeoutSeconds(30) .resource("arn")) .build(); } @Test public void retrierInTaskState_OnlyErrorEqualsSet_IsValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .retrier(retrier() .retryOnAllErrors()) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void retrierInTaskState_MaxAttemptsNegative_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .retrier(retrier() .maxAttempts(-1) .retryOnAllErrors()) .resource("arn")) .build(); } @Test public void retrierInTaskState_MaxAttemptsZero_IsValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .retrier(retrier() .maxAttempts(0) .retryOnAllErrors()) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void retrierInTaskState_IntervalSecondsNegative_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .retrier(retrier() .intervalSeconds(-1) .retryOnAllErrors()) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void retrierInTaskState_IntervalSecondsZero_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .retrier(retrier() .intervalSeconds(0) .retryOnAllErrors()) .resource("arn")) .build(); } @Test public void retrierInTaskState_IntervalSecondsPositive_IsValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .retrier(retrier() .intervalSeconds(10) .retryOnAllErrors()) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void retrierInTaskState_BackoffRateNegative_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .retrier(retrier() .backoffRate(-1.0) .retryOnAllErrors()) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void retrierInTaskState_BackoffRateLessThanOne_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .retrier(retrier() .backoffRate(0.5) .retryOnAllErrors()) .resource("arn")) .build(); } @Test public void retrierInTaskState_BackoffRateGreaterThanOne_IsValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .retrier(retrier() .backoffRate(1.5) .retryOnAllErrors()) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void retrierInTaskState_RetryAllHasOtherErrorCodes_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .retrier(retrier() .errorEquals("Foo", "Bar", ErrorCodes.ALL)) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void retrierInTaskState_RetryAllIsNotLastRetrier_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .retrier(retrier() .retryOnAllErrors()) .retrier(retrier() .errorEquals("Foo", "Bar")) .resource("arn")) .build(); } @Test public void catcherInTaskState_ValidTransition_IsValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .catcher(catcher() .transition(next("Terminal")) .catchAll()) .resource("arn")) .state("Terminal", succeedState()) .build(); } @Test(expected = ValidationException.class) public void catcherInTaskState_InvalidTransition_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .catcher(catcher() .transition(next("NoSuchState")) .catchAll()) .resource("arn")) .build(); } @Test(expected = ValidationException.class) public void catcherInTaskState_CatchAllHasOtherErrorCodes_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .catcher(catcher() .transition(next("Terminal")) .errorEquals("Foo", "Bar", ErrorCodes.ALL)) .resource("arn")) .state("Terminal", succeedState()) .build(); } @Test(expected = ValidationException.class) public void catcherInTaskState_CatchAllIsNotLastCatcher_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", taskState() .transition(end()) .catcher(catcher() .transition(next("Terminal")) .catchAll()) .catcher(catcher() .transition(next("Terminal")) .errorEquals("Foo", "Bar")) .resource("arn")) .state("Terminal", succeedState()) .build(); } @Test(expected = ValidationException.class) public void invalidTransitionInWaitState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", waitState() .waitFor(seconds(10)) .transition(next("NoSuchState"))) .build(); } @Test(expected = ValidationException.class) public void noWaitForSupplied_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", waitState() .transition(end())) .build(); } @Test(expected = ValidationException.class) public void waitForSeconds_NegativeSeconds_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", waitState() .waitFor(seconds(-1)) .transition(end())) .build(); } @Test(expected = ValidationException.class) public void waitForSeconds_ZeroSeconds_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", waitState() .waitFor(seconds(0)) .transition(end())) .build(); } @Test(expected = ValidationException.class) public void waitForTimestamp_NullDate_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", waitState() .waitFor(timestamp(null)) .transition(end())) .build(); } @Test(expected = ValidationException.class) public void waitForTimestampPath_MissingPath_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", waitState() .waitFor(timestampPath(null)) .transition(end())) .build(); } @Test(expected = ValidationException.class) public void waitForTimestampPath_EmptyPath_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", waitState() .waitFor(timestampPath("")) .transition(end())) .build(); } @Test(expected = ValidationException.class) public void waitForSecondsPath_MissingPath_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", waitState() .waitFor(secondsPath(null)) .transition(end())) .build(); } @Test(expected = ValidationException.class) public void waitForSecondsPath_EmptyPath_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", waitState() .waitFor(secondsPath("")) .transition(end())) .build(); } @Test(expected = ValidationException.class) public void invalidTransitionInPassState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", passState() .transition(next("NoSuchState"))) .build(); } @Test public void validTransitionInPassState_IsValid() { stateMachine() .startAt("Initial") .state("Initial", passState() .transition(next("Terminal"))) .state("Terminal", succeedState()) .build(); } @Test(expected = ValidationException.class) public void missingCauseInFailState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", failState() .error("Error")) .build(); } @Test public void missingErrorInFailState_IsValid() { stateMachine() .startAt("Initial") .state("Initial", failState() .cause("Cause")) .build(); } @Test public void failStateWithErrorAndCause_IsValid() { stateMachine() .startAt("Initial") .state("Initial", failState() .error("Error") .cause("Cause")) .build(); } @Test(expected = ValidationException.class) public void choiceStateWithNoChoices_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", choiceState() .defaultStateName("Terminal")) .state("Terminal", succeedState()) .build(); } @Test(expected = ValidationException.class) public void choiceStateWithInvalidDefaultState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", choiceState() .choice(choice() .condition(eq("$.foo", "bar")) .transition(next("Terminal"))) .defaultStateName("NoSuchState")) .state("Terminal", succeedState()) .build(); } @Test(expected = ValidationException.class) public void choiceStateWithInvalidChoiceTransition_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", choiceState() .choice(choice() .condition(eq("$.foo", "bar")) .transition(next("NoSuchState"))) .defaultStateName("Terminal")) .state("Terminal", succeedState()) .build(); } @Test public void choiceStateWithNoDefaultTransition_IsValid() { stateMachine() .startAt("Initial") .state("Initial", choiceState() .choice(choice() .condition(eq("$.foo", "bar")) .transition(next("Terminal")))) .state("Terminal", succeedState()) .build(); } @Test(expected = ValidationException.class) public void missingVariable_StringEqualsCondition_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", choiceState() .choice(choice() .condition(eq(null, "foo")) .transition(next("Terminal"))) .defaultStateName("Terminal")) .state("Terminal", succeedState()) .build(); } @Test(expected = ValidationException.class) public void missingExpectedValue_StringEqualsCondition_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", choiceState() .choice(choice() .condition(eq("$.foo", (String) null)) .transition(next("Terminal"))) .defaultStateName("Terminal")) .state("Terminal", succeedState()) .build(); } @Test(expected = ValidationException.class) public void noConditionsInAnd_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", choiceState() .choice(choice() .condition(and()) .transition(next("Terminal"))) .defaultStateName("Terminal")) .state("Terminal", succeedState()) .build(); } @Test(expected = ValidationException.class) public void noConditionSetForNot_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", choiceState() .choice(choice() .condition(NotCondition.builder()) .transition(next("Terminal"))) .defaultStateName("Terminal")) .state("Terminal", succeedState()) .build(); } @Test(expected = ValidationException.class) public void parallelStateWithNoBranches_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", parallelState() .transition(end())) .build(); } @Test(expected = ValidationException.class) public void parallelStateWithInvalidTransition_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", parallelState() .branch(branch() .startAt("InitialBranchState") .state("InitialBranchState", succeedState())) .transition(next("NoSuchState"))) .build(); } @Test(expected = ValidationException.class) public void parallelStateBranchStartAtStateInvalid_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", parallelState() .branch(branch() .startAt("NoSuchState") .state("InitialBranchState", succeedState())) .transition(end())) .build(); } @Test(expected = ValidationException.class) public void parallelStateInvalidBranchState_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", parallelState() .branch(branch() .startAt("InitialBranchState") .state("InitialBranchState", failState())) .transition(end())) .build(); } @Test(expected = ValidationException.class) public void parallelStateInvalidRetrier_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", parallelState() .branch(branch() .startAt("InitialBranchState") .state("InitialBranchState", succeedState())) .transition(end()) .retrier(retrier() .intervalSeconds(-1))) .build(); } @Test(expected = ValidationException.class) public void parallelStateInvalidCatcher_IsNotValid() { stateMachine() .startAt("Initial") .state("Initial", parallelState() .branch(branch() .startAt("InitialBranchState") .state("InitialBranchState", succeedState())) .transition(end()) .catcher(catcher() .transition(next("NoSuchState")))) .build(); } }