revision of simulator package

- move amqp endpoint implementation to amqp package
- improve code coverage of simulator testing
- remove some unnecessary code from package implementation
This commit is contained in:
Sonja Happ 2019-09-11 16:16:00 +02:00
parent 75c33c71a1
commit c0b8a6be80
7 changed files with 126 additions and 95 deletions

61
amqp/amqp_endpoints.go Normal file
View file

@ -0,0 +1,61 @@
package amqp
import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/database"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/helper"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func RegisterAMQPEndpoint(r *gin.RouterGroup) {
r.POST("/:simulatorID/action", sendActionToSimulator)
}
// sendActionToSimulator godoc
// @Summary Send an action to simulator (only available if backend server is started with -amqp parameter)
// @ID sendActionToSimulator
// @Tags AMQP
// @Produce json
// @Param inputAction query string true "Action for simulator"
// @Success 200 {object} docs.ResponseError "Action sent successfully"
// @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param simulatorID path int true "Simulator ID"
// @Router /simulators/{simulatorID}/action [post]
func sendActionToSimulator(c *gin.Context) {
ok, s := simulator.CheckPermissions(c, database.ModelSimulatorAction, database.Update, true)
if !ok {
return
}
var actions []Action
err := c.BindJSON(&actions)
if err != nil {
helper.BadRequestError(c, "Error binding form data to JSON: "+err.Error())
return
}
now := time.Now()
for _, action := range actions {
if action.When == 0 {
action.When = float32(now.Unix())
}
err = SendActionAMQP(action, s.UUID)
if err != nil {
helper.InternalServerError(c, "Unable to send actions to simulator: "+err.Error())
return
}
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "OK.",
})
}

View file

