diff --git a/build.gradle b/build.gradle index 6bc43211..f6083558 100644 --- a/build.gradle +++ b/build.gradle @@ -286,9 +286,15 @@ configure(sampleProjects()) { apply plugin: 'spring-boot' configurations.archives.artifacts.removeAll { it.archiveTask.is jar } tasks.findByPath("artifactoryPublish")?.enabled = false + // as samples are not published, we can use jdk8 + compileJava { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } dependencies { compile project(":spring-statemachine-core") compile "org.springframework:spring-context-support:$springVersion" + testCompile project(":spring-statemachine-test") testCompile "org.springframework:spring-test:$springVersion" testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion" testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion" diff --git a/docs/src/reference/asciidoc/images/model-deployer.png b/docs/src/reference/asciidoc/images/model-deployer.png new file mode 100644 index 00000000..73969e8a Binary files /dev/null and b/docs/src/reference/asciidoc/images/model-deployer.png differ diff --git a/docs/src/reference/asciidoc/images/sm-deploy-1.png b/docs/src/reference/asciidoc/images/sm-deploy-1.png new file mode 100644 index 00000000..9811a7fe Binary files /dev/null and b/docs/src/reference/asciidoc/images/sm-deploy-1.png differ diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index 9901d8a2..05914596 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -32,6 +32,8 @@ normal build cycle. Samples in this chapter are: <> Event Service. +<> Deploy. + [source,text] ---- @@ -1315,7 +1317,60 @@ should see `COUNT` variable to increase with every call. image::images/sm-eventservice-4.png[width=500] -Order to implement actual logic to track user behaviour, you'd then -define these in a various `Actions` in a state machine. You can -associate different `Actions` with transitions, state entry's or state -exits' and with various listener methods. +[[statemachine-examples-deploy]] +== Deploy +Deploy is an example how state machine concepts can be used with an +uml modeling to provide a generic error handling state. This state +machine is a relatively complex example of how various features can be +used to provide a centralized error handling concept. + +image::images/model-deployer.png[width=500] + +[NOTE] +==== +Above statechart is designed using _Eclipse Papyrus Plugin_ +<> and imported into _Spring StateMachine_ via its uml +model file. _Actions_ and _Guards_ defined in a model are resolved +from a _Spring Application Context_. +==== + +In this state machice scenario we have two different behaviors, +`DEPLOY` and `UNDEPLOY` what user tries to execute. + +What is happening a above statechart: + +* In `DEPLOY` state `INSTALL` and `START` states are entered + conditionally. We enter `START` directly if product is already + installed and no need to try to `START` if install fails. +* In `UNDEPLOY` state we enter `STOP` conditionally if application is + already running. +* Conditional choices for `DEPLOY` and `UNDEPLOY` are done via + _Choice_ pseudostate within those states and choices are selected + by _Guards_. +* We used _Exit Point_ pseudostates to have more controlled exit from + `DEPLOY` and `UNDEPLOY` states. +* After exit from `DEPLOY` and `UNDEPLOY` we go through a _Junction_ + pseudostate to make a choice if we want to go though `ERROR` state + in case error was added into an _Extended State_. +* Finally we go back to `READY` state to process new requests. + +Lets get into actual demo. Run the boot based sample application: + +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-deploy-{revnumber}.jar +---- + +In a browser you see something like: + +image::images/sm-deploy-1.png[width=500] + +[IMPORTANT] +==== +As we don't have a real _install_, _start_ or _stop_ functionality we +simulate failures by checking existence of particular message headers. +==== + +Now you can start to send event to a machine and choose various +message headers which will drive a different functionality. + diff --git a/settings.gradle b/settings.gradle index 295c042f..e2b594a6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,6 +22,7 @@ include 'spring-statemachine-samples:web' include 'spring-statemachine-samples:scope' include 'spring-statemachine-samples:security' include 'spring-statemachine-samples:eventservice' +include 'spring-statemachine-samples:deploy' rootProject.children.find { if (it.name == 'spring-statemachine-recipes') { diff --git a/spring-statemachine-samples/build.gradle b/spring-statemachine-samples/build.gradle index 5cbbfdfa..55402f76 100644 --- a/spring-statemachine-samples/build.gradle +++ b/spring-statemachine-samples/build.gradle @@ -92,3 +92,11 @@ project('spring-statemachine-samples-eventservice') { compile("org.apache.commons:commons-pool2:$commonsPool2Version") } } + +project('spring-statemachine-samples-deploy') { + description = 'Spring State Machine Deploy Sample' + dependencies { + compile project(":spring-statemachine-uml") + compile("org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion") + } +} diff --git a/spring-statemachine-samples/deploy/src/main/java/demo/deploy/Application.java b/spring-statemachine-samples/deploy/src/main/java/demo/deploy/Application.java new file mode 100644 index 00000000..b286a723 --- /dev/null +++ b/spring-statemachine-samples/deploy/src/main/java/demo/deploy/Application.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 demo.deploy; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-statemachine-samples/deploy/src/main/java/demo/deploy/StateMachineConfig.java b/spring-statemachine-samples/deploy/src/main/java/demo/deploy/StateMachineConfig.java new file mode 100644 index 00000000..272cc58a --- /dev/null +++ b/spring-statemachine-samples/deploy/src/main/java/demo/deploy/StateMachineConfig.java @@ -0,0 +1,152 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 demo.deploy; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineModelConfigurer; +import org.springframework.statemachine.config.model.StateMachineModelFactory; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.uml.UmlStateMachineModelFactory; + +@Configuration +public class StateMachineConfig { + + @Configuration + @EnableStateMachine + public static class Config extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true) + .listener(stateMachineLogListener()); + } + + @Override + public void configure(StateMachineModelConfigurer model) + throws Exception { + model + .withModel() + .factory(modelFactory()); + } + + @Bean + public StateMachineModelFactory modelFactory() { + return new UmlStateMachineModelFactory("classpath:model.uml"); + } + + @Bean + public StateMachineLogListener stateMachineLogListener() { + return new StateMachineLogListener(); + } + + @Bean + public Action errorEntryAction() { + return (context) -> { + System.out.println("Should handle error " + context.getMessageHeader("error")); + }; + } + + @Bean + public Action readyEntryction() { + return (context) -> { + context.getExtendedState().getVariables().clear(); + }; + } + + @Bean + public Action preparedeployEntryAction() { + return (context) -> { + System.out.println("Handle deploy prepare here"); + }; + } + + @Bean + public Action installEntryAction() { + return (context) -> { + System.out.println("Handle install here"); + }; + } + + @Bean + public Action startEntryAction() { + return (context) -> { + System.out.println("Handle start here"); + }; + } + + @Bean + public Action stopEntryAction() { + return (context) -> { + System.out.println("Handle stop here"); + }; + } + + @Bean + public Action exitErrorAction() { + return (context) -> { + if (context.getMessageHeaders().containsKey("hasError")) { + context.getExtendedState().getVariables().put("hasError", true); + context.getExtendedState().getVariables().put("error", new RuntimeException("Exception set in machine")); + } + if (context.getMessageHeaders().containsKey("isInstalled")) { + context.getExtendedState().getVariables().put("isInstalled", true); + } + if (context.getMessageHeaders().containsKey("installedOk")) { + context.getExtendedState().getVariables().put("installedOk", true); + } + if (context.getMessageHeaders().containsKey("isRunning")) { + context.getExtendedState().getVariables().put("isRunning", true); + } + }; + } + + @Bean + public Guard isInstalledGuard() { + return (context) -> { + return context.getExtendedState().getVariables().containsKey("isInstalled"); + }; + } + + @Bean + public Guard installedOkGuard() { + return (context) -> { + return context.getExtendedState().getVariables().containsKey("installedOk"); + }; + } + + @Bean + public Guard isRunningGuard() { + return (context) -> { + return context.getExtendedState().getVariables().containsKey("isRunning"); + }; + } + + @Bean + public Guard hasErrorGuard() { + return (context) -> { + return context.getExtendedState().getVariables().containsKey("hasError"); + }; + } + } +} diff --git a/spring-statemachine-samples/deploy/src/main/java/demo/deploy/StateMachineController.java b/spring-statemachine-samples/deploy/src/main/java/demo/deploy/StateMachineController.java new file mode 100644 index 00000000..c090824e --- /dev/null +++ b/spring-statemachine-samples/deploy/src/main/java/demo/deploy/StateMachineController.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 demo.deploy; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateMachine; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class StateMachineController { + + private final static String[] EVENTS = new String[] { "DEPLOY", "UNDEPLOY" }; + private final static String[] FAILS = new String[] { "isInstalled", "installedOk", "isRunning", "hasError" }; + + @Autowired + private StateMachine stateMachine; + + @Autowired + private StateMachineLogListener listener; + + @RequestMapping("/") + public String home() { + return "redirect:/state"; + } + + @RequestMapping("/state") + public String feedAndGetState(@RequestParam(value = "event", required = false) String event, + @RequestParam(value = "fail", required = false) Collection fails, Model model) throws Exception { + model.addAttribute("allTypes", EVENTS); + model.addAttribute("failTypes", FAILS); + if (StringUtils.hasText(event)) { + Map headers = new HashMap<>(); + if (fails != null) { + for (String fail : fails) { + headers.put(fail, true); + } + } + listener.resetMessages(); + stateMachine.sendEvent(MessageBuilder.createMessage(event, new MessageHeaders(headers))); + } + model.addAttribute("states", stateMachine.getState().getIds()); + model.addAttribute("messages", listener.getMessages()); + return "states"; + } +} diff --git a/spring-statemachine-samples/deploy/src/main/java/demo/deploy/StateMachineLogListener.java b/spring-statemachine-samples/deploy/src/main/java/demo/deploy/StateMachineLogListener.java new file mode 100644 index 00000000..4b48bcdc --- /dev/null +++ b/spring-statemachine-samples/deploy/src/main/java/demo/deploy/StateMachineLogListener.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 demo.deploy; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateContext.Stage; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; +import org.springframework.statemachine.state.State; + +public class StateMachineLogListener extends StateMachineListenerAdapter { + + private final List messages = new ArrayList(); + + public List getMessages() { + return messages; + } + + public void resetMessages() { + messages.clear(); + } + + @Override + public void stateEntered(State state) { +// messages.add("Enter " + state.getId()); + } + + @Override + public void stateExited(State state) { +// messages.add("Exit " + state.getId()); + } + + @Override + public void stateContext(StateContext stateContext) { + if (stateContext.getStage() == Stage.STATE_ENTRY) { + messages.add("Enter " + stateContext.getTarget().getId()); + if (stateContext.getTarget().getId().equals("ERROR")) { + messages.add("ERROR got exception " + stateContext.getExtendedState().getVariables().get("error")); + } + } else if (stateContext.getStage() == Stage.STATE_EXIT) { + messages.add("Exit " + stateContext.getSource().getId()); + } + } + +} diff --git a/spring-statemachine-samples/deploy/src/main/resources/application.yml b/spring-statemachine-samples/deploy/src/main/resources/application.yml new file mode 100644 index 00000000..5aa882d0 --- /dev/null +++ b/spring-statemachine-samples/deploy/src/main/resources/application.yml @@ -0,0 +1,3 @@ +security: + basic: + enabled: false diff --git a/spring-statemachine-samples/deploy/src/main/resources/logback.xml b/spring-statemachine-samples/deploy/src/main/resources/logback.xml new file mode 100644 index 00000000..39d50aa5 --- /dev/null +++ b/spring-statemachine-samples/deploy/src/main/resources/logback.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/spring-statemachine-samples/deploy/src/main/resources/model.di b/spring-statemachine-samples/deploy/src/main/resources/model.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-samples/deploy/src/main/resources/model.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-samples/deploy/src/main/resources/model.notation b/spring-statemachine-samples/deploy/src/main/resources/model.notation new file mode 100644 index 00000000..7655c663 --- /dev/null +++ b/spring-statemachine-samples/deploy/src/main/resources/model.notation @@ -0,0 +1,662 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-samples/deploy/src/main/resources/model.uml b/spring-statemachine-samples/deploy/src/main/resources/model.uml new file mode 100644 index 00000000..b036c036 --- /dev/null +++ b/spring-statemachine-samples/deploy/src/main/resources/model.uml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + bean + hasErrorGuard + + + + + + + + + bean + readyEntryction + + + bean + exitErrorAction + + + + + + + + + + bean + isInstalledGuard + + + + + + + + + + + bean + installedOkGuard + + + + + + + bean + preparedeployEntryAction + + + + + bean + installEntryAction + + + + + bean + startEntryAction + + + + + + + + + + + + + + + + bean + isRunningGuard + + + + + + + + + bean + stopEntryAction + + + + + + + + + bean + errorEntryAction + + + + + + + + + + + diff --git a/spring-statemachine-samples/deploy/src/main/resources/templates/states.html b/spring-statemachine-samples/deploy/src/main/resources/templates/states.html new file mode 100644 index 00000000..2d10e5af --- /dev/null +++ b/spring-statemachine-samples/deploy/src/main/resources/templates/states.html @@ -0,0 +1,39 @@ + + + + Spring Statemachine Deploy Demo + + + +
+

+

+ +
+
+

+

+
    +
  • + + +
  • +
+
+

+

+
    +
  • + + +
  • +
+ +
+ +
+

+

+

+ + diff --git a/spring-statemachine-samples/deploy/src/test/java/demo/deploy/DeployTests.java b/spring-statemachine-samples/deploy/src/test/java/demo/deploy/DeployTests.java new file mode 100644 index 00000000..79f03d4d --- /dev/null +++ b/spring-statemachine-samples/deploy/src/test/java/demo/deploy/DeployTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 demo.deploy; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = { Application.class}) +@WebAppConfiguration +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +public class DeployTests { + + private MockMvc mvc; + + @Autowired + private WebApplicationContext context; + + @Test + public void testHome() throws Exception { + mvc. + perform(get("/state")). + andExpect(status().isOk()); + } + + @Test + public void testDeploy() throws Exception { + mvc. + perform(get("/state").param("event", "DEPLOY")). + andExpect(status().isOk()). + andExpect(content().string(containsString("States: [READY]"))); + } + + @Test + public void testUndeploy() throws Exception { + mvc. + perform(get("/state").param("event", "UNDEPLOY")). + andExpect(status().isOk()). + andExpect(content().string(containsString("States: [READY]"))); + } + + @Before + public void setup() throws Exception { + mvc = MockMvcBuilders.webAppContextSetup(context).build(); + } +} diff --git a/spring-statemachine-samples/deploy/src/test/java/demo/deploy/StateMachineTests.java b/spring-statemachine-samples/deploy/src/test/java/demo/deploy/StateMachineTests.java new file mode 100644 index 00000000..dbe5619f --- /dev/null +++ b/spring-statemachine-samples/deploy/src/test/java/demo/deploy/StateMachineTests.java @@ -0,0 +1,137 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 demo.deploy; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.test.StateMachineTestPlan; +import org.springframework.statemachine.test.StateMachineTestPlanBuilder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = { StateMachineConfig.class}) +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +public class StateMachineTests { + + @Autowired + private StateMachine stateMachine; + + @Test + public void testDeployInstalledInstallOkInstallOk() throws Exception { + Message deploy = MessageBuilder.withPayload("DEPLOY") + .setHeader("isInstalled", true) + .setHeader("installedOk", true) + .setHeader("hasError", true).build(); + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectState("READY").and() + .step().sendEvent(deploy) + .expectStateChanged(6) + .expectStateEntered("DEPLOY", "PREPAREDEPLOY", "INSTALL", "START", "ERROR", "READY") + .expectStates("READY").and() + .build(); + plan.test(); + } + + @Test + public void testDeployInstalledInstallOkInstallError() throws Exception { + Message deploy = MessageBuilder.withPayload("DEPLOY") + .setHeader("isInstalled", true) + .setHeader("hasError", true).build(); + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectState("READY").and() + .step().sendEvent(deploy) + .expectStateChanged(5) + .expectStateEntered("DEPLOY", "PREPAREDEPLOY", "INSTALL", "ERROR", "READY") + .expectStates("READY").and() + .build(); + plan.test(); + } + + @Test + public void testDeployInstalledInstallFail() throws Exception { + Message deploy = MessageBuilder.withPayload("DEPLOY") + .setHeader("isInstalled", true).build(); + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectState("READY").and() + .step().sendEvent(deploy) + .expectStateChanged(4) + .expectStateEntered("DEPLOY", "PREPAREDEPLOY", "INSTALL", "READY") + .expectStates("READY").and() + .build(); + plan.test(); + } + + @Test + public void testUndeploy() throws Exception { + Message deploy = MessageBuilder.withPayload("UNDEPLOY").build(); + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectState("READY").and() + .step().sendEvent(deploy) + .expectStateChanged(3) + .expectStateEntered("UNDEPLOY", "PREPAREUNDEPLOY", "READY") + .expectStateExited("READY", "PREPAREUNDEPLOY", "UNDEPLOY") + .expectStates("READY").and() + .build(); + plan.test(); + } + + @Test + public void testUndeployRunningOk() throws Exception { + Message deploy = MessageBuilder.withPayload("UNDEPLOY") + .setHeader("isRunning", true) + .setHeader("hasError", true).build(); + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectState("READY").and() + .step().sendEvent(deploy) + .expectStateChanged(5) + .expectStateEntered("UNDEPLOY", "PREPAREUNDEPLOY", "STOP", "ERROR", "READY") + .expectStates("READY").and() + .build(); + plan.test(); + } + + @Test + public void testUndeployNotRunningOk() throws Exception { + Message undeploy = MessageBuilder.withPayload("UNDEPLOY").build(); + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectState("READY").and() + .step().sendEvent(undeploy) + .expectStateChanged(3) + .expectStateEntered("UNDEPLOY", "PREPAREUNDEPLOY", "READY") + .expectStates("READY").and() + .build(); + plan.test(); + } +}