diff --git a/common/responses.go b/common/responses.go index a3c6837..45b1e70 100644 --- a/common/responses.go +++ b/common/responses.go @@ -12,18 +12,6 @@ type SimulationModelResponse struct { StartParameters postgres.Jsonb `json:"startParameters"` } -type SimulatorResponse struct { - ID uint `json:"id"` - UUID string `json:"uuid"` - Host string `json:"host"` - Modeltype string `json:"modelType"` - Uptime int `json:"uptime"` - State string `json:"state"` - StateUpdateAt string `json:"stateUpdateAt"` - Properties postgres.Jsonb `json:"properties"` - RawProperties postgres.Jsonb `json:"rawProperties"` -} - type DashboardResponse struct { ID uint `json:"id"` Name string `json:"name"` @@ -105,14 +93,6 @@ type ResponseMsgWidget struct { Widget WidgetResponse `json:"widget"` } -type ResponseMsgSimulators struct { - Simulators []SimulatorResponse `json:"simulators"` -} - -type ResponseMsgSimulator struct { - Simulator SimulatorResponse `json:"simulator"` -} - type ResponseMsgFiles struct { Files []FileResponse `json:"files"` } diff --git a/common/serializers.go b/common/serializers.go index 5e66fee..90899c4 100644 --- a/common/serializers.go +++ b/common/serializers.go @@ -38,43 +38,6 @@ func (self *SimulationModelSerializer) Response() SimulationModelResponse { return response } -// Simulator/s Serializers - -type SimulatorsSerializer struct { - Ctx *gin.Context - Simulators []Simulator -} - -func (self *SimulatorsSerializer) Response() []SimulatorResponse { - response := []SimulatorResponse{} - for _, simulator := range self.Simulators { - serializer := SimulatorSerializer{self.Ctx, simulator} - response = append(response, serializer.Response()) - } - return response -} - -type SimulatorSerializer struct { - Ctx *gin.Context - Simulator -} - -func (self *SimulatorSerializer) Response() SimulatorResponse { - - response := SimulatorResponse{ - ID: self.ID, - UUID: self.UUID, - Host: self.Host, - Modeltype: self.Modeltype, - Uptime: self.Uptime, - State: self.State, - StateUpdateAt: self.StateUpdateAt, - Properties: self.Properties, - RawProperties: self.RawProperties, - } - return response -} - // Dashboard/s Serializers type DashboardsSerializer struct { diff --git a/common/testdata.go b/common/testdata.go index 732ed97..3d8869a 100644 --- a/common/testdata.go +++ b/common/testdata.go @@ -57,8 +57,6 @@ var UserBCredentials = Credentials{ var propertiesA = json.RawMessage(`{"name" : "TestNameA", "category" : "CategoryA", "location" : "anywhere on earth", "type": "dummy"}`) var propertiesB = json.RawMessage(`{"name" : "TestNameB", "category" : "CategoryB", "location" : "where ever you want", "type": "generic"}`) -var propertiesC = json.RawMessage(`{"name" : "TestNameC", "category" : "CategoryC", "location" : "my desk", "type": "blubb"}`) -var propertiesCupdated = json.RawMessage(`{"name" : "TestNameCUpdate", "category" : "CategoryC", "location" : "my desk", "type": "blubb"}`) var SimulatorA = Simulator{ UUID: "4854af30-325f-44a5-ad59-b67b2597de68", @@ -71,18 +69,6 @@ var SimulatorA = Simulator{ RawProperties: postgres.Jsonb{propertiesA}, } -var SimulatorA_response = SimulatorResponse{ - ID: 1, - UUID: SimulatorA.UUID, - Host: SimulatorA.Host, - Modeltype: SimulatorA.Modeltype, - Uptime: SimulatorA.Uptime, - State: SimulatorA.State, - StateUpdateAt: SimulatorA.StateUpdateAt, - Properties: SimulatorA.Properties, - RawProperties: SimulatorA.RawProperties, -} - var SimulatorB = Simulator{ UUID: "7be0322d-354e-431e-84bd-ae4c9633138b", Host: "Host_B", @@ -94,72 +80,22 @@ var SimulatorB = Simulator{ RawProperties: postgres.Jsonb{propertiesB}, } -var SimulatorB_response = SimulatorResponse{ - ID: 2, - UUID: SimulatorB.UUID, - Host: SimulatorB.Host, - Modeltype: SimulatorB.Modeltype, - Uptime: SimulatorB.Uptime, - State: SimulatorB.State, - StateUpdateAt: SimulatorB.StateUpdateAt, - Properties: SimulatorB.Properties, - RawProperties: SimulatorB.RawProperties, -} - -var SimulatorC = Simulator{ - UUID: "6d9776bf-b693-45e8-97b6-4c13d151043f", - Host: "Host_C", - Modeltype: "ModelTypeC", - Uptime: 0, - State: "idle", - StateUpdateAt: time.Now().String(), - Properties: postgres.Jsonb{propertiesC}, - RawProperties: postgres.Jsonb{propertiesC}, -} - -var SimulatorC_response = SimulatorResponse{ - ID: 3, - UUID: SimulatorC.UUID, - Host: SimulatorC.Host, - Modeltype: SimulatorC.Modeltype, - Uptime: SimulatorC.Uptime, - State: SimulatorC.State, - StateUpdateAt: SimulatorC.StateUpdateAt, - Properties: SimulatorC.Properties, - RawProperties: SimulatorC.RawProperties, -} - -var SimulatorCUpdated = Simulator{ - UUID: SimulatorC.UUID, - Host: "Host_Cupdated", - Modeltype: "ModelTypeCUpdated", - Uptime: SimulatorC.Uptime, - State: "running", - StateUpdateAt: time.Now().String(), - Properties: postgres.Jsonb{propertiesCupdated}, - RawProperties: postgres.Jsonb{propertiesCupdated}, -} - -var SimulatorCUpdated_response = SimulatorResponse{ - ID: 3, - UUID: SimulatorCUpdated.UUID, - Host: SimulatorCUpdated.Host, - Modeltype: SimulatorCUpdated.Modeltype, - Uptime: SimulatorCUpdated.Uptime, - State: SimulatorCUpdated.State, - StateUpdateAt: SimulatorCUpdated.StateUpdateAt, - Properties: SimulatorCUpdated.Properties, - RawProperties: SimulatorCUpdated.RawProperties, -} - // Scenarios var startParametersA = json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`) var startParametersB = json.RawMessage(`{"parameter1" : "testValue1B", "parameter2" : "testValue2B", "parameter3" : 43}`) var startParametersC = json.RawMessage(`{"parameter1" : "testValue1C", "parameter2" : "testValue2C", "parameter3" : 44}`) -var ScenarioA = Scenario{Name: "Scenario_A", Running: true, StartParameters: postgres.Jsonb{startParametersA}} -var ScenarioB = Scenario{Name: "Scenario_B", Running: false, StartParameters: postgres.Jsonb{startParametersB}} +var ScenarioA = Scenario{ + Name: "Scenario_A", + Running: true, + StartParameters: postgres.Jsonb{startParametersA}, +} +var ScenarioB = Scenario{ + Name: "Scenario_B", + Running: false, + StartParameters: postgres.Jsonb{startParametersB}, +} // Simulation Models diff --git a/doc/api/responses.go b/doc/api/responses.go index 7fb6413..20e4075 100644 --- a/doc/api/responses.go +++ b/doc/api/responses.go @@ -40,3 +40,11 @@ type ResponseScenarios struct { type ResponseScenario struct { scenario common.Scenario } + +type ResponseSimulationModels struct { + models []common.SimulationModel +} + +type ResponseSimulationModel struct { + model common.SimulationModel +} diff --git a/routes/simulator/simulator_endpoints.go b/routes/simulator/simulator_endpoints.go index 1ff2718..ead7fda 100644 --- a/routes/simulator/simulator_endpoints.go +++ b/routes/simulator/simulator_endpoints.go @@ -3,7 +3,6 @@ package simulator import ( "fmt" "net/http" - "strconv" "time" "github.com/gin-gonic/gin" @@ -29,17 +28,19 @@ func RegisterSimulatorEndpoints(r *gin.RouterGroup) { // @ID getSimulators // @Tags simulators // @Produce json -// @Success 200 {array} common.ResponseMsgSimulators "Simulator parameters requested by user" -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Success 200 {object} docs.ResponseSimulators "Simulators requested" +// @Failure 404 {object} docs.ResponseError "Not found" +// @Failure 422 {object} docs.ResponseError "Unprocessable entity" +// @Failure 500 {object} docs.ResponseError "Internal server error" // @Router /simulators [get] func getSimulators(c *gin.Context) { err := common.ValidateRole(c, common.ModelSimulator, common.Read) if err != nil { - c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).") + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) return } @@ -49,9 +50,9 @@ func getSimulators(c *gin.Context) { if common.ProvideErrorResponse(c, err) { return } - serializer := common.SimulatorsSerializer{c, simulators} + c.JSON(http.StatusOK, gin.H{ - "simulators": serializer.Response(), + "simulators": simulators, }) } @@ -61,48 +62,57 @@ func getSimulators(c *gin.Context) { // @Accept json // @Produce json // @Tags simulators -// @Param inputSimulator body common.ResponseMsgSimulator true "Simulator to be added" -// @Success 200 "OK." -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Param inputSimulator body simulator.validNewSimulator true "Simulator to be added" +// @Success 200 {object} docs.ResponseSimulator "Simulator that was added" +// @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" // @Router /simulators [post] func addSimulator(c *gin.Context) { err := common.ValidateRole(c, common.ModelSimulator, common.Create) if err != nil { - c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).") + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) return } - var newSimulatorData common.ResponseMsgSimulator - err = c.BindJSON(&newSimulatorData) + var req addSimulatorRequest + err = c.BindJSON(&req) if err != nil { errormsg := "Bad request. Error binding form data to JSON: " + err.Error() c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": errormsg, }) return } - var newSimulator Simulator - newSimulator.ID = newSimulatorData.Simulator.ID - newSimulator.State = newSimulatorData.Simulator.State - newSimulator.StateUpdateAt = newSimulatorData.Simulator.StateUpdateAt - newSimulator.Modeltype = newSimulatorData.Simulator.Modeltype - newSimulator.UUID = newSimulatorData.Simulator.UUID - newSimulator.Uptime = newSimulatorData.Simulator.Uptime - newSimulator.Host = newSimulatorData.Simulator.Host - newSimulator.RawProperties = newSimulatorData.Simulator.RawProperties - newSimulator.Properties = newSimulatorData.Simulator.Properties - - err = newSimulator.save() - if common.ProvideErrorResponse(c, err) == false { - c.JSON(http.StatusOK, gin.H{ - "message": "OK.", + // Validate the request + if err = req.validate(); err != nil { + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), }) + return } + + // Create the new simulator from the request + newSimulator := req.createSimulator() + + // Save new simulator to DB + err = newSimulator.save() + if err != nil { + common.ProvideErrorResponse(c, err) + return + } + + c.JSON(http.StatusOK, gin.H{ + "simulator": newSimulator.Simulator, + }) } // updateSimulator godoc @@ -111,53 +121,80 @@ func addSimulator(c *gin.Context) { // @Tags simulators // @Accept json // @Produce json -// @Param inputSimulator body common.ResponseMsgSimulator true "Simulator to be updated" -// @Success 200 "OK." -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Param inputSimulator body simulator.validUpdatedSimulator true "Simulator to be updated" +// @Success 200 {object} docs.ResponseSimulator "Simulator that was updated" +// @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} [put] func updateSimulator(c *gin.Context) { err := common.ValidateRole(c, common.ModelSimulator, common.Update) if err != nil { - c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).") - return - } - - var modifiedSimulator common.ResponseMsgSimulator - - err = c.BindJSON(&modifiedSimulator) - if err != nil { - errormsg := "Bad request. Error unmarshalling data to JSON: " + err.Error() - c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), }) return } - simulatorID, err := strconv.Atoi(c.Param("simulatorID")) + var req updateSimulatorRequest + err = c.BindJSON(&req) if err != nil { - errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulatorID path parameter") c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": "Bad request. Error binding form data to JSON: " + err.Error(), }) return } - var s Simulator - err = s.ByID(uint(simulatorID)) + // Validate the request + if err = req.validate(); err != nil { + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) + return + } + + // Get the ID of the simulator to be updated from the context + toBeUpdatedID, err := common.UintParamFromCtx(c, "simulatorID") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": fmt.Sprintf("Could not get simulator's ID from context"), + }) + return + } + + var oldSimulator Simulator + err = oldSimulator.ByID(toBeUpdatedID) if common.ProvideErrorResponse(c, err) { return } - err = s.update(modifiedSimulator.Simulator) - if common.ProvideErrorResponse(c, err) == false { - c.JSON(http.StatusOK, gin.H{ - "message": "OK.", + // Create the updatedSimulator from oldSimulator + updatedSimulator, err := req.updatedSimulator(oldSimulator) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), }) + return } + + // Finally update the simulator in the DB + err = oldSimulator.update(updatedSimulator) + if err != nil { + common.ProvideErrorResponse(c, err) + return + } + + c.JSON(http.StatusOK, gin.H{ + "simulator": updatedSimulator.Simulator, + }) + } // getSimulator godoc @@ -165,39 +202,42 @@ func updateSimulator(c *gin.Context) { // @ID getSimulator // @Produce json // @Tags simulators -// @Success 200 {object} common.ResponseMsgSimulator "Simulator requested by user" -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Success 200 {object} docs.ResponseSimulator "Simulator that was requested" +// @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} [get] func getSimulator(c *gin.Context) { err := common.ValidateRole(c, common.ModelSimulator, common.Read) if err != nil { - c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).") + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) return } - simulatorID, err := strconv.Atoi(c.Param("simulatorID")) + // Get the ID of the simulator from the context + simulatorID, err := common.UintParamFromCtx(c, "simulatorID") if err != nil { - errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulatorID path parameter") c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": fmt.Sprintf("Could not get simulator's ID from context"), }) return } var s Simulator - err = s.ByID(uint(simulatorID)) + err = s.ByID(simulatorID) if common.ProvideErrorResponse(c, err) { return } - serializer := common.SimulatorSerializer{c, s.Simulator} c.JSON(http.StatusOK, gin.H{ - "simulator": serializer.Response(), + "simulator": s.Simulator, }) } @@ -206,43 +246,48 @@ func getSimulator(c *gin.Context) { // @ID deleteSimulator // @Tags simulators // @Produce json -// @Success 200 "OK." -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Success 200 {object} docs.ResponseSimulator "Simulator that was deleted" +// @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} [delete] func deleteSimulator(c *gin.Context) { err := common.ValidateRole(c, common.ModelSimulator, common.Delete) if err != nil { - c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).") + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) return } - simulatorID, err := strconv.Atoi(c.Param("simulatorID")) + // Get the ID of the simulator from the context + simulatorID, err := common.UintParamFromCtx(c, "simulatorID") if err != nil { - errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulatorID path parameter") c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": fmt.Sprintf("Could not get simulator's ID from context"), }) return } var s Simulator - err = s.ByID(uint(simulatorID)) + err = s.ByID(simulatorID) if common.ProvideErrorResponse(c, err) { return } + // Delete the simulator err = s.delete() if common.ProvideErrorResponse(c, err) { return } c.JSON(http.StatusOK, gin.H{ - "message": "OK.", + "simulator": s.Simulator, }) } @@ -251,32 +296,36 @@ func deleteSimulator(c *gin.Context) { // @ID getModelsOfSimulator // @Tags simulators // @Produce json -// @Success 200 {object} common.ResponseMsgSimulationModels "Simulation models requested by user" -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Success 200 {object} docs.ResponseSimulationModels "Simulation models requested by user" +// @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}/models [get] func getModelsOfSimulator(c *gin.Context) { err := common.ValidateRole(c, common.ModelSimulator, common.Read) if err != nil { - c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).") + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) return } - simulatorID, err := strconv.Atoi(c.Param("simulatorID")) + // Get the ID of the simulator from the context + simulatorID, err := common.UintParamFromCtx(c, "simulatorID") if err != nil { - errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulatorID path parameter") c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": fmt.Sprintf("Could not get simulator's ID from context"), }) return } var s Simulator - err = s.ByID(uint(simulatorID)) + err = s.ByID(simulatorID) if common.ProvideErrorResponse(c, err) { return } @@ -287,11 +336,9 @@ func getModelsOfSimulator(c *gin.Context) { return } - serializer := common.SimulationModelsSerializer{c, allModels} c.JSON(http.StatusOK, gin.H{ - "models": serializer.Response(), + "models": allModels, }) - } // sendActionToSimulator godoc @@ -300,26 +347,30 @@ func getModelsOfSimulator(c *gin.Context) { // @Tags simulators // @Produce json // @Param inputAction query string true "Action for simulator" -// @Success 200 "OK." -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @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) { err := common.ValidateRole(c, common.ModelSimulatorAction, common.Update) if err != nil { - c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).") + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) return } - simulatorID, err := strconv.Atoi(c.Param("simulatorID")) + // Get the ID of the simulator from the context + simulatorID, err := common.UintParamFromCtx(c, "simulatorID") if err != nil { - errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulatorID path parameter") c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": fmt.Sprintf("Could not get simulator's ID from context"), }) return } @@ -358,6 +409,7 @@ func sendActionToSimulator(c *gin.Context) { } 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 37f9bd1..186bdaf 100644 --- a/routes/simulator/simulator_methods.go +++ b/routes/simulator/simulator_methods.go @@ -25,13 +25,12 @@ func (s *Simulator) ByID(id uint) error { return nil } -func (s *Simulator) update(modifiedSimulator common.SimulatorResponse) error { +func (s *Simulator) update(updatedSimulator Simulator) error { db := common.GetDB() - err := db.Model(s).Updates(modifiedSimulator).Error + err := db.Model(s).Updates(updatedSimulator).Error return err - } func (s *Simulator) delete() error { diff --git a/routes/simulator/simulator_test.go b/routes/simulator/simulator_test.go index 3ac13bc..8ef7964 100644 --- a/routes/simulator/simulator_test.go +++ b/routes/simulator/simulator_test.go @@ -1,7 +1,11 @@ package simulator import ( - "encoding/json" + "fmt" + "github.com/jinzhu/gorm" + "github.com/jinzhu/gorm/dialects/postgres" + "github.com/stretchr/testify/assert" + "os" "testing" "github.com/gin-gonic/gin" @@ -10,62 +14,398 @@ import ( "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user" ) -// Test /simulator endpoints -func TestSimulatorEndpoints(t *testing.T) { +var router *gin.Engine +var db *gorm.DB - var token string +type SimulatorRequest struct { + UUID string `json:"uuid,omitempty"` + Host string `json:"host,omitempty"` + Modeltype string `json:"modelType,omitempty"` + State string `json:"state,omitempty"` + Properties postgres.Jsonb `json:"properties,omitempty"` +} - var myModels = []common.SimulationModelResponse{common.SimulationModelA_response, common.SimulationModelB_response} - var msgModels = common.ResponseMsgSimulationModels{SimulationModels: myModels} - var simulatorC_msg = common.ResponseMsgSimulator{Simulator: common.SimulatorC_response} - var simulatorCupdated_msg = common.ResponseMsgSimulator{Simulator: common.SimulatorCUpdated_response} - var mySimulators = []common.SimulatorResponse{common.SimulatorA_response, common.SimulatorB_response} - var msgSimulators = common.ResponseMsgSimulators{Simulators: mySimulators} +func TestMain(m *testing.M) { - db := common.DummyInitDB() + db = common.DummyInitDB() defer db.Close() - common.DummyPopulateDB(db) - router := gin.Default() + router = gin.Default() api := router.Group("/api") - // All endpoints require authentication except when someone wants to - // login (POST /authenticate) user.RegisterAuthenticate(api.Group("/authenticate")) - api.Use(user.Authentication(true)) - RegisterSimulatorEndpoints(api.Group("/simulators")) - credjson, _ := json.Marshal(common.CredAdmin) - msgOKjson, _ := json.Marshal(common.MsgOK) - msgModelsjson, _ := json.Marshal(msgModels) - msgSimulatorsjson, _ := json.Marshal(msgSimulators) - msgSimulatorjson, _ := json.Marshal(simulatorC_msg) - simulatorCjson, _ := json.Marshal(simulatorC_msg) - simulatorCupdatedjson, _ := json.Marshal(simulatorCupdated_msg) - - token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200) - - // test GET simulators/ - common.TestEndpoint(t, router, token, "/api/simulators", "GET", nil, 200, msgSimulatorsjson) - - // test POST simulators/ - common.TestEndpoint(t, router, token, "/api/simulators", "POST", simulatorCjson, 200, msgOKjson) - - // test GET simulators/:SimulatorID - common.TestEndpoint(t, router, token, "/api/simulators/3", "GET", nil, 200, msgSimulatorjson) - - // test PUT simulators/:SimulatorID - common.TestEndpoint(t, router, token, "/api/simulators/3", "PUT", simulatorCupdatedjson, 200, msgOKjson) - common.TestEndpoint(t, router, token, "/api/simulators/3", "GET", nil, 200, simulatorCupdatedjson) - - // test DELETE simulators/:SimulatorID - common.TestEndpoint(t, router, token, "/api/simulators/3", "DELETE", nil, 200, msgOKjson) - common.TestEndpoint(t, router, token, "/api/simulators", "GET", nil, 200, msgSimulatorsjson) - - // test GET simulators/:SimulatorID/models - common.TestEndpoint(t, router, token, "/api/simulators/1/models", "GET", nil, 200, msgModelsjson) - - // TODO add tests for other return codes + os.Exit(m.Run()) +} + +func TestAddSimulatorAsAdmin(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as admin + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.AdminCredentials) + assert.NoError(t, err) + + // test POST simulators/ $newSimulator + newSimulator := SimulatorRequest{ + UUID: common.SimulatorA.UUID, + Host: common.SimulatorA.Host, + Modeltype: common.SimulatorA.Modeltype, + State: common.SimulatorA.State, + Properties: common.SimulatorA.Properties, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/simulators", "POST", common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare POST's response with the newSimulator + err = common.CompareResponse(resp, common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + + // Read newSimulator's ID from the response + newSimulatorID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // Get the newSimulator + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/simulators/%v", newSimulatorID), "GET", nil) + assert.NoError(t, err) + + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare GET's response with the newSimulator + err = common.CompareResponse(resp, common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) +} + +func TestAddSimulatorAsUser(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST simulators/ $newSimulator + newSimulator := SimulatorRequest{ + UUID: common.SimulatorA.UUID, + Host: common.SimulatorA.Host, + Modeltype: common.SimulatorA.Modeltype, + State: common.SimulatorA.State, + Properties: common.SimulatorA.Properties, + } + + // This should fail with unprocessable entity 422 error code + // Normal users are not allowed to add simulators + code, resp, err := common.NewTestEndpoint(router, token, + "/api/simulators", "POST", common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + assert.Equalf(t, 422, code, "Response body: \n%v\n", resp) +} + +func TestUpdateSimulatorAsAdmin(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as admin + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.AdminCredentials) + assert.NoError(t, err) + + // test POST simulators/ $newSimulator + newSimulator := SimulatorRequest{ + UUID: common.SimulatorA.UUID, + Host: common.SimulatorA.Host, + Modeltype: common.SimulatorA.Modeltype, + State: common.SimulatorA.State, + Properties: common.SimulatorA.Properties, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/simulators", "POST", common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare POST's response with the newSimulator + err = common.CompareResponse(resp, common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + + // Read newSimulator's ID from the response + newSimulatorID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // Test PUT simulators + newSimulator.Host = "ThisIsMyNewHost" + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/simulators/%v", newSimulatorID), "PUT", common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare PUT's response with the updated newSimulator + err = common.CompareResponse(resp, common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + + // Get the updated newSimulator + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/simulators/%v", newSimulatorID), "GET", nil) + assert.NoError(t, err) + + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare GET's response with the updated newSimulator + err = common.CompareResponse(resp, common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + +} + +func TestUpdateSimulatorAsUser(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as admin + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.AdminCredentials) + assert.NoError(t, err) + + // test POST simulators/ $newSimulator + newSimulator := SimulatorRequest{ + UUID: common.SimulatorA.UUID, + Host: common.SimulatorA.Host, + Modeltype: common.SimulatorA.Modeltype, + State: common.SimulatorA.State, + Properties: common.SimulatorA.Properties, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/simulators", "POST", common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newSimulator's ID from the response + newSimulatorID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // authenticate as user + token, err = common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // Test PUT simulators + // This should fail with unprocessable entity status code 422 + newSimulator.Host = "ThisIsMyNewHost" + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/simulators/%v", newSimulatorID), "PUT", common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + assert.Equalf(t, 422, code, "Response body: \n%v\n", resp) + +} + +func TestDeleteSimulatorAsAdmin(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as admin + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.AdminCredentials) + assert.NoError(t, err) + + // test POST simulators/ $newSimulator + newSimulator := SimulatorRequest{ + UUID: common.SimulatorA.UUID, + Host: common.SimulatorA.Host, + Modeltype: common.SimulatorA.Modeltype, + State: common.SimulatorA.State, + Properties: common.SimulatorA.Properties, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/simulators", "POST", common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newSimulator's ID from the response + newSimulatorID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // Count the number of all the simulators returned for admin + initialNumber, err := common.LengthOfResponse(router, token, + "/api/simulators", "GET", nil) + assert.NoError(t, err) + + // Delete the added newSimulator + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/simulators/%v", newSimulatorID), "DELETE", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare DELETE's response with the newSimulator + err = common.CompareResponse(resp, common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + + // Again count the number of all the simulators returned + finalNumber, err := common.LengthOfResponse(router, token, + "/api/simulators", "GET", nil) + assert.NoError(t, err) + + assert.Equal(t, finalNumber, initialNumber-1) +} + +func TestDeleteSimulatorAsUser(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as admin + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.AdminCredentials) + assert.NoError(t, err) + + // test POST simulators/ $newSimulator + newSimulator := SimulatorRequest{ + UUID: common.SimulatorA.UUID, + Host: common.SimulatorA.Host, + Modeltype: common.SimulatorA.Modeltype, + State: common.SimulatorA.State, + Properties: common.SimulatorA.Properties, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/simulators", "POST", common.KeyModels{"simulator": newSimulator}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newSimulator's ID from the response + newSimulatorID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // authenticate as user + token, err = common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // Test DELETE simulators + // This should fail with unprocessable entity status code 422 + newSimulator.Host = "ThisIsMyNewHost" + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/simulators/%v", newSimulatorID), "DELETE", nil) + assert.NoError(t, err) + assert.Equalf(t, 422, code, "Response body: \n%v\n", resp) +} + +func TestGetAllSimulators(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as admin + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.AdminCredentials) + assert.NoError(t, err) + + // get the length of the GET all simulators response for user + initialNumber, err := common.LengthOfResponse(router, token, + "/api/simulators", "GET", nil) + assert.NoError(t, err) + + // test POST simulators/ $newSimulatorA + newSimulatorA := SimulatorRequest{ + UUID: common.SimulatorA.UUID, + Host: common.SimulatorA.Host, + Modeltype: common.SimulatorA.Modeltype, + State: common.SimulatorA.State, + Properties: common.SimulatorA.Properties, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/simulators", "POST", common.KeyModels{"simulator": newSimulatorA}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // test POST simulators/ $newSimulatorB + newSimulatorB := SimulatorRequest{ + UUID: common.SimulatorB.UUID, + Host: common.SimulatorB.Host, + Modeltype: common.SimulatorB.Modeltype, + State: common.SimulatorB.State, + Properties: common.SimulatorB.Properties, + } + code, resp, err = common.NewTestEndpoint(router, token, + "/api/simulators", "POST", common.KeyModels{"simulator": newSimulatorB}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // get the length of the GET all simulators response again + finalNumber, err := common.LengthOfResponse(router, token, + "/api/simulators", "GET", nil) + assert.NoError(t, err) + + assert.Equal(t, finalNumber, initialNumber+2) + + // authenticate as normal user + token, err = common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // get the length of the GET all simulators response again + finalNumber2, err := common.LengthOfResponse(router, token, + "/api/simulators", "GET", nil) + assert.NoError(t, err) + + assert.Equal(t, finalNumber2, initialNumber+2) +} + +func TestGetSimulationModelsOfSimulator(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as admin + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.AdminCredentials) + assert.NoError(t, err) + + // test POST simulators/ $newSimulatorA + newSimulatorA := SimulatorRequest{ + UUID: common.SimulatorA.UUID, + Host: common.SimulatorA.Host, + Modeltype: common.SimulatorA.Modeltype, + State: common.SimulatorA.State, + Properties: common.SimulatorA.Properties, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/simulators", "POST", common.KeyModels{"simulator": newSimulatorA}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newSimulator's ID from the response + newSimulatorID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // test GET simulators/ID/models + // TODO how to properly test this without using simulation model endpoints? + numberOfModels, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/simulators/%v/models", newSimulatorID), "GET", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + assert.Equal(t, 0, numberOfModels) + + // authenticate as normal user + token, err = common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test GET simulators/ID/models + // TODO how to properly test this without using simulation model endpoints? + numberOfModels, err = common.LengthOfResponse(router, token, + fmt.Sprintf("/api/simulators/%v/models", newSimulatorID), "GET", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + assert.Equal(t, 0, numberOfModels) } diff --git a/routes/simulator/simulator_validators.go b/routes/simulator/simulator_validators.go new file mode 100644 index 0000000..2a4fa91 --- /dev/null +++ b/routes/simulator/simulator_validators.go @@ -0,0 +1,82 @@ +package simulator + +import ( + "github.com/jinzhu/gorm/dialects/postgres" + "gopkg.in/go-playground/validator.v9" +) + +var validate *validator.Validate + +type validNewSimulator struct { + UUID string `form:"UUID" validate:"required"` + Host string `form:"Host" validate:"required"` + Modeltype string `form:"Modeltype" validate:"required"` + Properties postgres.Jsonb `form:"Properties" validate:"required"` + State string `form:"State"` +} + +type validUpdatedSimulator struct { + UUID string `form:"UUID" validate:"omitempty"` + Host string `form:"Host" validate:"omitempty"` + Modeltype string `form:"Modeltype" validate:"omitempty"` + Properties postgres.Jsonb `form:"Properties" validate:"omitempty"` + State string `form:"State" validate:"omitempty"` +} + +type addSimulatorRequest struct { + validNewSimulator `json:"simulator"` +} + +type updateSimulatorRequest struct { + validUpdatedSimulator `json:"simulator"` +} + +func (r *addSimulatorRequest) validate() error { + validate = validator.New() + errs := validate.Struct(r) + return errs +} + +func (r *validUpdatedSimulator) validate() error { + validate = validator.New() + errs := validate.Struct(r) + return errs +} + +func (r *addSimulatorRequest) createSimulator() Simulator { + var s Simulator + + s.UUID = r.UUID + s.Host = r.Host + s.Modeltype = r.Modeltype + s.Properties = r.Properties + if r.State != "" { + s.State = r.State + } + return s +} + +func (r *updateSimulatorRequest) updatedSimulator(oldSimulator Simulator) (Simulator, error) { + // Use the old Simulator as a basis for the updated Simulator `s` + s := oldSimulator + + if r.UUID != "" { + s.UUID = r.UUID + } + + if r.Host != "" { + s.Host = r.Host + } + + if r.Modeltype != "" { + s.Modeltype = r.Modeltype + } + + if r.State != "" { + s.State = r.State + } + // TODO check for empty properties? + s.Properties = r.Properties + + return s, nil +}