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 package simulator
import ( import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/amqp"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/helper" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/helper"
"net/http"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/database" "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.GET("/:simulatorID", getSimulator)
r.DELETE("/:simulatorID", deleteSimulator) r.DELETE("/:simulatorID", deleteSimulator)
r.GET("/:simulatorID/models", getModelsOfSimulator) 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 // getSimulators godoc
@ -36,19 +29,15 @@ func RegisterSimulatorEndpoints(r *gin.RouterGroup) {
// @Router /simulators [get] // @Router /simulators [get]
func getSimulators(c *gin.Context) { func getSimulators(c *gin.Context) {
ok, _ := checkPermissions(c, database.ModelSimulator, database.Read, false) // Checking permission is not required here since READ access is independent of user's role
if !ok {
return
}
db := database.GetDB() db := database.GetDB()
var simulators []database.Simulator var simulators []database.Simulator
err := db.Order("ID asc").Find(&simulators).Error err := db.Order("ID asc").Find(&simulators).Error
if helper.DBError(c, err) { if !helper.DBError(c, err) {
return c.JSON(http.StatusOK, gin.H{"simulators": simulators})
} }
c.JSON(http.StatusOK, gin.H{"simulators": simulators})
} }
// addSimulator godoc // addSimulator godoc
@ -66,7 +55,7 @@ func getSimulators(c *gin.Context) {
// @Router /simulators [post] // @Router /simulators [post]
func addSimulator(c *gin.Context) { func addSimulator(c *gin.Context) {
ok, _ := checkPermissions(c, database.ModelSimulator, database.Create, false) ok, _ := CheckPermissions(c, database.ModelSimulator, database.Create, false)
if !ok { if !ok {
return return
} }
@ -89,12 +78,10 @@ func addSimulator(c *gin.Context) {
// Save new simulator to DB // Save new simulator to DB
err = newSimulator.save() err = newSimulator.save()
if err != nil { if !helper.DBError(c, err) {
helper.DBError(c, err) c.JSON(http.StatusOK, gin.H{"simulator": newSimulator.Simulator})
return
} }
c.JSON(http.StatusOK, gin.H{"simulator": newSimulator.Simulator})
} }
// updateSimulator godoc // updateSimulator godoc
@ -113,7 +100,7 @@ func addSimulator(c *gin.Context) {
// @Router /simulators/{simulatorID} [put] // @Router /simulators/{simulatorID} [put]
func updateSimulator(c *gin.Context) { 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 { if !ok {
return return
} }
@ -132,21 +119,14 @@ func updateSimulator(c *gin.Context) {
} }
// Create the updatedSimulator from oldSimulator // Create the updatedSimulator from oldSimulator
updatedSimulator, err := req.updatedSimulator(oldSimulator) updatedSimulator := req.updatedSimulator(oldSimulator)
if err != nil {
helper.BadRequestError(c, err.Error())
return
}
// Finally update the simulator in the DB // Finally update the simulator in the DB
err = oldSimulator.update(updatedSimulator) err = oldSimulator.update(updatedSimulator)
if err != nil { if !helper.DBError(c, err) {
helper.DBError(c, err) c.JSON(http.StatusOK, gin.H{"simulator": updatedSimulator.Simulator})
return
} }
c.JSON(http.StatusOK, gin.H{"simulator": updatedSimulator.Simulator})
} }
// getSimulator godoc // getSimulator godoc
@ -163,7 +143,7 @@ func updateSimulator(c *gin.Context) {
// @Router /simulators/{simulatorID} [get] // @Router /simulators/{simulatorID} [get]
func getSimulator(c *gin.Context) { 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 { if !ok {
return return
} }
@ -185,18 +165,17 @@ func getSimulator(c *gin.Context) {
// @Router /simulators/{simulatorID} [delete] // @Router /simulators/{simulatorID} [delete]
func deleteSimulator(c *gin.Context) { 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 { if !ok {
return return
} }
// Delete the simulator // Delete the simulator
err := s.delete() err := s.delete()
if helper.DBError(c, err) { if !helper.DBError(c, err) {
return c.JSON(http.StatusOK, gin.H{"simulator": s.Simulator})
} }
c.JSON(http.StatusOK, gin.H{"simulator": s.Simulator})
} }
// getModelsOfSimulator godoc // getModelsOfSimulator godoc
@ -213,63 +192,15 @@ func deleteSimulator(c *gin.Context) {
// @Router /simulators/{simulatorID}/models [get] // @Router /simulators/{simulatorID}/models [get]
func getModelsOfSimulator(c *gin.Context) { 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 { if !ok {
return return
} }
// get all associated simulation models // get all associated simulation models
allModels, _, err := s.getModels() allModels, _, err := s.getModels()
if helper.DBError(c, err) { if !helper.DBError(c, err) {
return 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 { func (s *Simulator) ByID(id uint) error {
db := database.GetDB() db := database.GetDB()
err := db.Find(s, id).Error err := db.Find(s, id).Error
if err != nil { return err
return fmt.Errorf("Simulator with id=%v does not exist", id)
}
return nil
} }
func (s *Simulator) update(updatedSimulator Simulator) error { func (s *Simulator) update(updatedSimulator Simulator) error {

View file

@ -7,7 +7,7 @@ import (
"github.com/gin-gonic/gin" "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 var s Simulator

View file

@ -51,6 +51,23 @@ func TestAddSimulatorAsAdmin(t *testing.T) {
"/api/authenticate", "POST", helper.AdminCredentials) "/api/authenticate", "POST", helper.AdminCredentials)
assert.NoError(t, err) 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 // test POST simulators/ $newSimulator
newSimulator := SimulatorRequest{ newSimulator := SimulatorRequest{
UUID: database.SimulatorA.UUID, UUID: database.SimulatorA.UUID,
@ -59,7 +76,7 @@ func TestAddSimulatorAsAdmin(t *testing.T) {
State: database.SimulatorA.State, State: database.SimulatorA.State,
Properties: database.SimulatorA.Properties, 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}) "/api/simulators", "POST", helper.KeyModels{"simulator": newSimulator})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) 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 // Compare GET's response with the newSimulator
err = helper.CompareResponse(resp, helper.KeyModels{"simulator": newSimulator}) err = helper.CompareResponse(resp, helper.KeyModels{"simulator": newSimulator})
assert.NoError(t, err) 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) { func TestAddSimulatorAsUser(t *testing.T) {
@ -142,6 +166,13 @@ func TestUpdateSimulatorAsAdmin(t *testing.T) {
newSimulatorID, err := helper.GetResponseID(resp) newSimulatorID, err := helper.GetResponseID(resp)
assert.NoError(t, err) 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 // Test PUT simulators
newSimulator.Host = "ThisIsMyNewHost" newSimulator.Host = "ThisIsMyNewHost"
code, resp, err = helper.TestEndpoint(router, token, 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.Equalf(t, 200, code, "Response body: \n%v\n", resp)
assert.Equal(t, 0, numberOfModels) 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 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` // Use the old Simulator as a basis for the updated Simulator `s`
s := oldSimulator s := oldSimulator
@ -89,5 +89,5 @@ func (r *updateSimulatorRequest) updatedSimulator(oldSimulator Simulator) (Simul
s.Properties = r.Properties s.Properties = r.Properties
} }
return s, nil return s
} }

View file

@ -64,6 +64,10 @@ func main() {
file.RegisterFileEndpoints(api.Group("/files")) file.RegisterFileEndpoints(api.Group("/files"))
user.RegisterUserEndpoints(api.Group("/users")) user.RegisterUserEndpoints(api.Group("/users"))
simulator.RegisterSimulatorEndpoints(api.Group("/simulators")) 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)) r.GET("swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))