diff --git a/common/models.go b/common/models.go index 556e0cc..8f603fe 100644 --- a/common/models.go +++ b/common/models.go @@ -53,9 +53,9 @@ type SimulationModel struct { // Name of simulation model Name string `json:"name" gorm:"not null"` // Number of output signals - OutputLength int `json:"outputLength" gorm:"default:1"` + OutputLength int `json:"outputLength" gorm:"default:0"` // Number of input signals - InputLength int `json:"inputLength" gorm:"default:1"` + InputLength int `json:"inputLength" gorm:"default:0"` // Start parameters of simulation model as JSON StartParameters postgres.Jsonb `json:"startParameters"` // ID of Scenario to which simulation model belongs diff --git a/common/responses.go b/common/responses.go index 45b1e70..4de4cc6 100644 --- a/common/responses.go +++ b/common/responses.go @@ -2,16 +2,6 @@ package common import "github.com/jinzhu/gorm/dialects/postgres" -type SimulationModelResponse struct { - ID uint `json:"id"` - Name string `json:"name"` - OutputLength int `json:"outputLength"` - InputLength int `json:"inputLength"` - ScenarioID uint `json:"scenarioID"` - SimulatorID uint `json:"simulatorID"` - StartParameters postgres.Jsonb `json:"startParameters"` -} - type DashboardResponse struct { ID uint `json:"id"` Name string `json:"name"` @@ -61,14 +51,6 @@ type ResponseMsg struct { Message string `json:"message"` } -type ResponseMsgSimulationModels struct { - SimulationModels []SimulationModelResponse `json:"models"` -} - -type ResponseMsgSimulationModel struct { - SimulationModel SimulationModelResponse `json:"model"` -} - type ResponseMsgSignals struct { Signals []SignalResponse `json:"signals"` } diff --git a/common/serializers.go b/common/serializers.go index 90899c4..5d3be7e 100644 --- a/common/serializers.go +++ b/common/serializers.go @@ -4,40 +4,6 @@ import ( "github.com/gin-gonic/gin" ) -// Model/s Serializers - -type SimulationModelsSerializer struct { - Ctx *gin.Context - SimulationModels []SimulationModel -} - -func (self *SimulationModelsSerializer) Response() []SimulationModelResponse { - response := []SimulationModelResponse{} - for _, simulationmodel := range self.SimulationModels { - serializer := SimulationModelSerializer{self.Ctx, simulationmodel} - response = append(response, serializer.Response()) - } - return response -} - -type SimulationModelSerializer struct { - Ctx *gin.Context - SimulationModel -} - -func (self *SimulationModelSerializer) Response() SimulationModelResponse { - response := SimulationModelResponse{ - ID: self.ID, - Name: self.Name, - OutputLength: self.OutputLength, - InputLength: self.InputLength, - ScenarioID: self.ScenarioID, - SimulatorID: self.SimulatorID, - StartParameters: self.StartParameters, - } - return response -} - // Dashboard/s Serializers type DashboardsSerializer struct { diff --git a/common/testdata.go b/common/testdata.go index 3d8869a..9f8a539 100644 --- a/common/testdata.go +++ b/common/testdata.go @@ -84,7 +84,6 @@ var SimulatorB = Simulator{ 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", @@ -101,72 +100,14 @@ var ScenarioB = Scenario{ var SimulationModelA = SimulationModel{ Name: "SimulationModel_A", - OutputLength: 1, - InputLength: 1, StartParameters: postgres.Jsonb{startParametersA}, } -var SimulationModelA_response = SimulationModelResponse{ - ID: 1, - Name: SimulationModelA.Name, - InputLength: SimulationModelA.InputLength, - OutputLength: SimulationModelA.OutputLength, - StartParameters: SimulationModelA.StartParameters, -} - var SimulationModelB = SimulationModel{ Name: "SimulationModel_B", - OutputLength: 1, - InputLength: 1, StartParameters: postgres.Jsonb{startParametersB}, } -var SimulationModelB_response = SimulationModelResponse{ - ID: 2, - Name: SimulationModelB.Name, - InputLength: SimulationModelB.InputLength, - OutputLength: SimulationModelB.OutputLength, - StartParameters: SimulationModelB.StartParameters, -} - -var SimulationModelC = SimulationModel{ - Name: "SimulationModel_C", - OutputLength: 1, - InputLength: 1, - StartParameters: postgres.Jsonb{startParametersC}, -} - -var SimulationModelC_response = SimulationModelResponse{ - ID: 3, - Name: SimulationModelC.Name, - InputLength: SimulationModelC.InputLength, - OutputLength: SimulationModelC.OutputLength, - ScenarioID: SimulationModelC.ScenarioID, - SimulatorID: SimulationModelC.SimulatorID, - StartParameters: SimulationModelC.StartParameters, -} - -var SimulationModelCUpdated = SimulationModel{ - Name: "SimulationModel_CUpdated", - OutputLength: SimulationModelC.OutputLength, - InputLength: SimulationModelC.InputLength, - ScenarioID: SimulationModelC.ScenarioID, - SimulatorID: 2, - StartParameters: SimulationModelC.StartParameters, - InputMapping: SimulationModelC.InputMapping, - OutputMapping: SimulationModelC.OutputMapping, -} - -var SimulationModelCUpdated_response = SimulationModelResponse{ - ID: 3, - Name: SimulationModelCUpdated.Name, - InputLength: SimulationModelCUpdated.InputLength, - OutputLength: SimulationModelCUpdated.OutputLength, - ScenarioID: SimulationModelCUpdated.ScenarioID, - SimulatorID: SimulationModelCUpdated.SimulatorID, - StartParameters: SimulationModelCUpdated.StartParameters, -} - // Signals var OutSignalA = Signal{ diff --git a/common/utilities.go b/common/utilities.go index c27bbea..70fb75d 100644 --- a/common/utilities.go +++ b/common/utilities.go @@ -152,8 +152,9 @@ func CompareResponse(resp *bytes.Buffer, expected interface{}) error { } // Compare opts := jsondiff.DefaultConsoleOptions() - diff, _ := jsondiff.Compare(resp.Bytes(), expectedBytes, &opts) + diff, text := jsondiff.Compare(resp.Bytes(), expectedBytes, &opts) if diff.String() != "FullMatch" && diff.String() != "SupersetMatch" { + fmt.Println(text) return fmt.Errorf("Response: Expected \"%v\". Got \"%v\".", "(FullMatch OR SupersetMatch)", diff.String()) } diff --git a/routes/simulationmodel/simulationmodel_endpoints.go b/routes/simulationmodel/simulationmodel_endpoints.go index 2bb5565..8f11c8a 100644 --- a/routes/simulationmodel/simulationmodel_endpoints.go +++ b/routes/simulationmodel/simulationmodel_endpoints.go @@ -1,6 +1,7 @@ package simulationmodel import ( + "fmt" "net/http" "github.com/gin-gonic/gin" @@ -22,11 +23,10 @@ func RegisterSimulationModelEndpoints(r *gin.RouterGroup) { // @ID getSimulationModels // @Produce json // @Tags models -// @Success 200 {array} common.SimulationModelResponse "Array of models to which belong to scenario" -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Success 200 {object} docs.ResponseSimulationModels "Simulation models which belong to scenario" +// @Failure 404 {object} docs.ResponseError "Not found" +// @Failure 422 {object} docs.ResponseError "Unprocessable entity" +// @Failure 500 {object} docs.ResponseError "Internal server error" // @Param scenarioID query int true "Scenario ID" // @Router /models [get] func getSimulationModels(c *gin.Context) { @@ -43,9 +43,8 @@ func getSimulationModels(c *gin.Context) { return } - serializer := common.SimulationModelsSerializer{c, models} c.JSON(http.StatusOK, gin.H{ - "models": serializer.Response(), + "models": models, }) } @@ -55,45 +54,54 @@ func getSimulationModels(c *gin.Context) { // @Accept json // @Produce json // @Tags models -// @Param inputSimulationModel body common.ResponseMsgSimulationModel true "Simulation model to be added incl. IDs of scenario and simulator" -// @Success 200 "OK." -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Param inputSimulationModel body simulationmodel.validNewSimulationModel true "Simulation model to be added incl. IDs of scenario and simulator" +// @Success 200 {object} docs.ResponseSimulationModel "simulation model 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 /models [post] func addSimulationModel(c *gin.Context) { - var newModelData common.ResponseMsgSimulationModel - err := c.BindJSON(&newModelData) + // Bind the request to JSON + var req addSimulationModelRequest + err := c.ShouldBindJSON(&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": "Bad request. Error binding form data to JSON: " + err.Error(), }) return } - var newModel SimulationModel - newModel.ID = newModelData.SimulationModel.ID - newModel.Name = newModelData.SimulationModel.Name - newModel.SimulatorID = newModelData.SimulationModel.SimulatorID - newModel.ScenarioID = newModelData.SimulationModel.ScenarioID - newModel.StartParameters = newModelData.SimulationModel.StartParameters - newModel.OutputLength = 0 - newModel.InputLength = 0 + // validate the request + if err = req.validate(); err != nil { + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) + return + } - ok, _ := scenario.CheckPermissions(c, common.Create, "body", int(newModel.ScenarioID)) + // Create the new simulation model from the request + newSimulationModel := req.createSimulationModel() + + // check access to the scenario + ok, _ := scenario.CheckPermissions(c, common.Create, "body", int(newSimulationModel.ScenarioID)) if !ok { return } - err = newModel.addToScenario() - if common.ProvideErrorResponse(c, err) == false { - c.JSON(http.StatusOK, gin.H{ - "message": "OK.", - }) + // add the new simulation model to the scenario + err = newSimulationModel.addToScenario() + if err != nil { + common.ProvideErrorResponse(c, err) + return } + + c.JSON(http.StatusOK, gin.H{ + "model": newSimulationModel.SimulationModel, + }) } // updateSimulationModel godoc @@ -102,37 +110,61 @@ func addSimulationModel(c *gin.Context) { // @Tags models // @Accept json // @Produce json -// @Param inputSimulationModel body common.ResponseMsgSimulationModel true "Simulation model to be updated" -// @Success 200 "OK." -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Param inputSimulationModel body simulationmodel.validUpdatedSimulationModel true "Simulation model to be updated" +// @Success 200 {object} docs.ResponseSimulationModel "simulation model 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" // @Param modelID path int true "Model ID" // @Router /models/{modelID} [put] func updateSimulationModel(c *gin.Context) { - ok, m := CheckPermissions(c, common.Update, "path", -1) + ok, oldSimulationModel := CheckPermissions(c, common.Update, "path", -1) if !ok { return } - var modifiedModel common.ResponseMsgSimulationModel - err := c.BindJSON(&modifiedModel) + var req updateSimulationModelRequest + 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": "Bad request. Error binding form data to JSON: " + err.Error(), }) return } - err = m.Update(modifiedModel.SimulationModel) - 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.StatusBadRequest, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), }) + return } + + // Create the updatedSimulationModel from oldSimulationModel + updatedSimulationModel, err := req.updatedSimulationModel(oldSimulationModel) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) + return + } + + // Finally, update the simulation model + err = oldSimulationModel.Update(updatedSimulationModel) + if err != nil { + common.ProvideErrorResponse(c, err) + return + } + + c.JSON(http.StatusOK, gin.H{ + "model": updatedSimulationModel.SimulationModel, + }) + } // getSimulationModel godoc @@ -140,11 +172,11 @@ func updateSimulationModel(c *gin.Context) { // @ID getSimulationModel // @Tags models // @Produce json -// @Success 200 {object} common.SimulationModelResponse "Requested simulation model." -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Success 200 {object} docs.ResponseSimulationModel "simulation model 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 modelID path int true "Model ID" // @Router /models/{modelID} [get] func getSimulationModel(c *gin.Context) { @@ -154,9 +186,8 @@ func getSimulationModel(c *gin.Context) { return } - serializer := common.SimulationModelSerializer{c, m.SimulationModel} c.JSON(http.StatusOK, gin.H{ - "model": serializer.Response(), + "model": m.SimulationModel, }) } @@ -165,11 +196,11 @@ func getSimulationModel(c *gin.Context) { // @ID deleteSimulationModel // @Tags models // @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.ResponseSimulationModel "simulation model 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 modelID path int true "Model ID" // @Router /models/{modelID} [delete] func deleteSimulationModel(c *gin.Context) { @@ -185,6 +216,6 @@ func deleteSimulationModel(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{ - "message": "OK.", + "model": m.SimulationModel, }) } diff --git a/routes/simulationmodel/simulationmodel_methods.go b/routes/simulationmodel/simulationmodel_methods.go index 295376b..37dfe54 100644 --- a/routes/simulationmodel/simulationmodel_methods.go +++ b/routes/simulationmodel/simulationmodel_methods.go @@ -1,8 +1,6 @@ package simulationmodel import ( - "fmt" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator" @@ -22,7 +20,7 @@ func (m *SimulationModel) ByID(id uint) error { db := common.GetDB() err := db.Find(m, id).Error if err != nil { - return fmt.Errorf("Simulation Model with id=%v does not exist", id) + return err } return nil } @@ -55,7 +53,7 @@ func (m *SimulationModel) addToScenario() error { return err } -func (m *SimulationModel) Update(modifiedSimulationModel common.SimulationModelResponse) error { +func (m *SimulationModel) Update(modifiedSimulationModel SimulationModel) error { db := common.GetDB() if m.SimulatorID != modifiedSimulationModel.SimulatorID { @@ -82,12 +80,15 @@ func (m *SimulationModel) Update(modifiedSimulationModel common.SimulationModelR } } + if m.ScenarioID != modifiedSimulationModel.ScenarioID { + // TODO do we allow this case? + } + err := db.Model(m).Updates(map[string]interface{}{ "Name": modifiedSimulationModel.Name, - "OutputLength": modifiedSimulationModel.OutputLength, - "InputLength": modifiedSimulationModel.InputLength, "StartParameters": modifiedSimulationModel.StartParameters, "SimulatorID": modifiedSimulationModel.SimulatorID, + //"ScenarioID": modifiedSimulationModel.ScenarioID, }).Error return err diff --git a/routes/simulationmodel/simulationmodel_middleware.go b/routes/simulationmodel/simulationmodel_middleware.go index ee18883..f54ab49 100644 --- a/routes/simulationmodel/simulationmodel_middleware.go +++ b/routes/simulationmodel/simulationmodel_middleware.go @@ -17,7 +17,10 @@ func CheckPermissions(c *gin.Context, operation common.CRUD, modelIDSource strin err := common.ValidateRole(c, common.ModelSimulationModel, operation) if err != nil { - c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).") + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("Access denied (role validation failed): %v", err), + }) return false, m } @@ -27,16 +30,17 @@ func CheckPermissions(c *gin.Context, operation common.CRUD, modelIDSource strin if err != nil { errormsg := fmt.Sprintf("Bad request. No or incorrect format of modelID path parameter") c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": errormsg, }) return false, m } } else if modelIDSource == "query" { modelID, err = strconv.Atoi(c.Request.URL.Query().Get("modelID")) if err != nil { - errormsg := fmt.Sprintf("Bad request. No or incorrect format of modelID query parameter") c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": fmt.Sprintf("Bad request. No or incorrect format of modelID query parameter"), }) return false, m } diff --git a/routes/simulationmodel/simulationmodel_test.go b/routes/simulationmodel/simulationmodel_test.go index 9dc8c11..975138f 100644 --- a/routes/simulationmodel/simulationmodel_test.go +++ b/routes/simulationmodel/simulationmodel_test.go @@ -1,65 +1,364 @@ package simulationmodel import ( - "encoding/json" - "testing" - - "github.com/gin-gonic/gin" - + "fmt" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user" + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" + "github.com/jinzhu/gorm/dialects/postgres" + "github.com/stretchr/testify/assert" + "os" + "testing" ) -// Test /models endpoints -func TestSimulationModelEndpoints(t *testing.T) { +var router *gin.Engine +var db *gorm.DB - var token string +type SimulationModelRequest struct { + Name string `json:"name,omitempty"` + ScenarioID uint `json:"scenarioID,omitempty"` + SimulatorID uint `json:"simulatorID,omitempty"` + StartParameters postgres.Jsonb `json:"startParameters,omitempty"` +} - var myModels = []common.SimulationModelResponse{common.SimulationModelA_response, common.SimulationModelB_response} - var msgModels = common.ResponseMsgSimulationModels{SimulationModels: myModels} - var msgModel = common.ResponseMsgSimulationModel{SimulationModel: common.SimulationModelC_response} - var msgModelupdated = common.ResponseMsgSimulationModel{SimulationModel: common.SimulationModelCUpdated_response} +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"` +} - db := common.DummyInitDB() +type ScenarioRequest struct { + Name string `json:"name,omitempty"` + Running bool `json:"running,omitempty"` + StartParameters postgres.Jsonb `json:"startParameters,omitempty"` +} + +func addScenarioAndSimulator() (scenarioID uint, simulatorID uint) { + + // authenticate as admin + token, _ := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.AdminCredentials) + + // POST $newSimulatorA + newSimulatorA := SimulatorRequest{ + UUID: common.SimulatorA.UUID, + Host: common.SimulatorA.Host, + Modeltype: common.SimulatorA.Modeltype, + State: common.SimulatorA.State, + Properties: common.SimulatorA.Properties, + } + _, resp, _ := common.NewTestEndpoint(router, token, + "/api/simulators", "POST", common.KeyModels{"simulator": newSimulatorA}) + + // Read newSimulator's ID from the response + newSimulatorID, _ := common.GetResponseID(resp) + + // authenticate as normal user + token, _ = common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + + // POST $newScenario + newScenario := ScenarioRequest{ + Name: common.ScenarioA.Name, + Running: common.ScenarioA.Running, + StartParameters: common.ScenarioA.StartParameters, + } + _, resp, _ = common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": newScenario}) + + // Read newScenario's ID from the response + newScenarioID, _ := common.GetResponseID(resp) + + return uint(newScenarioID), uint(newSimulatorID) +} + +func TestMain(m *testing.M) { + + 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)) - RegisterSimulationModelEndpoints(api.Group("/models")) + // scenario endpoints required here to first add a scenario to the DB + // that can be associated with a new simulation model + scenario.RegisterScenarioEndpoints(api.Group("/scenarios")) + // simulator endpoints required here to first add a simulator to the DB + // that can be associated with a new simulation model + simulator.RegisterSimulatorEndpoints(api.Group("/simulators")) - credjson, _ := json.Marshal(common.CredUser) - msgOKjson, _ := json.Marshal(common.MsgOK) - msgModelsjson, _ := json.Marshal(msgModels) - msgModeljson, _ := json.Marshal(msgModel) - msgModelupdatedjson, _ := json.Marshal(msgModelupdated) + os.Exit(m.Run()) +} - token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200) +func TestAddSimulationModel(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) - // test GET models - common.TestEndpoint(t, router, token, "/api/models?scenarioID=1", "GET", nil, 200, msgModelsjson) + // prepare the content of the DB for testing + // by adding a scenario and a simulator to the DB + // using the respective endpoints of the API + scenarioID, simulatorID := addScenarioAndSimulator() - // test POST models - common.TestEndpoint(t, router, token, "/api/models", "POST", msgModeljson, 200, msgOKjson) + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) - // test GET models/:ModelID to check if previous POST worked correctly - common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, msgModeljson) + // test POST models/ $newSimulationModel + newSimulationModel := SimulationModelRequest{ + Name: common.SimulationModelA.Name, + ScenarioID: scenarioID, + SimulatorID: simulatorID, + StartParameters: common.SimulationModelA.StartParameters, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/models", "POST", common.KeyModels{"model": newSimulationModel}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) - // test PUT models/:ModelID - common.TestEndpoint(t, router, token, "/api/models/3", "PUT", msgModelupdatedjson, 200, msgOKjson) - common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, msgModelupdatedjson) + // Compare POST's response with the newSimulationModel + err = common.CompareResponse(resp, common.KeyModels{"model": newSimulationModel}) + assert.NoError(t, err) - // test DELETE models/:ModelID - common.TestEndpoint(t, router, token, "/api/models/3", "DELETE", nil, 200, msgOKjson) - common.TestEndpoint(t, router, token, "/api/models?scenarioID=1", "GET", nil, 200, msgModelsjson) + // Read newSimulationModel's ID from the response + newSimulationModelID, err := common.GetResponseID(resp) + assert.NoError(t, err) - // TODO add testing for other return codes + // Get the newSimulationModel + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/models/%v", newSimulationModelID), "GET", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare GET's response with the newSimulationModel + err = common.CompareResponse(resp, common.KeyModels{"model": newSimulationModel}) + assert.NoError(t, err) + + // try to POST a malformed simulation model + // Required fields are missing + malformedNewSimulationModel := SimulationModelRequest{ + Name: "ThisIsAMalformedRequest", + } + // this should NOT work and return a unprocessable entity 442 status code + code, resp, err = common.NewTestEndpoint(router, token, + "/api/models", "POST", common.KeyModels{"model": malformedNewSimulationModel}) + assert.NoError(t, err) + assert.Equalf(t, 422, code, "Response body: \n%v\n", resp) } + +func TestUpdateSimulationModel(t *testing.T) { + + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // prepare the content of the DB for testing + // by adding a scenario and a simulator to the DB + // using the respective endpoints of the API + scenarioID, simulatorID := addScenarioAndSimulator() + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST models/ $newSimulationModel + newSimulationModel := SimulationModelRequest{ + Name: common.SimulationModelA.Name, + ScenarioID: scenarioID, + SimulatorID: simulatorID, + StartParameters: common.SimulationModelA.StartParameters, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/models", "POST", common.KeyModels{"model": newSimulationModel}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newSimulationModel's ID from the response + newSimulationModelID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + updatedSimulationModel := SimulationModelRequest{ + Name: common.SimulationModelB.Name, + StartParameters: common.SimulationModelB.StartParameters, + } + + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/models/%v", newSimulationModelID), "PUT", common.KeyModels{"model": updatedSimulationModel}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare PUT's response with the updatedSimulationModel + err = common.CompareResponse(resp, common.KeyModels{"model": updatedSimulationModel}) + assert.NoError(t, err) + + // Get the updatedSimulationModel + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/models/%v", newSimulationModelID), "GET", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare GET's response with the updatedSimulationModel + err = common.CompareResponse(resp, common.KeyModels{"model": updatedSimulationModel}) + assert.NoError(t, err) + + // try to update a simulation model that does not exist (should return not found 404 status code) + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/models/%v", newSimulationModelID+1), "PUT", common.KeyModels{"scenario": updatedSimulationModel}) + assert.NoError(t, err) + assert.Equalf(t, 404, code, "Response body: \n%v\n", resp) +} + +func TestDeleteSimulationModel(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // prepare the content of the DB for testing + // by adding a scenario and a simulator to the DB + // using the respective endpoints of the API + scenarioID, simulatorID := addScenarioAndSimulator() + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST models/ $newSimulationModel + newSimulationModel := SimulationModelRequest{ + Name: common.SimulationModelA.Name, + ScenarioID: scenarioID, + SimulatorID: simulatorID, + StartParameters: common.SimulationModelA.StartParameters, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/models", "POST", common.KeyModels{"model": newSimulationModel}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newSimulationModel's ID from the response + newSimulationModelID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // Count the number of all the simulation models returned for scenario + initialNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/models?scenarioID=%v", scenarioID), "GET", nil) + assert.NoError(t, err) + + // Delete the added newSimulationModel + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/models/%v", newSimulationModelID), "DELETE", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare DELETE's response with the newSimulationModel + err = common.CompareResponse(resp, common.KeyModels{"model": newSimulationModel}) + assert.NoError(t, err) + + // Again count the number of all the simulation models returned + finalNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/models?scenarioID=%v", scenarioID), "GET", nil) + assert.NoError(t, err) + + assert.Equal(t, initialNumber-1, finalNumber) +} + +func TestGetAllSimulationModelsOfScenario(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // prepare the content of the DB for testing + // by adding a scenario and a simulator to the DB + // using the respective endpoints of the API + scenarioID, simulatorID := addScenarioAndSimulator() + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST models/ $newSimulationModel + newSimulationModel := SimulationModelRequest{ + Name: common.SimulationModelA.Name, + ScenarioID: scenarioID, + SimulatorID: simulatorID, + StartParameters: common.SimulationModelA.StartParameters, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/models", "POST", common.KeyModels{"model": newSimulationModel}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Count the number of all the simulation models returned for scenario + NumberOfSimulationModels, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/models?scenarioID=%v", scenarioID), "GET", nil) + assert.NoError(t, err) + + assert.Equal(t, 1, NumberOfSimulationModels) + +} + +//// Test /models endpoints +//func TestSimulationModelEndpoints(t *testing.T) { +// +// var token string +// +// var myModels = []common.SimulationModelResponse{common.SimulationModelA_response, common.SimulationModelB_response} +// var msgModels = common.ResponseMsgSimulationModels{SimulationModels: myModels} +// var msgModel = common.ResponseMsgSimulationModel{SimulationModel: common.SimulationModelC_response} +// var msgModelupdated = common.ResponseMsgSimulationModel{SimulationModel: common.SimulationModelCUpdated_response} +// +// db := common.DummyInitDB() +// defer db.Close() +// common.DummyPopulateDB(db) +// +// 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)) +// +// RegisterSimulationModelEndpoints(api.Group("/models")) +// +// credjson, _ := json.Marshal(common.CredUser) +// msgOKjson, _ := json.Marshal(common.MsgOK) +// msgModelsjson, _ := json.Marshal(msgModels) +// msgModeljson, _ := json.Marshal(msgModel) +// msgModelupdatedjson, _ := json.Marshal(msgModelupdated) +// +// token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200) +// +// // test GET models +// common.TestEndpoint(t, router, token, "/api/models?scenarioID=1", "GET", nil, 200, msgModelsjson) +// +// // test POST models +// common.TestEndpoint(t, router, token, "/api/models", "POST", msgModeljson, 200, msgOKjson) +// +// // test GET models/:ModelID to check if previous POST worked correctly +// common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, msgModeljson) +// +// // test PUT models/:ModelID +// common.TestEndpoint(t, router, token, "/api/models/3", "PUT", msgModelupdatedjson, 200, msgOKjson) +// common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, msgModelupdatedjson) +// +// // test DELETE models/:ModelID +// common.TestEndpoint(t, router, token, "/api/models/3", "DELETE", nil, 200, msgOKjson) +// common.TestEndpoint(t, router, token, "/api/models?scenarioID=1", "GET", nil, 200, msgModelsjson) +// +// // TODO add testing for other return codes +// +//} diff --git a/routes/simulationmodel/simulationmodel_validators.go b/routes/simulationmodel/simulationmodel_validators.go new file mode 100644 index 0000000..c800ccd --- /dev/null +++ b/routes/simulationmodel/simulationmodel_validators.go @@ -0,0 +1,80 @@ +package simulationmodel + +import ( + "encoding/json" + "github.com/jinzhu/gorm/dialects/postgres" + "github.com/nsf/jsondiff" + "gopkg.in/go-playground/validator.v9" +) + +var validate *validator.Validate + +type validNewSimulationModel struct { + Name string `form:"Name" validate:"required"` + ScenarioID uint `form:"ScenarioID" validate:"required"` + SimulatorID uint `form:"SimulatorID" validate:"required"` + StartParameters postgres.Jsonb `form:"StartParameters" validate:"required"` +} + +type validUpdatedSimulationModel struct { + Name string `form:"Name" validate:"omitempty"` + SimulatorID uint `form:"SimulatorID" validate:"omitempty"` + StartParameters postgres.Jsonb `form:"StartParameters" validate:"omitempty"` +} + +type addSimulationModelRequest struct { + validNewSimulationModel `json:"model"` +} + +type updateSimulationModelRequest struct { + validUpdatedSimulationModel `json:"model"` +} + +func (r *addSimulationModelRequest) validate() error { + validate = validator.New() + errs := validate.Struct(r) + return errs +} + +func (r *validUpdatedSimulationModel) validate() error { + validate = validator.New() + errs := validate.Struct(r) + return errs +} + +func (r *addSimulationModelRequest) createSimulationModel() SimulationModel { + var s SimulationModel + + s.Name = r.Name + s.ScenarioID = r.ScenarioID + s.SimulatorID = r.SimulatorID + s.StartParameters = r.StartParameters + + return s +} + +func (r *updateSimulationModelRequest) updatedSimulationModel(oldSimulationModel SimulationModel) (SimulationModel, error) { + // Use the old SimulationModel as a basis for the updated Simulation model + s := oldSimulationModel + + if r.Name != "" { + s.Name = r.Name + } + + if r.SimulatorID != 0 { + s.SimulatorID = r.SimulatorID + } + + // only update Params if not empty + var emptyJson postgres.Jsonb + // Serialize empty json and params + emptyJson_ser, _ := json.Marshal(emptyJson) + startParams_ser, _ := json.Marshal(r.StartParameters) + opts := jsondiff.DefaultConsoleOptions() + diff, _ := jsondiff.Compare(emptyJson_ser, startParams_ser, &opts) + if diff.String() != "FullMatch" { + s.StartParameters = r.StartParameters + } + + return s, nil +}