diff --git a/README.md b/README.md index 49659b0..3ecd628 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ REST service for the creation and modification of nondeterministic finite automaton for the automatic generation of GUI tests with the help of a genetic algorithm. The service hides the actual implementation and defines a fixed interface for calls. Therefore, calling systems do not depend on the concrete implementation and it can be mocked easily for tests. +Basically, it does only provide only the two calls `getState` and `executeAction`. ## Automatic Build with TravisCI [![Build Status](https://travis-ci.org/retest/gui-state-machine-api.svg?branch=master)](https://travis-ci.org/retest/gui-state-machine-api) @@ -56,29 +57,6 @@ A state is defined by the set of all visible and interactable windows together w ## Scala API for GUI State Machines The package [api](./src/main/scala/de/retest/guistatemachine/api/) contains all types and methods for getting and modifying the GUI state machine. -## DSL -There is a DSL to construct an NFA with GUI actions manually. -The package [dsl](./src/main/scala/de/retest/guistatemachine/dsl/). - -The following example shows how to construct an NFA in Scala: -```scala -case object Start extends InitialState -case object S0 extends State -case object S1 extends State -case object End extends FinalState -case object EnterText extends Action -case object PressExitButton extends Action - -StateMachines { - StateMachine { - Start - EnterText - S0 - Start - EnterText - S1 - S0 - PressExitButton - End - S1 - PressExitButton - End - } -} -``` - ## REST API Some suggestions how the REST API for the state machine could look like: * `/state-machines` GET queries all existing state machines. @@ -90,19 +68,8 @@ Some suggestions how the REST API for the state machine could look like: * `/state-machine//state//transition/` GET queries a specific transition of a specific state. * `/state-machine//execute` POST executes the passed action from the passed state which might lead to a new state and adds a transition to the state machine. The action must be part of all actions? -Some suggestions on how the test representation REST API could look like (not necessarily required): - -* `/applications` GET queries all existing GUI applications. -* `/create-application` POST creates a new GUI application. -* `/application/` GET queries an existing GUI application. -* `/application/` DELETE deletes an existing GUI application and all of its test suites etc. -* `/application//test-suites` GET queries all test suites for an existing GUI application. -* `/application//create-test-suite` POST creates a new test suite for an existing GUI application. -* `/application//test-suite/` GET queries an existing test suite for an existing GUI application. -* `/application//test-suite/` DELETE deletes an existing test suite for an existing GUI application. - -## NFA Frameworks -This list contains frameworks for Scala which support the representation of an NFA: +## Possible NFA Frameworks +This list contains frameworks for Scala which support the representation of an NFA and could be used as backend to construct the state machine: * Akka FSM (FSM for actors): * Neo4J: * Gremlin-Scala: \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/api/Action.scala b/src/main/scala/de/retest/guistatemachine/api/Action.scala index f5ceecf..1b6f7f1 100644 --- a/src/main/scala/de/retest/guistatemachine/api/Action.scala +++ b/src/main/scala/de/retest/guistatemachine/api/Action.scala @@ -3,4 +3,8 @@ package de.retest.guistatemachine.api /** * Interaction from the user with the GUI. */ -case class Action(a : org.openqa.selenium.interactions.Action) \ No newline at end of file +case class Action(a: org.openqa.selenium.interactions.Action) { + + // TODO #5 Convert abstract representation of actions into string. + override def toString: String = "Selenium Action" +} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/api/Descriptors.scala b/src/main/scala/de/retest/guistatemachine/api/Descriptors.scala index 8a9d050..d52701e 100644 --- a/src/main/scala/de/retest/guistatemachine/api/Descriptors.scala +++ b/src/main/scala/de/retest/guistatemachine/api/Descriptors.scala @@ -5,4 +5,4 @@ import de.retest.ui.descriptors.RootElement /** * Set of root elements which identifies a state. */ -case class Descriptors(rootElements: Set[RootElement]) +case class Descriptors(rootElements: Set[RootElement]) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala index 8d73700..7517580 100644 --- a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala +++ b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala @@ -26,4 +26,4 @@ trait GuiStateMachine { * @return The current state which the transition of a leads to. */ def executeAction(from: State, a: Action, descriptors: Descriptors, neverExploredActions: Set[Action]): State -} +} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachineApi.scala b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachineApi.scala new file mode 100644 index 0000000..9a88725 --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachineApi.scala @@ -0,0 +1,17 @@ +package de.retest.guistatemachine.api + +trait GuiStateMachineApi { + + /** + * Creates a new [[GuiStateMachine]]. + * @return The new GUI state machine. + */ + def createStateMachine: GuiStateMachine + + /** + * Removes a persisted [[GuiStateMachine]]. + * @param stateMachine The persisted GUI state machine. + * @return True if it has been persisted before and is no remove. Otherwise, false. + */ + def removeStateMachine(stateMachine: GuiStateMachine): Boolean +} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/api/State.scala b/src/main/scala/de/retest/guistatemachine/api/State.scala index 1f7fdab..87c65db 100644 --- a/src/main/scala/de/retest/guistatemachine/api/State.scala +++ b/src/main/scala/de/retest/guistatemachine/api/State.scala @@ -5,6 +5,8 @@ package de.retest.guistatemachine.api * It consists of actions which have not been explored yet and transitions which build up the state machine. */ trait State { + def getDescriptors: Descriptors + def getNeverExploredActions: Set[Action] /** @@ -12,4 +14,29 @@ trait State { * Hence, we have a set of states per action. */ def getTransitions: Map[Action, Set[State]] -} + + /** + * Adds a new transition to the state which is only allowed by calling the methods of [[GuiStateMachine]]. + * @param a The action which represents the transition's consumed symbol. + * @param to The state which the transition leads t o. + */ + private[api] def addTransition(a: Action, to: State): Unit + + /** + * Overriding this method is required to allow the usage of a set of states. + * Comparing the descriptors should check for the equality of all root elements which compares the identifying attributes and the contained components + * for each root element. + */ + override def equals(obj: Any): Boolean = { + if (obj.isInstanceOf[State]) { + val other = obj.asInstanceOf[State] + this.getDescriptors == other.getDescriptors + } else { + super.equals(obj) + } + } + + override def hashCode(): Int = this.getDescriptors.hashCode() + + override def toString: String = s"descriptors=${getDescriptors},neverExploredActions=${getNeverExploredActions},transitions=${getTransitions}" +} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImpl.scala b/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImpl.scala new file mode 100644 index 0000000..c440e39 --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImpl.scala @@ -0,0 +1,19 @@ +package de.retest.guistatemachine.api.impl + +import de.retest.guistatemachine.api.GuiStateMachineApi +import de.retest.guistatemachine.api.GuiStateMachine + +import scala.collection.mutable.HashSet + +object GuiStateMachineApiImpl extends GuiStateMachineApi { + // TODO #4 Use Persistence instead of a custom set? + val stateMachines = new HashSet[GuiStateMachine] + + override def createStateMachine: GuiStateMachine = { + val r = new GuiStateMachineImpl + stateMachines += r + r + } + + override def removeStateMachine(stateMachine: GuiStateMachine): Boolean = stateMachines.remove(stateMachine) +} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala b/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala index fb2481c..61e7235 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala @@ -4,7 +4,7 @@ import de.retest.guistatemachine.api.{Action, Descriptors, GuiStateMachine, Stat import scala.collection.mutable.HashMap -object GuiStateMachineImpl extends GuiStateMachine { +class GuiStateMachineImpl extends GuiStateMachine { val states = new HashMap[Descriptors, State] override def getState(descriptors: Descriptors, neverExploredActions: Set[Action]): State = { @@ -19,7 +19,7 @@ object GuiStateMachineImpl extends GuiStateMachine { override def executeAction(from: State, a: Action, descriptors: Descriptors, neverExploredActions: Set[Action]): State = { val to = getState(descriptors, neverExploredActions) - from.asInstanceOf[StateImpl].addTransition(a, to) + from.addTransition(a, to) to } -} +} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala b/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala index 118cee3..2556093 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala @@ -7,36 +7,21 @@ import scala.collection.immutable.{HashMap, HashSet} class StateImpl(val descriptors: Descriptors, var neverExploredActions: Set[Action]) extends State { /** - * TODO #4 Currently, there is no MultiMap trait for immutable maps. + * TODO #4 Currently, there is no MultiMap trait for immutable maps in the Scala standard library. */ var transitions = new HashMap[Action, Set[State]] + override def getDescriptors: Descriptors = descriptors override def getNeverExploredActions: Set[Action] = neverExploredActions override def getTransitions: Map[Action, Set[State]] = transitions - def addTransition(a: Action, to: State): Unit = { + private[api] override def addTransition(a: Action, to: State): Unit = { if (!transitions.contains(a)) { transitions = transitions + (a -> HashSet(to)) - // TODO #4 This is not done in the legacy code: + // In the legacy code this is done in `increaseTimesExecuted`. neverExploredActions = neverExploredActions - a } else { transitions = transitions + (a -> (transitions(a) + to)) } } - - /** - * Overriding this method is required to allow the usage of a set of states. - * Comparing the descriptors should check for the equality of all root elements which compares the identifying attributes and the contained components - * for each root element. - */ - override def equals(obj: Any): Boolean = { - if (obj.isInstanceOf[StateImpl]) { - val other = obj.asInstanceOf[StateImpl] - this.descriptors == other.descriptors - } else { - super.equals(obj) - } - } - - override def hashCode(): Int = this.descriptors.hashCode() -} +} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/dsl/Action.scala b/src/main/scala/de/retest/guistatemachine/dsl/Action.scala deleted file mode 100644 index 2378220..0000000 --- a/src/main/scala/de/retest/guistatemachine/dsl/Action.scala +++ /dev/null @@ -1,3 +0,0 @@ -package de.retest.guistatemachine.dsl - -trait Action \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/dsl/FinalState.scala b/src/main/scala/de/retest/guistatemachine/dsl/FinalState.scala deleted file mode 100644 index b008214..0000000 --- a/src/main/scala/de/retest/guistatemachine/dsl/FinalState.scala +++ /dev/null @@ -1,6 +0,0 @@ -package de.retest.guistatemachine.dsl - -/** - * NFAs can have more than one final state. - */ -trait FinalState extends State \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/dsl/InitialState.scala b/src/main/scala/de/retest/guistatemachine/dsl/InitialState.scala deleted file mode 100644 index fdbb4b2..0000000 --- a/src/main/scala/de/retest/guistatemachine/dsl/InitialState.scala +++ /dev/null @@ -1,6 +0,0 @@ -package de.retest.guistatemachine.dsl - -/** - * NFAs have only one initial state. - */ -trait InitialState extends State \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/dsl/State.scala b/src/main/scala/de/retest/guistatemachine/dsl/State.scala deleted file mode 100644 index ecd7763..0000000 --- a/src/main/scala/de/retest/guistatemachine/dsl/State.scala +++ /dev/null @@ -1,30 +0,0 @@ -package de.retest.guistatemachine.dsl - -import scala.collection.mutable.ListBuffer - -trait State { - /** - * The previous state has to be stored for the DSL only to reach the initial state. - */ - private[dsl] var previous: State = null - // TODO Use a set instead of a list buffer? Actually it is a multi map with the action as key and multiple possible states as values. - private[dsl] var transitions: ListBuffer[Transition] = ListBuffer.empty[Transition] - - def getTransitions: Seq[Transition] = transitions - - /** - * Goes back to the initial state from the current state and returns it. - */ - def getInitial: InitialState = { - def getFirst: State = if (previous eq null) this else previous.getInitial - - getFirst.asInstanceOf[InitialState] - } - - // TODO Rename to -> - def -(a: Action): Transition = { - val t = Transition(this, a) - transitions += t - t - } -} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/dsl/StateMachine.scala b/src/main/scala/de/retest/guistatemachine/dsl/StateMachine.scala deleted file mode 100644 index 398192b..0000000 --- a/src/main/scala/de/retest/guistatemachine/dsl/StateMachine.scala +++ /dev/null @@ -1,50 +0,0 @@ -package de.retest.guistatemachine.dsl - -import scala.collection.immutable.HashSet - -/** - * NFA: - * 5–Tupel (Z, Σ, δ, z0, E) - * TODO NFAs can have multiple initial states. Do we really need this? - * TODO Make the constructor private. - */ -case class StateMachine(initial: InitialState, var previous: StateMachine) { - - def getInitial: InitialState = initial - - /** - * Appends another state machine. - */ - def ~(s: StateMachine): StateMachine = { - s.previous = this - s - } - - /** - * All states. - */ - //def Z(): Set[State] - /** - * Input alphabet. - */ - //def Σ(): Set[Action] - /** - * Partially defined function which returns the next state. - */ - //def δ(s: State, a: Action): State - /** - * Initial state. - * TODO NFAs can have multiple initial states. Do we really need this? - */ - //def z0: InitialState = initial - - /** - * All final states. - */ - //def E: Set[FinalState] -} - -object StateMachine { - // TODO f should return a FinalState since all state machines have to end with one - def apply(f: => State): StateMachine = StateMachine(f.getInitial, null) -} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/dsl/StateMachines.scala b/src/main/scala/de/retest/guistatemachine/dsl/StateMachines.scala deleted file mode 100644 index ab5bf16..0000000 --- a/src/main/scala/de/retest/guistatemachine/dsl/StateMachines.scala +++ /dev/null @@ -1,25 +0,0 @@ -package de.retest.guistatemachine.dsl - -import scala.collection.immutable.HashMap - -object StateMachines { - def apply(f: => StateMachine): Seq[StateMachine] = { - def constructSeq(s: StateMachine, seq: Seq[StateMachine]): Seq[StateMachine] = - if (s.previous ne null) constructSeq(s.previous, Seq(s) ++ seq) else Seq(s) ++ seq - - constructSeq(f, Seq()) - } - - def toModel(s: Seq[StateMachine]): de.retest.guistatemachine.model.StateMachines = { - val hashmap = new HashMap[de.retest.guistatemachine.model.Id, de.retest.guistatemachine.model.StateMachine]() - - // TODO Implement conversion from the DSL to the model. - /* - s.foreach(x => { - de.retest.guistatemachine.model.StateMachine - }) - */ - - de.retest.guistatemachine.model.StateMachines(de.retest.guistatemachine.model.Map[de.retest.guistatemachine.model.StateMachine](hashmap)) - } -} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/dsl/Transition.scala b/src/main/scala/de/retest/guistatemachine/dsl/Transition.scala deleted file mode 100644 index cb36d66..0000000 --- a/src/main/scala/de/retest/guistatemachine/dsl/Transition.scala +++ /dev/null @@ -1,15 +0,0 @@ -package de.retest.guistatemachine.dsl - -// TODO Make constructor private pls -case class Transition(from: State, a: Action, var to: State = null) { - - def getAction: Action = a - def getTo: State = to - - // TODO Rename to -> - def -(to: State): State = { - to.previous = from - this.to = to - to - } -} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/dsl/UnknownState.scala b/src/main/scala/de/retest/guistatemachine/dsl/UnknownState.scala deleted file mode 100644 index db09e64..0000000 --- a/src/main/scala/de/retest/guistatemachine/dsl/UnknownState.scala +++ /dev/null @@ -1,6 +0,0 @@ -package de.retest.guistatemachine.dsl - -/** - * All actions can be executed for an unknown state. - */ -case object UnknownState extends State \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/Action.scala b/src/main/scala/de/retest/guistatemachine/model/Action.scala deleted file mode 100644 index 08c8b74..0000000 --- a/src/main/scala/de/retest/guistatemachine/model/Action.scala +++ /dev/null @@ -1,3 +0,0 @@ -package de.retest.guistatemachine.model - -case class Action() \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/Actions.scala b/src/main/scala/de/retest/guistatemachine/model/Actions.scala deleted file mode 100644 index 69df23f..0000000 --- a/src/main/scala/de/retest/guistatemachine/model/Actions.scala +++ /dev/null @@ -1,3 +0,0 @@ -package de.retest.guistatemachine.model - -case class Actions(actions: Map[Action]) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/GuiApplication.scala b/src/main/scala/de/retest/guistatemachine/model/GuiApplication.scala deleted file mode 100644 index f7a5189..0000000 --- a/src/main/scala/de/retest/guistatemachine/model/GuiApplication.scala +++ /dev/null @@ -1,3 +0,0 @@ -package de.retest.guistatemachine.model - -final case class GuiApplication(testSuites: TestSuites) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/GuiApplications.scala b/src/main/scala/de/retest/guistatemachine/model/GuiApplications.scala deleted file mode 100644 index 3314403..0000000 --- a/src/main/scala/de/retest/guistatemachine/model/GuiApplications.scala +++ /dev/null @@ -1,3 +0,0 @@ -package de.retest.guistatemachine.model - -final case class GuiApplications(apps: Map[GuiApplication]) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/Map.scala b/src/main/scala/de/retest/guistatemachine/model/Map.scala deleted file mode 100644 index 705177c..0000000 --- a/src/main/scala/de/retest/guistatemachine/model/Map.scala +++ /dev/null @@ -1,14 +0,0 @@ -package de.retest.guistatemachine.model - -/** - * This custom type allows storing values using [[Id]] as key. - * [[JsonFormatForIdMap]] implements marshalling and unmarshalling for JSON for this type. - * We cannot extend immutable maps in Scala, so we have to keep it as field. - */ -case class Map[T](var values: scala.collection.immutable.Map[Id, T]) { - /** - * Generates a new ID based on the existing entries. - * TODO Generate IDs in a better way. Maybe random numbers until one unused element is found? - */ - def generateId: Id = this.synchronized { if (values.isEmpty) Id(0) else Id(values.keySet.max.id + 1) } -} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/State.scala b/src/main/scala/de/retest/guistatemachine/model/State.scala deleted file mode 100644 index c115b92..0000000 --- a/src/main/scala/de/retest/guistatemachine/model/State.scala +++ /dev/null @@ -1,5 +0,0 @@ -package de.retest.guistatemachine.model - -import scala.collection.immutable.HashMap - -final case class State(transitions: Transitions) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/StateMachine.scala b/src/main/scala/de/retest/guistatemachine/model/StateMachine.scala deleted file mode 100644 index 6b33275..0000000 --- a/src/main/scala/de/retest/guistatemachine/model/StateMachine.scala +++ /dev/null @@ -1,6 +0,0 @@ -package de.retest.guistatemachine.model - -/** - * State machine which represents a GUI test. - */ -final case class StateMachine(states: States, actions: Actions) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/States.scala b/src/main/scala/de/retest/guistatemachine/model/States.scala deleted file mode 100644 index 585ece9..0000000 --- a/src/main/scala/de/retest/guistatemachine/model/States.scala +++ /dev/null @@ -1,3 +0,0 @@ -package de.retest.guistatemachine.model - -case class States(states: Map[State]) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/TestSuite.scala b/src/main/scala/de/retest/guistatemachine/model/TestSuite.scala deleted file mode 100644 index 27b104f..0000000 --- a/src/main/scala/de/retest/guistatemachine/model/TestSuite.scala +++ /dev/null @@ -1,3 +0,0 @@ -package de.retest.guistatemachine.model - -final case class TestSuite() \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/TestSuites.scala b/src/main/scala/de/retest/guistatemachine/model/TestSuites.scala deleted file mode 100644 index bc22830..0000000 --- a/src/main/scala/de/retest/guistatemachine/model/TestSuites.scala +++ /dev/null @@ -1,3 +0,0 @@ -package de.retest.guistatemachine.model - -final case class TestSuites(suites: Map[TestSuite]) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/Transitions.scala b/src/main/scala/de/retest/guistatemachine/model/Transitions.scala deleted file mode 100644 index b7a60b0..0000000 --- a/src/main/scala/de/retest/guistatemachine/model/Transitions.scala +++ /dev/null @@ -1,3 +0,0 @@ -package de.retest.guistatemachine.model - -final case class Transitions(transitions: Map[Transition]) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/persistence/Persistence.scala b/src/main/scala/de/retest/guistatemachine/persistence/Persistence.scala index fc5eaa8..c5b4d6e 100644 --- a/src/main/scala/de/retest/guistatemachine/persistence/Persistence.scala +++ b/src/main/scala/de/retest/guistatemachine/persistence/Persistence.scala @@ -1,106 +1,19 @@ package de.retest.guistatemachine.persistence -import scala.collection.immutable.HashMap +import de.retest.guistatemachine.rest.model.{Id, Map, StateMachine, StateMachines} -import de.retest.guistatemachine.model.GuiApplication -import de.retest.guistatemachine.model.GuiApplications -import de.retest.guistatemachine.model.Id -import de.retest.guistatemachine.model.TestSuite -import de.retest.guistatemachine.model.TestSuites -import de.retest.guistatemachine.model.Map -import de.retest.guistatemachine.model.StateMachines -import de.retest.guistatemachine.model.StateMachine +import scala.collection.immutable.HashMap -/** - * Allows concurrent access to the persistence of the resources. - * The actual persistence layer is hidden by this class. - */ class Persistence { // database private val stateMachines = StateMachines(Map(new HashMap[Id, StateMachine])) - private val guiApplications = GuiApplications(Map(new HashMap[Id, GuiApplication])) def getStateMachines(): StateMachines = stateMachines - def getApplications(): GuiApplications = guiApplications - - def addApplication(): Id = { - val apps = guiApplications - apps.synchronized { - val id = apps.apps.generateId - apps.apps.values = apps.apps.values + (id -> GuiApplication(TestSuites(Map(new HashMap[Id, TestSuite])))) - id - } - } - def getApplication(id: Id): Option[GuiApplication] = { - val apps = guiApplications - apps.synchronized { - if (apps.apps.values.contains(id)) Some(apps.apps.values(id)) else { None } - } - } - - def deleteApplication(id: Id): Boolean = { - val apps = guiApplications - apps.synchronized { - if (apps.apps.values.contains(id)) { - apps.apps.values = apps.apps.values - id - true - } else { - false - } - } - } - - def getTestSuites(applicationId: Id): Option[TestSuites] = { - val app = getApplication(applicationId) - app match { - case Some(x) => Some(x.testSuites) - case None => None - } - } + def getStateMachine(id: Id): Option[StateMachine] = if (stateMachines.stateMachines.hasElement(id)) Some(stateMachines.stateMachines.getElement(id)) else None - def addTestSuite(applicationId: Id): Option[Id] = { - val app = getApplication(applicationId) - app match { - case Some(x) => { - val testSuites = x.testSuites - testSuites.synchronized { - val id = testSuites.suites.generateId - testSuites.suites.values = testSuites.suites.values + (id -> TestSuite()) - Some(id) - } - } - case None => None - } - } - def getTestSuite(applicationId: Id, testSuiteId: Id): Option[TestSuite] = { - val app = getApplication(applicationId) - app match { - case Some(x) => { - val testSuites = x.testSuites - testSuites.synchronized { - if (testSuites.suites.values.contains(testSuiteId)) Some(testSuites.suites.values(testSuiteId)) else None - } - } - case None => None - } - } + // TODO #4 Pass all unexplored actions for the initial state! + def createStateMachine(): Id = stateMachines.stateMachines.addNewElement(StateMachine()) - def deleteTestSuite(applicationId: Id, testSuiteId: Id): Boolean = { - val app = getApplication(applicationId) - app match { - case Some(x) => { - val testSuites = x.testSuites - testSuites.synchronized { - if (testSuites.suites.values.contains(testSuiteId)) { - testSuites.suites.values = testSuites.suites.values - testSuiteId - true - } else { - false - } - } - } - case None => false - } - } + def deleteStateMachine(id: Id): Boolean = stateMachines.stateMachines.removeElement(id) } \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/rest/JsonFormatForIdMap.scala b/src/main/scala/de/retest/guistatemachine/rest/JsonFormatForIdMap.scala index 8cba7fc..0194428 100644 --- a/src/main/scala/de/retest/guistatemachine/rest/JsonFormatForIdMap.scala +++ b/src/main/scala/de/retest/guistatemachine/rest/JsonFormatForIdMap.scala @@ -1,30 +1,27 @@ package de.retest.guistatemachine.rest -import de.retest.guistatemachine.model.Map -import de.retest.guistatemachine.model.Id -import spray.json.JsValue -import spray.json.JsonFormat -import spray.json.RootJsonFormat +import de.retest.guistatemachine.rest.model.Id +import spray.json.{JsValue, JsonFormat, RootJsonFormat} /** - * Transforms a [[de.retest.guistatemachine.model.Map]] into a `scala.collection.immutable.Map[String, T]`, so it can be converted into valid JSON. - * Besides, transforms a JSON object which is a `scala.collection.immutable.Map[String, T]` back into a [[de.retest.guistatemachine.model.Map]]. + * Transforms a [[model.Map]] into a `scala.collection.immutable.Map[String, T]`, so it can be converted into valid JSON. + * Besides, transforms a JSON object which is a `scala.collection.immutable.Map[String, T]` back into a [[model.Map]]. * This transformer requires a JSON format for the type `K`. */ class JsonFormatForIdMap[T](implicit val jsonFormat0: JsonFormat[scala.collection.immutable.Map[String, T]], implicit val jsonFormat1: JsonFormat[T]) - extends RootJsonFormat[Map[T]] { + extends RootJsonFormat[model.Map[T]] { - override def write(obj: Map[T]): JsValue = + override def write(obj: model.Map[T]): JsValue = jsonFormat0.write(obj.values.map { field => (field._1.id.toString -> field._2) }) - override def read(json: JsValue): Map[T] = { + override def read(json: JsValue): model.Map[T] = { val map = jsonFormat0.read(json) - new Map[T](map.map { x => + new model.Map[T](map.map { x => (Id(x._1.toLong) -> x._2) }) } -} +} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/rest/RestService.scala b/src/main/scala/de/retest/guistatemachine/rest/RestService.scala index c81b3a4..e73822a 100644 --- a/src/main/scala/de/retest/guistatemachine/rest/RestService.scala +++ b/src/main/scala/de/retest/guistatemachine/rest/RestService.scala @@ -1,31 +1,14 @@ package de.retest.guistatemachine.rest import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.stream.ActorMaterializer -import akka.Done -import akka.http.scaladsl.server.Route -import akka.http.scaladsl.server.Directives._ -import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ -import spray.json.DefaultJsonProtocol._ -import spray.json._ - -import de.retest.guistatemachine.persistence.Persistence -import de.retest.guistatemachine.model.GuiApplications -import de.retest.guistatemachine.model.GuiApplication -import de.retest.guistatemachine.model.TestSuite -import de.retest.guistatemachine.model.TestSuites -import de.retest.guistatemachine.model.Id +import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.server.Directives._ +import akka.http.scaladsl.server.Route +import akka.stream.ActorMaterializer import de.retest.guistatemachine.persistence.Persistence -import de.retest.guistatemachine.model.StateMachine -import de.retest.guistatemachine.model.Transition -import de.retest.guistatemachine.model.Transitions -import de.retest.guistatemachine.model.Action -import de.retest.guistatemachine.model.Actions -import de.retest.guistatemachine.model.StateMachines -import de.retest.guistatemachine.model.State -import de.retest.guistatemachine.model.States +import de.retest.guistatemachine.rest.model.{Action, Actions, Id, State, StateMachine, StateMachines, States, Transition, Transitions} +import spray.json.DefaultJsonProtocol._ trait RestService { implicit val system: ActorSystem @@ -47,19 +30,6 @@ trait RestService { implicit val idMapFormatStateMachines = new JsonFormatForIdMap[StateMachine] implicit val stateMachinesFormat = jsonFormat1(StateMachines) - implicit val testSuiteFormat = jsonFormat0(TestSuite) - implicit val idMapFormatTestSuites = new JsonFormatForIdMap[TestSuite] - implicit val testSuitesFormat = jsonFormat1(TestSuites) - implicit val applicationFormat = jsonFormat1(GuiApplication) - implicit val idMapFormatApplications = new JsonFormatForIdMap[GuiApplication] - implicit val applicationsFormat = jsonFormat1(GuiApplications) - - /** - * Creates the complete route for the REST service with all possible paths. - * Note that the order of path prefixes is important. - * For example, if "application/LongNumber" comes before "application/LongNumber/bla", the second path - * will always be ignored. - */ def getRoute(persistence: Persistence): Route = get { pathSingleSlash { @@ -68,51 +38,23 @@ trait RestService { path("state-machines") { complete(persistence.getStateMachines()) } ~ - path("applications") { - complete(persistence.getApplications()) - } ~ - path("application" / LongNumber / "test-suites") { id => - val testSuites = persistence.getTestSuites(Id(id)) - testSuites match { - case Some(x) => complete(x) - case None => complete(StatusCodes.NotFound) - } - } ~ - path("application" / LongNumber / "test-suite" / LongNumber) { (appId, suiteId) => - val suite = persistence.getTestSuite(Id(appId), Id(suiteId)) - suite match { - case Some(x) => complete(x) - case None => complete(StatusCodes.NotFound) - } - } ~ - path("application" / LongNumber) { id => - val app = persistence.getApplication(Id(id)) + path("state-machine" / LongNumber) { id => + val app = persistence.getStateMachine(Id(id)) app match { case Some(x) => complete(x) - case None => complete(StatusCodes.NotFound) + case None => complete(StatusCodes.NotFound) } } } ~ post { - path("create-application") { - val id = persistence.addApplication() + path("create-state-machine") { + val id = persistence.createStateMachine() complete(id) - } ~ - pathPrefix("application" / LongNumber / "create-test-suite") { appId => - { - val id = persistence.addTestSuite(Id(appId)) - complete(id) - } - } - } ~ delete { - path("application" / LongNumber / "test-suite" / LongNumber) { (appId, suiteId) => - val r = persistence.deleteTestSuite(Id(appId), Id(suiteId)) - complete(if (r) StatusCodes.OK else StatusCodes.NotFound) } } ~ delete { - path("application" / LongNumber) { id => - val r = persistence.deleteApplication(Id(id)) - complete(if (r) StatusCodes.OK else StatusCodes.NotFound) - } + path("state-machine" / LongNumber) { stateMachineId => + val r = persistence.deleteStateMachine(Id(stateMachineId)) + complete(if (r) StatusCodes.OK else StatusCodes.NotFound) } + } } \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/rest/WebServer.scala b/src/main/scala/de/retest/guistatemachine/rest/WebServer.scala index 582cfb4..625be27 100644 --- a/src/main/scala/de/retest/guistatemachine/rest/WebServer.scala +++ b/src/main/scala/de/retest/guistatemachine/rest/WebServer.scala @@ -1,7 +1,5 @@ package de.retest.guistatemachine.rest -import scala.io.StdIn - import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.server.RouteResult.route2HandlerFlow @@ -9,9 +7,11 @@ import akka.stream.ActorMaterializer import de.retest.guistatemachine.persistence.Persistence import scopt.OptionParser +import scala.io.StdIn + object WebServer extends App with RestService { - final val HOST = "localhost" - final val PORT = 8888 + final val Host = "localhost" + final val Port = 8888 implicit val system = ActorSystem("gui-state-machine-api-system") implicit val materializer = ActorMaterializer() @@ -34,9 +34,9 @@ object WebServer extends App with RestService { case Some(config) => val persistence = new Persistence - val bindingFuture = Http().bindAndHandle(getRoute(persistence), HOST, PORT) + val bindingFuture = Http().bindAndHandle(getRoute(persistence), Host, Port) - println(s"Server online at http://${HOST}:${PORT}/") + println(s"Server online at http://${Host}:${Port}/") if (config.maxtime < 0) { println("Press RETURN to stop...") @@ -48,6 +48,5 @@ object WebServer extends App with RestService { bindingFuture .flatMap(_.unbind()) // trigger unbinding from the port .onComplete(_ => system.terminate()) // and shutdown when done - case None => } } \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/rest/model/Action.scala b/src/main/scala/de/retest/guistatemachine/rest/model/Action.scala new file mode 100644 index 0000000..fe5a367 --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/rest/model/Action.scala @@ -0,0 +1,3 @@ +package de.retest.guistatemachine.rest.model + +case class Action() \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/rest/model/Actions.scala b/src/main/scala/de/retest/guistatemachine/rest/model/Actions.scala new file mode 100644 index 0000000..ef15e6c --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/rest/model/Actions.scala @@ -0,0 +1,3 @@ +package de.retest.guistatemachine.rest.model + +case class Actions(actions: Map[Action]) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/Id.scala b/src/main/scala/de/retest/guistatemachine/rest/model/Id.scala similarity index 58% rename from src/main/scala/de/retest/guistatemachine/model/Id.scala rename to src/main/scala/de/retest/guistatemachine/rest/model/Id.scala index a02f591..7c898e2 100644 --- a/src/main/scala/de/retest/guistatemachine/model/Id.scala +++ b/src/main/scala/de/retest/guistatemachine/rest/model/Id.scala @@ -1,7 +1,6 @@ -package de.retest.guistatemachine.model +package de.retest.guistatemachine.rest.model final case class Id(val id: Long) extends Ordered[Id] { - import scala.math.Ordered.orderingToOrdered override def compare(that: Id): Int = this.id compare that.id } \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/rest/model/Map.scala b/src/main/scala/de/retest/guistatemachine/rest/model/Map.scala new file mode 100644 index 0000000..d6974db --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/rest/model/Map.scala @@ -0,0 +1,42 @@ +package de.retest.guistatemachine.rest.model + +import scala.collection.immutable.HashMap + +/** + * This custom type allows storing values using [[Id]] as key. + * [[de.retest.guistatemachine.rest.JsonFormatForIdMap]] implements marshalling and unmarshalling for JSON for this type. + * We cannot extend immutable maps in Scala, so we have to keep it as field. + */ +case class Map[T](var values: scala.collection.immutable.Map[Id, T] = new HashMap[Id, T]) { + + /** + * Generates a new ID based on the existing entries. + * TODO #1 Generate IDs in a better way. Maybe random numbers until one unused element is found? + */ + def generateId: Id = if (values.isEmpty) Id(0) else Id(values.keySet.max.id + 1) + def addNewElement(v: T): Id = { + val generatedId = generateId + values = values + (generatedId -> v) + generatedId + } + + def removeElement(id: Id): Boolean = + if (values.contains(id)) { + values = values - id + true + } else { + false + } + + def getElement(id: Id): T = values(id) + + def hasElement(id: Id): Boolean = values.contains(id) +} + +object Map { + def fromValues[T](v: T*): Map[T] = { + val r = Map[T]() + for (e <- v) r.addNewElement(e) + r + } +} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/rest/model/State.scala b/src/main/scala/de/retest/guistatemachine/rest/model/State.scala new file mode 100644 index 0000000..2282f7d --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/rest/model/State.scala @@ -0,0 +1,3 @@ +package de.retest.guistatemachine.rest.model + +final case class State(transitions: Transitions) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/rest/model/StateMachine.scala b/src/main/scala/de/retest/guistatemachine/rest/model/StateMachine.scala new file mode 100644 index 0000000..1706747 --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/rest/model/StateMachine.scala @@ -0,0 +1,6 @@ +package de.retest.guistatemachine.rest.model + +/** + * State machine which represents a GUI test. + */ +final case class StateMachine(states: States = States(Map.fromValues[State](State(Transitions()))), actions: Actions = Actions(Map())) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/StateMachines.scala b/src/main/scala/de/retest/guistatemachine/rest/model/StateMachines.scala similarity index 59% rename from src/main/scala/de/retest/guistatemachine/model/StateMachines.scala rename to src/main/scala/de/retest/guistatemachine/rest/model/StateMachines.scala index c88252d..c48c492 100644 --- a/src/main/scala/de/retest/guistatemachine/model/StateMachines.scala +++ b/src/main/scala/de/retest/guistatemachine/rest/model/StateMachines.scala @@ -1,3 +1,3 @@ -package de.retest.guistatemachine.model +package de.retest.guistatemachine.rest.model final case class StateMachines(stateMachines: Map[StateMachine]) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/rest/model/States.scala b/src/main/scala/de/retest/guistatemachine/rest/model/States.scala new file mode 100644 index 0000000..9f18dc9 --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/rest/model/States.scala @@ -0,0 +1,3 @@ +package de.retest.guistatemachine.rest.model + +case class States(states: Map[State]) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/model/Transition.scala b/src/main/scala/de/retest/guistatemachine/rest/model/Transition.scala similarity index 52% rename from src/main/scala/de/retest/guistatemachine/model/Transition.scala rename to src/main/scala/de/retest/guistatemachine/rest/model/Transition.scala index 930e10a..4e3965a 100644 --- a/src/main/scala/de/retest/guistatemachine/model/Transition.scala +++ b/src/main/scala/de/retest/guistatemachine/rest/model/Transition.scala @@ -1,3 +1,3 @@ -package de.retest.guistatemachine.model +package de.retest.guistatemachine.rest.model final case class Transition(to : Id, action : Id) \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/rest/model/Transitions.scala b/src/main/scala/de/retest/guistatemachine/rest/model/Transitions.scala new file mode 100644 index 0000000..54da2ad --- /dev/null +++ b/src/main/scala/de/retest/guistatemachine/rest/model/Transitions.scala @@ -0,0 +1,3 @@ +package de.retest.guistatemachine.rest.model + +final case class Transitions(transitions: Map[Transition] = Map()) \ No newline at end of file diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImplSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImplSpec.scala new file mode 100644 index 0000000..4329219 --- /dev/null +++ b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineApiImplSpec.scala @@ -0,0 +1,19 @@ +package de.retest.guistatemachine.api.impl + +import de.retest.guistatemachine.api.{AbstractApiSpec, GuiStateMachine} + +class GuiStateMachineApiImplSpec extends AbstractApiSpec { + + var stateMachine: GuiStateMachine = null + + "GuiStateMachineApi" should { + "create a new state machine" in { + stateMachine = GuiStateMachineApiImpl.createStateMachine + stateMachine should not be null + } + + "remove a state machine" in { + GuiStateMachineApiImpl.removeStateMachine(stateMachine) shouldBe true + } + } +} \ No newline at end of file diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala index 9367088..ed4fa45 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala @@ -4,6 +4,7 @@ import de.retest.guistatemachine.api.{AbstractApiSpec, Action, Descriptors} class GuiStateMachineImplSpec extends AbstractApiSpec { + val sut = new GuiStateMachineImpl val rootElementA = getRootElement("a") val rootElementB = getRootElement("b") val action0Mock = getAction() @@ -11,22 +12,34 @@ class GuiStateMachineImplSpec extends AbstractApiSpec { "GuiStateMachine" should { "get an initial state" in { - val initial = GuiStateMachineImpl.getState(getDescriptors, getNeverExploredActions) + val initial = sut.getState(getDescriptors, getNeverExploredActions) initial.getNeverExploredActions.size shouldEqual 2 initial.getTransitions.size shouldEqual 0 } - "add a transition to a new state" in { + "add two transitions to two new states for the same action" in { val initialDescriptors = getDescriptors - val initial = GuiStateMachineImpl.getState(getDescriptors, getNeverExploredActions) - val sDescriptors = Descriptors(Set(rootElementA)) + val initial = sut.getState(getDescriptors, getNeverExploredActions) + + val s0Descriptors = Descriptors(Set(rootElementA)) + // It should be a new state and not the same again. + initialDescriptors.equals(s0Descriptors) shouldEqual false + val s0 = sut.executeAction(initial, Action(action0Mock), s0Descriptors, getNeverExploredActions) + initial.getNeverExploredActions.size shouldEqual 1 + initial.getTransitions.size shouldEqual 1 + initial.getTransitions(Action(action0Mock)).size shouldEqual 1 + s0.getNeverExploredActions.size shouldEqual 2 + s0.getTransitions.size shouldEqual 0 + + val s1Descriptors = Descriptors(Set(rootElementB)) // It should be a new state and not the same again. - initialDescriptors.equals(sDescriptors) shouldEqual false - val s = GuiStateMachineImpl.executeAction(initial, Action(action0Mock), sDescriptors, getNeverExploredActions) + initialDescriptors.equals(s1Descriptors) shouldEqual false + val s1 = sut.executeAction(initial, Action(action0Mock), s1Descriptors, getNeverExploredActions) initial.getNeverExploredActions.size shouldEqual 1 initial.getTransitions.size shouldEqual 1 - s.getNeverExploredActions.size shouldEqual 2 - s.getTransitions.size shouldEqual 0 + initial.getTransitions(Action(action0Mock)).size shouldEqual 2 + s1.getNeverExploredActions.size shouldEqual 2 + s1.getTransitions.size shouldEqual 0 } } diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala index 8179c32..4efb196 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala @@ -1,4 +1,5 @@ package de.retest.guistatemachine.api.impl + import de.retest.guistatemachine.api.{AbstractApiSpec, Action, Descriptors} import scala.collection.immutable.HashSet @@ -15,6 +16,7 @@ class StateImplSpec extends AbstractApiSpec { val s0 = new StateImpl(descriptorsA, HashSet(Action(action0Mock))) val s1 = new StateImpl(descriptorsB, HashSet(Action(action1Mock))) s0.equals(s1) shouldEqual false + s0.equals(null) shouldEqual false } "equal" in { @@ -22,5 +24,10 @@ class StateImplSpec extends AbstractApiSpec { val s1 = new StateImpl(descriptorsA, HashSet(Action(action1Mock))) s0.equals(s1) shouldEqual true } + + "be converted into a string" in { + val s0 = new StateImpl(descriptorsA, HashSet(Action(action0Mock))) + s0.toString shouldEqual "descriptors=Descriptors(Set()),neverExploredActions=Set(Selenium Action),transitions=Map()" + } } } diff --git a/src/test/scala/de/retest/guistatemachine/dsl/StateMachinesSpec.scala b/src/test/scala/de/retest/guistatemachine/dsl/StateMachinesSpec.scala deleted file mode 100644 index 142577e..0000000 --- a/src/test/scala/de/retest/guistatemachine/dsl/StateMachinesSpec.scala +++ /dev/null @@ -1,57 +0,0 @@ -package de.retest.guistatemachine.dsl - -import org.scalatest.WordSpec -import org.scalatest.Matchers - -/** - * Tests the construction of state machines with the DSL. - */ -class StateMachinesSpec extends WordSpec with Matchers { - - "StateMachines" should { - "be constructed as NFAs from objects" in { - case object Start0 extends InitialState - case object S0_0 extends State - case object S0_1 extends State - case object End0 extends FinalState - - case object Start1 extends InitialState - case object S1_0 extends State - case object S1_1 extends State - case object End1 extends FinalState - - case object EnterText extends Action - case object PressExitButton extends Action - - val r = StateMachines { - StateMachine { - Start0 - EnterText - S0_0 - Start0 - EnterText - S0_1 - S0_0 - PressExitButton - End0 - S0_1 - PressExitButton - End0 - } ~ - StateMachine { - Start1 - EnterText - S1_0 - Start1 - EnterText - S1_1 - S1_0 - PressExitButton - End1 - S1_1 - PressExitButton - End1 - } - } - - r.size shouldEqual 2 - val stateMachine0 = r(0) - - val initial0 = stateMachine0.getInitial - initial0 should be theSameInstanceAs Start0 - initial0.getTransitions.size shouldEqual 2 - - val initial0Trans0 = initial0.getTransitions(0) - initial0Trans0.getAction should be theSameInstanceAs EnterText - initial0Trans0.getTo should be theSameInstanceAs S0_0 - - val initial0Trans1 = initial0.getTransitions(1) - initial0Trans1.getAction should be theSameInstanceAs EnterText - initial0Trans1.getTo should be theSameInstanceAs S0_1 - } - } -} \ No newline at end of file diff --git a/src/test/scala/de/retest/guistatemachine/persistence/PersistenceSpec.scala b/src/test/scala/de/retest/guistatemachine/persistence/PersistenceSpec.scala deleted file mode 100644 index 27168a8..0000000 --- a/src/test/scala/de/retest/guistatemachine/persistence/PersistenceSpec.scala +++ /dev/null @@ -1,22 +0,0 @@ -package de.retest.guistatemachine.persistence - -import org.scalatest.Matchers -import org.scalatest.WordSpec -import de.retest.guistatemachine.model.Id - -class PersistenceSpec extends WordSpec with Matchers { - val sut = new Persistence - - "The pesistence" should { - "allow adding one test suite" in { - sut.getApplications().apps.values.size shouldEqual 0 - sut.addApplication().id shouldEqual 0 - sut.getApplications().apps.values.size shouldEqual 1 - sut.getTestSuites(Id(0)).get.suites.values.size shouldEqual 0 - sut.addTestSuite(Id(0)).get.id shouldEqual 0 - sut.getTestSuites(Id(0)).get.suites.values.size shouldEqual 1 - val s = sut.getTestSuite(Id(0), Id(0)) - s.isEmpty shouldEqual false - } - } -} \ No newline at end of file diff --git a/src/test/scala/de/retest/guistatemachine/rest/JsonFormatForIdMapSpec.scala b/src/test/scala/de/retest/guistatemachine/rest/JsonFormatForIdMapSpec.scala index f6ac8d8..f2bed4d 100644 --- a/src/test/scala/de/retest/guistatemachine/rest/JsonFormatForIdMapSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/rest/JsonFormatForIdMapSpec.scala @@ -1,65 +1,37 @@ package de.retest.guistatemachine.rest -import org.scalatest.WordSpec -import org.scalatest.Matchers - -import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ +import de.retest.guistatemachine.rest.model.{Action, Actions, Id} +import org.scalatest.{Matchers, WordSpec} import spray.json.DefaultJsonProtocol._ import spray.json._ -import de.retest.guistatemachine.model.TestSuites -import de.retest.guistatemachine.model.Id -import de.retest.guistatemachine.model.TestSuite + import scala.collection.immutable.HashMap -import de.retest.guistatemachine.model.Map -import de.retest.guistatemachine.model.GuiApplications -import de.retest.guistatemachine.model.GuiApplication class JsonFormatForIdMapSpec extends WordSpec with Matchers { - implicit val idFormat = jsonFormat1(Id) - implicit val testSuiteFormat = jsonFormat0(TestSuite) - implicit val hashMapFormatTestSuites = new JsonFormatForIdMap[TestSuite] - implicit val testSuitesFormat = jsonFormat1(TestSuites) - implicit val applicationFormat = jsonFormat1(GuiApplication) - implicit val hashMapFormatApplications = new JsonFormatForIdMap[GuiApplication] - implicit val applicationsFormat = jsonFormat1(GuiApplications) - "The JSON format" should { - "convert an empty test suite into JSON and back" in { - val testSuites = TestSuites(Map(new HashMap[Id, TestSuite]())) - val json = testSuites.toJson - json.toString shouldEqual "{\"suites\":{}}" - val transformedTestSuites = json.convertTo[TestSuites] - transformedTestSuites.suites.values.isEmpty shouldEqual true - } + implicit val actionFormat = jsonFormat0(Action) + implicit val idMapFormatActions = new JsonFormatForIdMap[Action] + implicit val actionsFormat = jsonFormat1(Actions) - "convert a test suite with elements into JSON and back" in { - val testSuites = TestSuites(Map(new HashMap[Id, TestSuite]())) - testSuites.suites.values = testSuites.suites.values + (Id(0) -> TestSuite()) - val json = testSuites.toJson - json.toString shouldEqual "{\"suites\":{\"0\":{}}}" - val transformedTestSuites = json.convertTo[TestSuites] - transformedTestSuites.suites.values.isEmpty shouldEqual false - transformedTestSuites.suites.values.contains(Id(0)) shouldEqual true + "The JSON format" should { + "convert empty actions into JSON and back" in { + val actions = model.Actions(model.Map(new HashMap[Id, Action]())) + val json = actions.toJson + json.toString shouldEqual "{\"actions\":{}}" + val transformedActions = json.convertTo[Actions] + transformedActions.actions.values.isEmpty shouldEqual true } - /** - * Tests nested fields which do both contain a Map. - */ - "convert an application into JSON and back" in { - val testSuites = TestSuites(Map(new HashMap[Id, TestSuite]())) - testSuites.suites.values = testSuites.suites.values + (Id(0) -> TestSuite()) - val apps = GuiApplications(Map(new HashMap[Id, GuiApplication]())) - apps.apps.values = apps.apps.values + (Id(0) -> new GuiApplication(testSuites)) - val json = apps.toJson - json.toString shouldEqual "{\"apps\":{\"0\":{\"testSuites\":{\"suites\":{\"0\":{}}}}}}" - val transformedApps = json.convertTo[GuiApplications] - transformedApps.apps.values.isEmpty shouldEqual false - transformedApps.apps.values.contains(Id(0)) shouldEqual true - val transformedSuites = transformedApps.apps.values(Id(0)).testSuites.suites.values - transformedSuites.isEmpty shouldEqual false - transformedSuites.contains(Id(0)) shouldEqual true + "convert actions with elements into JSON and back" in { + val actions = model.Actions(model.Map(new HashMap[Id, Action]())) + actions.actions.values = actions.actions.values + (Id(0) -> Action()) + val json = actions.toJson + json.toString shouldEqual "{\"actions\":{\"0\":{}}}" + val transformedActions = json.convertTo[Actions] + transformedActions.actions.values.isEmpty shouldEqual false + transformedActions.actions.values.contains(Id(0)) shouldEqual true } } -} \ No newline at end of file +} diff --git a/src/test/scala/de/retest/guistatemachine/rest/RestServiceSpec.scala b/src/test/scala/de/retest/guistatemachine/rest/RestServiceSpec.scala index fcce136..6a585de 100644 --- a/src/test/scala/de/retest/guistatemachine/rest/RestServiceSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/rest/RestServiceSpec.scala @@ -1,23 +1,10 @@ package de.retest.guistatemachine.rest -import org.scalatest.{ Matchers, WordSpec } -import akka.http.scaladsl.model.StatusCodes -import akka.http.scaladsl.testkit.ScalatestRouteTest -import akka.http.scaladsl.server._ -import Directives._ -import akka.http.scaladsl.model.HttpEntity -import akka.http.scaladsl.model.ContentTypes -import akka.http.scaladsl.model.HttpCharset import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ -import de.retest.guistatemachine.model.GuiApplications -import de.retest.guistatemachine.model.Id -import de.retest.guistatemachine.model.GuiApplication -import de.retest.guistatemachine.model.TestSuites +import akka.http.scaladsl.model.{MediaTypes, StatusCodes} +import akka.http.scaladsl.testkit.ScalatestRouteTest import de.retest.guistatemachine.persistence.Persistence -import akka.http.scaladsl.model.MediaType -import akka.http.scaladsl.model.MediaTypes -import de.retest.guistatemachine.model.TestSuite -import akka.http.scaladsl.model.StatusCode +import org.scalatest.{Matchers, WordSpec} class RestServiceSpec extends WordSpec with Matchers with ScalatestRouteTest with RestService { @@ -33,113 +20,61 @@ class RestServiceSpec extends WordSpec with Matchers with ScalatestRouteTest wit } } - "return an empty list for the GET request with the path /applications" in { - Get("/applications") ~> sut ~> check { + "return an empty list for the GET request with the path /state-machines" in { + Get("/state-machines") ~> sut ~> check { + import de.retest.guistatemachine.rest.model.StateMachines handled shouldEqual true mediaType shouldEqual MediaTypes.`application/json` - val r = responseAs[GuiApplications] - r.apps.values.size shouldEqual 0 - } - } - - "fail for the GET request with the path /application/0" in { - Get("/application/0") ~> sut ~> check { - handled shouldEqual true - status shouldEqual StatusCodes.NotFound - } - } - - "fail for the DELETE request with the path /application/0" in { - Delete("/application/0") ~> sut ~> check { - handled shouldEqual true - status shouldEqual StatusCodes.NotFound - } - } - - "fail for the GET request with the path /application/0/test-suites" in { - Get("/application/0/test-suites") ~> sut ~> check { - handled shouldEqual true - status shouldEqual StatusCodes.NotFound - } - } - - "allow POST for path /create-application" in { - Post("/create-application") ~> sut ~> check { - handled shouldEqual true - responseAs[Id] shouldEqual Id(0) - persistence.getApplications.apps.values.size shouldEqual 1 - } - } - - "return an empty application for the GET request with the path /application/0" in { - Get("/application/0") ~> sut ~> check { - handled shouldEqual true - status shouldEqual StatusCodes.OK - val r = responseAs[GuiApplication] - r.testSuites.suites.values.size shouldEqual 0 + val r = responseAs[StateMachines] + r.stateMachines.values.size shouldEqual 0 } } - "return an empty list for the GET request with the path /application/0/test-suites" in { - Get("/application/0/test-suites") ~> sut ~> check { - handled shouldEqual true - status shouldEqual StatusCodes.OK - val r = responseAs[TestSuites] - r.suites.values.size shouldEqual 0 - } - } - - "fail for the GET request with the path /application/0/test-suite/0" in { - Get("/application/0/test-suite/0") ~> sut ~> check { + "fail for the GET request with the path /state-machine/0" in { + Get("/state-machine/0") ~> sut ~> check { handled shouldEqual true status shouldEqual StatusCodes.NotFound } } - "fail for the DELETE request with the path /application/0/test-suite/0" in { - Delete("/application/0/test-suite/0") ~> sut ~> check { + "fail for the DELETE request with the path /state-machine/0" in { + Delete("/state-machine/0") ~> sut ~> check { handled shouldEqual true status shouldEqual StatusCodes.NotFound } } - "allow POST for path /application/0/create-test-suite" in { - Post("/application/0/create-test-suite") ~> sut ~> check { + "allow POST for path /create-state-machine" in { + Post("/create-state-machine") ~> sut ~> check { + import de.retest.guistatemachine.rest.model.Id handled shouldEqual true responseAs[Id] shouldEqual Id(0) - persistence.getTestSuites(Id(0)).get.suites.values.size shouldEqual 1 + persistence.getStateMachines().stateMachines.values.size shouldEqual 1 } } - "return an empty test suite for the GET request with the path /application/0/test-suite/0" in { - Get("/application/0/test-suite/0") ~> sut ~> check { + "return an empty application for the GET request with the path /state-machine/0" in { + Get("/state-machine/0") ~> sut ~> check { + import de.retest.guistatemachine.rest.model.StateMachine handled shouldEqual true status shouldEqual StatusCodes.OK - val r = responseAs[TestSuite] - // TODO There is no content in a test suite at the moment - } - } - - "return status OK for the DELETE request with the path /application/0/test-suite/0" in { - Delete("/application/0/test-suite/0") ~> sut ~> check { - handled shouldEqual true - status shouldEqual StatusCodes.OK - responseAs[String] shouldEqual "OK" - persistence.getTestSuites(Id(0)).get.suites.values.size shouldEqual 0 + val r = responseAs[StateMachine] + r.states.states.values.size shouldEqual 1 + r.actions.actions.values.size shouldEqual 0 } } - "return status OK for the DELETE request with the path /application/0" in { - Delete("/application/0") ~> sut ~> check { + "return status OK for the DELETE request with the path /state-machine/0" in { + Delete("/state-machine/0") ~> sut ~> check { handled shouldEqual true status shouldEqual StatusCodes.OK responseAs[String] shouldEqual "OK" - persistence.getApplications().apps.values.size shouldEqual 0 + persistence.getStateMachines().stateMachines.values.size shouldEqual 0 } } - "not handle the GET request with the path /applications/bla/hello/bla" in { - Get("/applications/bla/hello/bla") ~> sut ~> check { + "not handle the GET request with the path /state-machines/bla/hello/bla" in { + Get("/state-machines/bla/hello/bla") ~> sut ~> check { handled shouldEqual false //mediaType shouldEqual MediaTypes.`application/json` //val r = responseAs[GuiApplications] diff --git a/src/test/scala/de/retest/guistatemachine/rest/WebServerSpec.scala b/src/test/scala/de/retest/guistatemachine/rest/WebServerSpec.scala index 34b5af5..77c9f26 100644 --- a/src/test/scala/de/retest/guistatemachine/rest/WebServerSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/rest/WebServerSpec.scala @@ -1,13 +1,14 @@ package de.retest.guistatemachine.rest -import org.scalatest.Matchers -import org.scalatest.WordSpec +import org.scalatest.{Matchers, WordSpec} class WebServerSpec extends WordSpec with Matchers { "The web server" should { "start and end successfully" in { + WebServer.Host shouldEqual "localhost" + WebServer.Port shouldEqual 8888 WebServer.main(Array("--maxtime=1000")) } } -} \ No newline at end of file +} diff --git a/src/test/scala/de/retest/guistatemachine/model/IdSpec.scala b/src/test/scala/de/retest/guistatemachine/rest/model/IdSpec.scala similarity index 71% rename from src/test/scala/de/retest/guistatemachine/model/IdSpec.scala rename to src/test/scala/de/retest/guistatemachine/rest/model/IdSpec.scala index 8b7d110..a0bc895 100644 --- a/src/test/scala/de/retest/guistatemachine/model/IdSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/rest/model/IdSpec.scala @@ -1,7 +1,6 @@ -package de.retest.guistatemachine.model +package de.retest.guistatemachine.rest.model -import org.scalatest.WordSpec -import org.scalatest.Matchers +import org.scalatest.{Matchers, WordSpec} class IdSpec extends WordSpec with Matchers { @@ -14,4 +13,4 @@ class IdSpec extends WordSpec with Matchers { id1.compare(id0) shouldEqual 1 } } -} \ No newline at end of file +} diff --git a/src/test/scala/de/retest/guistatemachine/model/MapSpec.scala b/src/test/scala/de/retest/guistatemachine/rest/model/MapSpec.scala similarity index 78% rename from src/test/scala/de/retest/guistatemachine/model/MapSpec.scala rename to src/test/scala/de/retest/guistatemachine/rest/model/MapSpec.scala index 2ba7cbb..6d990eb 100644 --- a/src/test/scala/de/retest/guistatemachine/model/MapSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/rest/model/MapSpec.scala @@ -1,7 +1,7 @@ -package de.retest.guistatemachine.model +package de.retest.guistatemachine.rest.model + +import org.scalatest.{Matchers, WordSpec} -import org.scalatest.WordSpec -import org.scalatest.Matchers import scala.collection.immutable.HashMap class MapSpec extends WordSpec with Matchers { @@ -17,4 +17,4 @@ class MapSpec extends WordSpec with Matchers { id1.id shouldEqual 1 } } -} \ No newline at end of file +}