@ -1,12 +1,9 @@
package simulator
import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/amqp"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/helper"
"net/http"
"time"
"github.com/gin-gonic/gin"
"net/http"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/database"
)
@ -18,10 +15,6 @@ func RegisterSimulatorEndpoints(r *gin.RouterGroup) {
r.GET("/:simulatorID", getSimulator)
r.DELETE("/:simulatorID", deleteSimulator)
r.GET("/:simulatorID/models", getModelsOfSimulator)
// register action endpoint only if AMQP client is used
if database.WITH_AMQP == true {
r.POST("/:simulatorID/action", sendActionToSimulator)
}
}
// getSimulators godoc
@ -36,19 +29,15 @@ func RegisterSimulatorEndpoints(r *gin.RouterGroup) {
// @Router /simulators [get]
func getSimulators(c *gin.Context) {
ok, _ := checkPermissions(c, database.ModelSimulator, database.Read, false)
if !ok {
return
}
// Checking permission is not required here since READ access is independent of user's role
db := database.GetDB()
var simulators []database.Simulator
err := db.Order("ID asc").Find(&simulators).Error
if helper.DBError(c, err) {
return
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"simulators": simulators})
}
c.JSON(http.StatusOK, gin.H{"simulators": simulators})
}
// addSimulator godoc
@ -66,7 +55,7 @@ func getSimulators(c *gin.Context) {
// @Router /simulators [post]
func addSimulator(c *gin.Context) {
ok, _ := checkPermissions(c, database.ModelSimulator, database.Create, false)
ok, _ := CheckPermissions(c, database.ModelSimulator, database.Create, false)
if !ok {
return
}
@ -89,12 +78,10 @@ func addSimulator(c *gin.Context) {
// Save new simulator to DB
err = newSimulator.save()
if err != nil {
helper.DBError(c, err)
return
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"simulator": newSimulator.Simulator})
}
c.JSON(http.StatusOK, gin.H{"simulator": newSimulator.Simulator})
}
// updateSimulator godoc
@ -113,7 +100,7 @@ func addSimulator(c *gin.Context) {
// @Router /simulators/{simulatorID} [put]
func updateSimulator(c *gin.Context) {
ok, oldSimulator := checkPermissions(c, database.ModelSimulator, database.Update, true)
ok, oldSimulator := CheckPermissions(c, database.ModelSimulator, database.Update, true)
if !ok {
return
}
@ -132,21 +119,14 @@ func updateSimulator(c *gin.Context) {
}
// Create the updatedSimulator from oldSimulator
updatedSimulator, err := req.updatedSimulator(oldSimulator)
if err != nil {
helper.BadRequestError(c, err.Error())
return
}
updatedSimulator := req.updatedSimulator(oldSimulator)
// Finally update the simulator in the DB
err = oldSimulator.update(updatedSimulator)
if err != nil {
helper.DBError(c, err)
return
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"simulator": updatedSimulator.Simulator})
}
c.JSON(http.StatusOK, gin.H{"simulator": updatedSimulator.Simulator})
}
// getSimulator godoc
@ -163,7 +143,7 @@ func updateSimulator(c *gin.Context) {
// @Router /simulators/{simulatorID} [get]
func getSimulator(c *gin.Context) {
ok, s := checkPermissions(c, database.ModelSimulator, database.Read, true)
ok, s := CheckPermissions(c, database.ModelSimulator, database.Read, true)
if !ok {
return
}
@ -185,18 +165,17 @@ func getSimulator(c *gin.Context) {
// @Router /simulators/{simulatorID} [delete]
func deleteSimulator(c *gin.Context) {
ok, s := checkPermissions(c, database.ModelSimulator, database.Delete, true)
ok, s := CheckPermissions(c, database.ModelSimulator, database.Delete, true)
if !ok {
return
}
// Delete the simulator
err := s.delete()
if helper.DBError(c, err) {
return
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"simulator": s.Simulator})
}
c.JSON(http.StatusOK, gin.H{"simulator": s.Simulator})
}
// getModelsOfSimulator godoc
@ -213,63 +192,15 @@ func deleteSimulator(c *gin.Context) {
// @Router /simulators/{simulatorID}/models [get]
func getModelsOfSimulator(c *gin.Context) {
ok, s := checkPermissions(c, database.ModelSimulator, database.Read, true)
ok, s := CheckPermissions(c, database.ModelSimulator, database.Read, true)
if !ok {
return
}
// get all associated simulation models
allModels, _, err := s.getModels()
if helper.DBError(c, err) {
return
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"models": allModels})
}
c.JSON(http.StatusOK, gin.H{"models": allModels})
}
// sendActionToSimulator godoc
// @Summary Send an action to simulator (only available if backend server is started with -amqp parameter)
// @ID sendActionToSimulator
// @Tags simulators
// @Produce json
// @Param inputAction query string true "Action for simulator"
// @Success 200 {object} docs.ResponseError "Action sent successfully"
// @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param simulatorID path int true "Simulator ID"
// @Router /simulators/{simulatorID}/action [post]
func sendActionToSimulator(c *gin.Context) {
ok, s := checkPermissions(c, database.ModelSimulatorAction, database.Update, true)
if !ok {
return
}
var actions []amqp.Action
err := c.BindJSON(&actions)
if err != nil {
helper.BadRequestError(c, "Error binding form data to JSON: "+err.Error())
return
}
now := time.Now()
for _, action := range actions {
if action.When == 0 {
action.When = float32(now.Unix())
}
err = amqp.SendActionAMQP(action, s.UUID)
if err != nil {
helper.InternalServerError(c, "Unable to send actions to simulator: "+err.Error())
return
}
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "OK.",
})
}

View file

