diff --git a/amqp/amqp_endpoints.go b/amqp/amqp_endpoints.go new file mode 100644 index 0000000..f6ba08c --- /dev/null +++ b/amqp/amqp_endpoints.go @@ -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.", + }) +} diff --git a/routes/simulator/simulator_endpoints.go b/routes/simulator/simulator_endpoints.go index 8c52efa..a034dc9 100644 --- a/routes/simulator/simulator_endpoints.go +++ b/routes/simulator/simulator_endpoints.go @@ -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.", - }) } diff --git a/routes/simulator/simulator_methods.go b/routes/simulator/simulator_methods.go index 9375904..67f1dbe 100644 --- a/routes/simulator/simulator_methods.go +++ b/routes/simulator/simulator_methods.go @@ -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 { diff --git a/routes/simulator/simulator_middleware.go b/routes/simulator/simulator_middleware.go index 7189a10..2977f5a 100644 --- a/routes/simulator/simulator_middleware.go +++ b/routes/simulator/simulator_middleware.go @@ -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 diff --git a/routes/simulator/simulator_test.go b/routes/simulator/simulator_test.go index 1368963..df73098 100644 --- a/routes/simulator/simulator_test.go +++ b/routes/simulator/simulator_test.go @@ -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) } diff --git a/routes/simulator/simulator_validators.go b/routes/simulator/simulator_validators.go index db9dc0c..6f878c7 100644 --- a/routes/simulator/simulator_validators.go +++ b/routes/simulator/simulator_validators.go @@ -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 } diff --git a/start.go b/start.go index e23d243..167aca3 100644 --- a/start.go +++ b/start.go @@ -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))