Skip to content
This repository has been archived by the owner on Mar 12, 2020. It is now read-only.

Commit

Permalink
Adapt REST API to Scala API #1 #2
Browse files Browse the repository at this point in the history
- Don't use any custom model package types but the types from the api package
for REST.
- GuiStateMachineApi works now with IDs for the state machines.
This allows the removal and retrieval of state machines based on the unique ID.
- Remove persistence layer since the Scala API provides persistence calls.
- Add bodys for the parameters for `get-state` and `execute-action`.
- Only use one service class GuiStateMachineService.
- Swagger uses the correct service class now (not RestService).
- Add initial JsonFormat types for all the required Scala API types.
- Remove model package and Bash scripts for the old REST API.
- Simplify documentation.
- Adapt unit tests.
  • Loading branch information
tdauth committed Nov 15, 2018
1 parent 04cd7a8 commit 63dc948
Show file tree
Hide file tree
Showing 44 changed files with 242 additions and 381 deletions.
42 changes: 9 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,42 +32,18 @@ The NFA is used to generate test cases (sequence of UI actions) with the help of
For example, whenever a random action is executed with the help of monkey testing, it adds a transition to the state machine.
After running the genetic algorithm, the state machine is then used to create a test suite.

**At the moment, the following definitions are incomplete and must be adapted to the actual implementation which calls this service.**

### Test Suite
A set of test cases.

### Test Case
A sequence of UI actions.

### UI Action
An action which can be triggered by the user via the GUI.

### UI Path
A sequence of states with transitions from one state to another.
Each transition is a UI action.

### State
A state is defined by the set of all visible and interactable windows together with their enabled widgets.

## 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.

## REST API
At the moment there is only an initial version of a REST API which has to be mapped to the Scala API.
Some suggestions how the REST API for the state machine could look like:
* `/state-machines` GET queries all existing state machines.
* `/create-state-machine` POST creates a new state machine.
* `/state-machine/<long>` GET queries an existing state machine.
* `/state-machine/<long>/states` GET queries all existing states of the state machine.
* `/state-machine/<long>/state/<long>` GET queries a specific state of the state machine which contains transitions.
* `/state-machine/<long>/state/<long>/transitions` GET queries all transitions of a specific state.
* `/state-machine/<long>/state/<long>/transition/<long>` GET queries a specific transition of a specific state.
* `/state-machine/<long>/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?

## Swagger Support
The REST service can be started with `sbt run`.
It has the address `http://localhost:8888/`.
The REST API can be tested manually with `curl`:
```bash
curl -H "Content-Type: application/json" -X POST http://localhost:8888/state-machine
```