@ -19,10 +19,7 @@ func (s *Simulator) save() error {
func (s *Simulator) ByID(id uint) error {
db := database.GetDB()
err := db.Find(s, id).Error
if err != nil {
return fmt.Errorf("Simulator with id=%v does not exist", id)
}
return nil
return err
}
func (s *Simulator) update(updatedSimulator Simulator) error {

View file

@ -7,7 +7,7 @@ import (
"github.com/gin-gonic/gin"
)
func checkPermissions(c *gin.Context, modeltype database.ModelName, operation database.CRUD, hasID bool) (bool, Simulator) {
func CheckPermissions(c *gin.Context, modeltype database.ModelName, operation database.CRUD, hasID bool) (bool, Simulator) {
var s Simulator

View file

@ -51,6 +51,23 @@ func TestAddSimulatorAsAdmin(t *testing.T) {
"/api/authenticate", "POST", helper.AdminCredentials)
assert.NoError(t, err)
// try to POST with non JSON body
// should result in bad request
code, resp, err := helper.TestEndpoint(router, token,
"/api/simulators", "POST", "This is no JSON")
assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
// try to POST malformed simulator (required fields missing, validation should fail)
// should result in an unprocessable entity
newMalformedSimulator := SimulatorRequest{
UUID: database.SimulatorB.UUID,
}
code, resp, err = helper.TestEndpoint(router, token,
"/api/simulators", "POST", helper.KeyModels{"simulator": newMalformedSimulator})
assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
// test POST simulators/ $newSimulator
newSimulator := SimulatorRequest{
UUID: database.SimulatorA.UUID,
@ -59,7 +76,7 @@ func TestAddSimulatorAsAdmin(t *testing.T) {
State: database.SimulatorA.State,
Properties: database.SimulatorA.Properties,
}
code, resp, err := helper.TestEndpoint(router, token,
code, resp, err = helper.TestEndpoint(router, token,
"/api/simulators", "POST", helper.KeyModels{"simulator": newSimulator})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
@ -82,6 +99,13 @@ func TestAddSimulatorAsAdmin(t *testing.T) {
// Compare GET's response with the newSimulator
err = helper.CompareResponse(resp, helper.KeyModels{"simulator": newSimulator})
assert.NoError(t, err)
// Try to GET a simulator that does not exist
// should result in not found
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/simulators/%v", newSimulatorID+1), "GET", nil)
assert.NoError(t, err)
assert.Equalf(t, 404, code, "Response body: \n%v\n", resp)
}
func TestAddSimulatorAsUser(t *testing.T) {
@ -142,6 +166,13 @@ func TestUpdateSimulatorAsAdmin(t *testing.T) {
newSimulatorID, err := helper.GetResponseID(resp)
assert.NoError(t, err)
// try to PUT with non JSON body
// should result in bad request
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/simulators/%v", newSimulatorID), "PUT", "This is no JSON")
assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
// Test PUT simulators
newSimulator.Host = "ThisIsMyNewHost"
code, resp, err = helper.TestEndpoint(router, token,
@ -409,4 +440,11 @@ func TestGetSimulationModelsOfSimulator(t *testing.T) {
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
assert.Equal(t, 0, numberOfModels)
// Try to get models of simulator that does not exist
// should result in not found
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/simulators/%v/models", newSimulatorID+1), "GET", nil)
assert.NoError(t, err)
assert.Equalf(t, 404, code, "Response body: \n%v\n", resp)
}

View file

@ -58,7 +58,7 @@ func (r *addSimulatorRequest) createSimulator() Simulator {
return s
}
func (r *updateSimulatorRequest) updatedSimulator(oldSimulator Simulator) (Simulator, error) {
func (r *updateSimulatorRequest) updatedSimulator(oldSimulator Simulator) Simulator {
// Use the old Simulator as a basis for the updated Simulator `s`
s := oldSimulator
@ -89,5 +89,5 @@ func (r *updateSimulatorRequest) updatedSimulator(oldSimulator Simulator) (Simul
s.Properties = r.Properties
}
return s, nil
return s
}

View file

@ -64,6 +64,10 @@ func main() {
file.RegisterFileEndpoints(api.Group("/files"))
user.RegisterUserEndpoints(api.Group("/users"))
simulator.RegisterSimulatorEndpoints(api.Group("/simulators"))
// register simulator action endpoint only if AMQP client is used
if database.WITH_AMQP == true {
amqp.RegisterAMQPEndpoint(api.Group("/simulators"))
}
r.GET("swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))