### Swagger Support
The Swagger support is based on [swagger-akka-http](https://github.com/swagger-akka-http/swagger-akka-http).
The URL `http://localhost:8888/api-docs/swagger.json` should show create Swagger JSON output which can be rendered by Swagger UI.

### Bash Scripts for REST Calls
The directory [scripts](./scripts) contains a number of Bash scripts which use `curl` to send REST calls to a running server.
The URL `http://localhost:8888/api-docs/swagger.json` should show create Swagger JSON output which can be rendered by Swagger UI.
2 changes: 0 additions & 2 deletions scripts/application.sh

This file was deleted.

2 changes: 0 additions & 2 deletions scripts/applications.sh

This file was deleted.

2 changes: 0 additions & 2 deletions scripts/create-application.sh

This file was deleted.

2 changes: 0 additions & 2 deletions scripts/create-test-suite.sh

This file was deleted.

2 changes: 0 additions & 2 deletions scripts/delete-application.sh

This file was deleted.

2 changes: 0 additions & 2 deletions scripts/delete-test-suite.sh

This file was deleted.

52 changes: 0 additions & 52 deletions scripts/runcompleterestservicetest.sh

This file was deleted.

2 changes: 0 additions & 2 deletions scripts/test-suite.sh

This file was deleted.

2 changes: 0 additions & 2 deletions scripts/test-suites.sh

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,23 @@ trait GuiStateMachineApi {
*
* @return The new GUI state machine.
*/
def createStateMachine: GuiStateMachine
def createStateMachine(): Id

/**
* Removes an existing [[GuiStateMachine]].
*
* @param stateMachine The persisted GUI state machine.
* @param id The ID of the GUI state machine.
* @return True if it existed and was removed by this call. Otherwise, false.
*/
def removeStateMachine(stateMachine: GuiStateMachine): Boolean
def removeStateMachine(id: Id): Boolean

/**
* Gets an existing [[GuiStateMachine]].
*
* @param id The ID of the GUI state machine.
* @return The existing GUI state machine or nothing.
*/
def getStateMachine(id: Id): Option[GuiStateMachine]

/**
* Stores all state machines on the disk.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.retest.guistatemachine.rest.model
package de.retest.guistatemachine.api

final case class Id(val id: Long) extends Ordered[Id] {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
package de.retest.guistatemachine.api.impl

import de.retest.guistatemachine.api.GuiStateMachineApi
import de.retest.guistatemachine.api.GuiStateMachine
import de.retest.guistatemachine.api.{GuiStateMachine, GuiStateMachineApi, Id}

import scala.collection.mutable.HashSet
import scala.collection.immutable.HashMap

object GuiStateMachineApiImpl extends GuiStateMachineApi {
val stateMachines = new HashSet[GuiStateMachine]
val stateMachines = IdMap(new HashMap[Id, GuiStateMachine])

override def createStateMachine: GuiStateMachine = {
val r = new GuiStateMachineImpl
stateMachines += r
r
}
override def createStateMachine(): Id = stateMachines.addNewElement(new GuiStateMachineImpl)

override def removeStateMachine(id: Id): Boolean = stateMachines.removeElement(id)

override def removeStateMachine(stateMachine: GuiStateMachine): Boolean = stateMachines.remove(stateMachine)
override def getStateMachine(id: Id): Option[GuiStateMachine] = stateMachines.getElement(id)

override def persist(): Unit = {
// TODO #9 store on the disk
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package de.retest.guistatemachine.api.impl

import de.retest.guistatemachine.api.{Action, Descriptors, GuiStateMachine, State}
import scala.collection.immutable.{HashMap, HashSet}

class GuiStateMachineImpl extends GuiStateMachine {
var states = new HashMap[Descriptors, State]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package de.retest.guistatemachine.rest.model
package de.retest.guistatemachine.api.impl

import de.retest.guistatemachine.api.Id

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]) {
case class IdMap[T](var values: scala.collection.immutable.Map[Id, T] = new HashMap[Id, T]) {

/**
* Generates a new ID based on the existing entries.
Expand All @@ -28,14 +29,14 @@ case class Map[T](var values: scala.collection.immutable.Map[Id, T] = new HashMa
false
}

def getElement(id: Id): T = values(id)
def getElement(id: Id): Option[T] = values.get(id)

def hasElement(id: Id): Boolean = values.contains(id)
}

object Map {
def fromValues[T](v: T*): Map[T] = {
val r = Map[T]()
object IdMap {
def fromValues[T](v: T*): IdMap[T] = {
val r = IdMap[T]()
for (e <- v) r.addNewElement(e)
r
}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.retest.guistatemachine.rest

import de.retest.guistatemachine.api.{Action, Descriptors, State}

case class ExecuteActionBody(from: State, a: Action, descriptors: Descriptors, neverExploredActions: Set[Action])
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.retest.guistatemachine.rest

import de.retest.guistatemachine.api.{Action, Descriptors}

case class GetStateBody(descriptors: Descriptors, neverExploredActions: Set[Action])
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package de.retest.guistatemachine.rest

import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.model.{StatusCode, StatusCodes}
import akka.http.scaladsl.server.{Directives, Route}
import de.retest.guistatemachine.api.{GuiStateMachine, GuiStateMachineApi, Id, State}
import de.retest.guistatemachine.rest.json.DefaultJsonFormats
import io.swagger.annotations.{Api, ApiOperation, ApiResponse, ApiResponses}
import javax.ws.rs.Path

@Api(value = "/state-machine", description = "Gets a state machine")
@Path("/state-machine")
class GuiStateMachineService(api: GuiStateMachineApi) extends Directives with DefaultJsonFormats {

def getRoute(): Route = getStateMachine() ~ deleteStateMachine() ~ postStateMachine() ~ getState()

@ApiOperation(httpMethod = "GET", response = classOf[GuiStateMachine], value = "Returns a state machine based on the ID")
@ApiResponses(Array(new ApiResponse(code = 404, message = "State machine not found")))
def getStateMachine(): Route = get {
path("state-machine" / LongNumber) { id =>
val r = api.getStateMachine(Id(id))
r match {
case Some(x) => complete(x)
case None => complete(StatusCodes.NotFound)
}
}
}

@ApiOperation(httpMethod = "DELETE", response = classOf[StatusCode], value = "Returns the status code")
@ApiResponses(
Array(
new ApiResponse(code = 200, message = "Successful deletion"),
new ApiResponse(code = 404, message = "State machine not found")
))
def deleteStateMachine(): Route = delete {
path("state-machine" / LongNumber) { stateMachineId =>
import de.retest.guistatemachine.api.Id
val r = api.removeStateMachine(Id(stateMachineId))
complete(if (r) StatusCodes.OK else StatusCodes.NotFound)
}
}

@ApiOperation(httpMethod = "POST", response = classOf[Id], value = "Returns the ID")
@ApiResponses(Array(new ApiResponse(code = 200, message = "Successful creation")))
def postStateMachine(): Route = post {
path("state-machine") {
val id = api.createStateMachine()
complete(id)
}
}

@ApiOperation(httpMethod = "POST", response = classOf[State], value = "Returns the existing or newly created state")
@ApiResponses(Array(new ApiResponse(code = 404, message = "State machine not found")))
def getState(): Route = post {
path("state-machine" / LongNumber / "get-state") { id =>
val app = api.getStateMachine(Id(id))
app match {
case Some(x) => {
entity(as[GetStateBody]) { body =>
complete(x.getState(body.descriptors, body.neverExploredActions))
}
}
case None => complete(StatusCodes.NotFound)
}
}
}

@ApiOperation(httpMethod = "POST", response = classOf[State], value = "Returns the state which is reached by executing this action")
@ApiResponses(Array(new ApiResponse(code = 404, message = "State machine not found")))
def executeAction(): Route = post {
path("state-machine" / LongNumber / "execute-action") { id =>
val app = api.getStateMachine(Id(id))
app match {
case Some(x) => {
entity(as[ExecuteActionBody]) { body =>
complete(x.executeAction(body.from, body.a, body.descriptors, body.neverExploredActions))
}
}
case None => complete(StatusCodes.NotFound)
}
}
}
}
Loading

0 comments on commit 63dc948

Please sign in to comment.