From 2772bde5ee0d0f14e3836034b7713cfdf06c558c Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 4 Sep 2019 16:31:20 +0200 Subject: [PATCH] - Modify scenario testing - Add scenario validators - Clean up responses and serializes with respect to scenario - Fix error handling and responses of scenario endpoints --- common/requests.go | 9 +- common/responses.go | 15 - common/serializers.go | 31 -- common/testdata.go | 6 - routes/scenario/scenario_endpoints.go | 114 +++-- routes/scenario/scenario_methods.go | 26 +- routes/scenario/scenario_test.go | 559 +++++++++++++++++++++---- routes/scenario/scenario_validators.go | 65 +++ 8 files changed, 634 insertions(+), 191 deletions(-) create mode 100644 routes/scenario/scenario_validators.go diff --git a/common/requests.go b/common/requests.go index 5211d9c..eb59732 100644 --- a/common/requests.go +++ b/common/requests.go @@ -5,11 +5,10 @@ import "github.com/jinzhu/gorm/dialects/postgres" type KeyModels map[string]interface{} type Request struct { - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Mail string `json:"mail,omitempty"` - Role string `json:"role,omitempty"` - + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Mail string `json:"mail,omitempty"` + Role string `json:"role,omitempty"` Name string `json:"name,omitempty"` Running bool `json:"running,omitempty"` StartParameters postgres.Jsonb `json:"startParameters,omitempty"` diff --git a/common/responses.go b/common/responses.go index c3d242c..a3c6837 100644 --- a/common/responses.go +++ b/common/responses.go @@ -2,13 +2,6 @@ package common import "github.com/jinzhu/gorm/dialects/postgres" -type ScenarioResponse struct { - Name string `json:"name"` - ID uint `json:"id"` - Running bool `json:"running"` - StartParameters postgres.Jsonb `json:"startParameters"` -} - type SimulationModelResponse struct { ID uint `json:"id"` Name string `json:"name"` @@ -80,14 +73,6 @@ type ResponseMsg struct { Message string `json:"message"` } -type ResponseMsgScenarios struct { - Scenarios []ScenarioResponse `json:"scenarios"` -} - -type ResponseMsgScenario struct { - Scenario ScenarioResponse `json:"scenario"` -} - type ResponseMsgSimulationModels struct { SimulationModels []SimulationModelResponse `json:"models"` } diff --git a/common/serializers.go b/common/serializers.go index e9eb56f..5e66fee 100644 --- a/common/serializers.go +++ b/common/serializers.go @@ -4,37 +4,6 @@ import ( "github.com/gin-gonic/gin" ) -// Scenario/s Serializers - -type ScenariosSerializer struct { - Ctx *gin.Context - Scenarios []Scenario -} - -func (self *ScenariosSerializer) Response() []ScenarioResponse { - response := []ScenarioResponse{} - for _, so := range self.Scenarios { - serializer := ScenarioSerializer{self.Ctx, so} - response = append(response, serializer.Response()) - } - return response -} - -type ScenarioSerializer struct { - Ctx *gin.Context - Scenario -} - -func (self *ScenarioSerializer) Response() ScenarioResponse { - response := ScenarioResponse{ - Name: self.Name, - ID: self.ID, - Running: self.Running, - StartParameters: self.StartParameters, - } - return response -} - // Model/s Serializers type SimulationModelsSerializer struct { diff --git a/common/testdata.go b/common/testdata.go index caf4cb1..979c364 100644 --- a/common/testdata.go +++ b/common/testdata.go @@ -154,13 +154,7 @@ var startParametersB = json.RawMessage(`{"parameter1" : "testValue1B", "paramete var startParametersC = json.RawMessage(`{"parameter1" : "testValue1C", "parameter2" : "testValue2C", "parameter3" : 44}`) var ScenarioA = Scenario{Name: "Scenario_A", Running: true, StartParameters: postgres.Jsonb{startParametersA}} -var ScenarioA_response = ScenarioResponse{ID: 1, Name: ScenarioA.Name, Running: ScenarioA.Running, StartParameters: ScenarioA.StartParameters} var ScenarioB = Scenario{Name: "Scenario_B", Running: false, StartParameters: postgres.Jsonb{startParametersB}} -var ScenarioB_response = ScenarioResponse{ID: 2, Name: ScenarioB.Name, Running: ScenarioB.Running, StartParameters: ScenarioB.StartParameters} -var ScenarioC = Scenario{Name: "Scenario_C", Running: false, StartParameters: postgres.Jsonb{startParametersC}} -var ScenarioC_response = ScenarioResponse{ID: 3, Name: ScenarioC.Name, Running: ScenarioC.Running, StartParameters: ScenarioC.StartParameters} -var ScenarioCUpdated = Scenario{Name: "Scenario_Cupdated", Running: true, StartParameters: postgres.Jsonb{startParametersC}} -var ScenarioCUpdated_response = ScenarioResponse{ID: 3, Name: ScenarioCUpdated.Name, Running: ScenarioCUpdated.Running, StartParameters: ScenarioCUpdated.StartParameters} // Simulation Models diff --git a/routes/scenario/scenario_endpoints.go b/routes/scenario/scenario_endpoints.go index 08b07fe..330296e 100644 --- a/routes/scenario/scenario_endpoints.go +++ b/routes/scenario/scenario_endpoints.go @@ -1,6 +1,7 @@ package scenario import ( + "fmt" "net/http" "github.com/gin-gonic/gin" @@ -64,9 +65,8 @@ func getScenarios(c *gin.Context) { } } // TODO return list of simulationModelIDs, dashboardIDs and userIDs per scenario - serializer := common.ScenariosSerializer{c, scenarios} c.JSON(http.StatusOK, gin.H{ - "scenarios": serializer.Response(), + "scenarios": scenarios, }) } @@ -98,35 +98,44 @@ func addScenario(c *gin.Context) { return } - var newScenarioData common.ResponseMsgScenario - err = c.BindJSON(&newScenarioData) - if err != nil { - errormsg := "Bad request. Error binding form data to JSON: " + err.Error() + var req addScenarioRequest + if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": fmt.Sprintf("%v", err), }) return } - var newScenario Scenario - newScenario.ID = newScenarioData.Scenario.ID - newScenario.StartParameters = newScenarioData.Scenario.StartParameters - newScenario.Running = newScenarioData.Scenario.Running - newScenario.Name = newScenarioData.Scenario.Name + // Validate the request + if err = req.validate(); err != nil { + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) + return + } - // save new scenario to DB + // Create the new scenario from the request + newScenario := req.createScenario() + + // Save the new scenario in the DB err = newScenario.save() - if common.ProvideErrorResponse(c, err) { + if err != nil { + common.ProvideErrorResponse(c, err) return } // add user to new scenario err = newScenario.addUser(&(u.User)) - if common.ProvideErrorResponse(c, err) == false { - c.JSON(http.StatusOK, gin.H{ - "message": "OK.", - }) + if err != nil { + common.ProvideErrorResponse(c, err) + return } + + c.JSON(http.StatusOK, gin.H{ + "scenario": newScenario.Scenario, + }) } // updateScenario godoc @@ -145,27 +154,50 @@ func addScenario(c *gin.Context) { // @Router /scenarios/{scenarioID} [put] func updateScenario(c *gin.Context) { - ok, so := CheckPermissions(c, common.Update, "path", -1) + ok, oldScenario := CheckPermissions(c, common.Update, "path", -1) if !ok { return } - var modifiedScenarioData common.ResponseMsgScenario - err := c.BindJSON(&modifiedScenarioData) - if err != nil { - errormsg := "Bad request. Error binding form data to JSON: " + err.Error() + // Bind the (context) with the updateScenarioRequest struct + var req updateScenarioRequest + if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": fmt.Sprintf("%v", err), }) return } - err = so.update(modifiedScenarioData.Scenario) - if common.ProvideErrorResponse(c, err) == false { - c.JSON(http.StatusOK, gin.H{ - "message": "OK.", + // Validate the request based on struct updateScenarioRequest json tags + if err := req.validate(); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), }) + return } + + // Create the updatedScenario from oldScenario + updatedScenario, err := req.updatedScenario(oldScenario) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) + return + } + + // Finally update the scenario + err = oldScenario.update(updatedScenario) + if err != nil { + common.ProvideErrorResponse(c, err) + return + } + + c.JSON(http.StatusOK, gin.H{ + "scenario": updatedScenario.Scenario, + }) } // getScenario godoc @@ -188,9 +220,8 @@ func getScenario(c *gin.Context) { } // TODO return list of simulationModelIDs, dashboardIDs and userIDs per scenario - serializer := common.ScenarioSerializer{c, so.Scenario} c.JSON(http.StatusOK, gin.H{ - "scenario": serializer.Response(), + "scenario": so.Scenario, }) } @@ -214,11 +245,14 @@ func deleteScenario(c *gin.Context) { } err := so.delete() - if common.ProvideErrorResponse(c, err) == false { - c.JSON(http.StatusOK, gin.H{ - "message": "OK.", - }) + if err != nil { + common.ProvideErrorResponse(c, err) + return } + + c.JSON(http.StatusOK, gin.H{ + "scenario": so.Scenario, + }) } // getUsersOfScenario godoc @@ -283,7 +317,7 @@ func addUserToScenario(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{ - "message": "OK.", + "user": u.User, }) } @@ -309,12 +343,18 @@ func deleteUserFromScenario(c *gin.Context) { username := c.Request.URL.Query().Get("username") - err := so.deleteUser(username) + var u user.User + err := u.ByUsername(username) + if common.ProvideErrorResponse(c, err) { + return + } + + err = so.deleteUser(username) if common.ProvideErrorResponse(c, err) { return } c.JSON(http.StatusOK, gin.H{ - "message": "OK.", + "user": u.User, }) } diff --git a/routes/scenario/scenario_methods.go b/routes/scenario/scenario_methods.go index 650c691..b2f3c1a 100644 --- a/routes/scenario/scenario_methods.go +++ b/routes/scenario/scenario_methods.go @@ -2,9 +2,9 @@ package scenario import ( "fmt" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user" + "github.com/jinzhu/gorm" ) type Scenario struct { @@ -15,7 +15,7 @@ func (s *Scenario) ByID(id uint) error { db := common.GetDB() err := db.Find(s, id).Error if err != nil { - return fmt.Errorf("scenario with id=%v does not exist", id) + return err } return nil } @@ -33,9 +33,15 @@ func (s *Scenario) save() error { return err } -func (s *Scenario) update(modifiedScenario common.ScenarioResponse) error { +func (s *Scenario) update(updatedScenario Scenario) error { + + // TODO: if the field is empty member shouldn't be updated + s.Name = updatedScenario.Name + s.Running = updatedScenario.Running + s.StartParameters = updatedScenario.StartParameters + db := common.GetDB() - err := db.Model(s).Update(modifiedScenario).Error + err := db.Model(s).Update(updatedScenario).Error return err } @@ -69,7 +75,17 @@ func (s *Scenario) deleteUser(username string) error { return err } } else { - return fmt.Errorf("cannot delete last user from scenario without deleting scenario itself, doing nothing") + // There is only one associated user + var remainingUser user.User + err = db.Model(s).Related(&remainingUser, "Users").Error + if remainingUser.Username == username { + // if the remaining user is the one to be deleted + return fmt.Errorf("cannot delete last user from scenario without deleting scenario itself, doing nothing") + } else { + // the remaining user is NOT the one to be deleted + // that means the user to be deleted is not associated with the scenario + return gorm.ErrRecordNotFound + } } return nil diff --git a/routes/scenario/scenario_test.go b/routes/scenario/scenario_test.go index 3839138..c77d46b 100644 --- a/routes/scenario/scenario_test.go +++ b/routes/scenario/scenario_test.go @@ -1,115 +1,490 @@ package scenario import ( - "encoding/json" "fmt" "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" - "net/http" - "net/http/httptest" + "os" "testing" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user" ) -// Test /scenarios endpoints -func TestScenarioEndpoints(t *testing.T) { +var router *gin.Engine +var db *gorm.DB - var myScenarios = []common.ScenarioResponse{common.ScenarioA_response, common.ScenarioB_response} - var msgScenarios = common.ResponseMsgScenarios{Scenarios: myScenarios} - var msgScenario = common.ResponseMsgScenario{Scenario: common.ScenarioC_response} - var msgScenarioUpdated = common.ResponseMsgScenario{Scenario: common.ScenarioCUpdated_response} +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)) - RegisterScenarioEndpoints(api.Group("/scenarios")) - credjson, _ := json.Marshal(common.CredUser) - - token := common.AuthenticateForTest(t, router, "/api/authenticate", - "POST", credjson, 200) - - // test GET scenarios/ - err := common.NewTestEndpoint(router, token, "/api/scenarios", - "GET", nil, 200, msgScenarios) - assert.NoError(t, err) - - // test POST scenarios/ - err = common.NewTestEndpoint(router, token, "/api/scenarios", - "POST", msgScenario, 200, common.MsgOK) - assert.NoError(t, err) - - // test GET scenarios/:ScenarioID - err = common.NewTestEndpoint(router, token, "/api/scenarios/3", - "GET", nil, 200, msgScenario) - assert.NoError(t, err) - - // test PUT scenarios/:ScenarioID - err = common.NewTestEndpoint(router, token, "/api/scenarios/3", - "PUT", msgScenarioUpdated, 200, common.MsgOK) - assert.NoError(t, err) - err = common.NewTestEndpoint(router, token, "/api/scenarios/3", - "GET", nil, 200, msgScenarioUpdated) - assert.NoError(t, err) - - // test DELETE scenarios/:ScenarioID - err = common.NewTestEndpoint(router, token, "/api/scenarios/3", - "DELETE", nil, 200, common.MsgOK) - assert.NoError(t, err) - err = common.NewTestEndpoint(router, token, "/api/scenarios", "GET", - nil, 200, msgScenarios) - assert.NoError(t, err) - - // test GET scenarios/:ScenarioID/users - err = common.NewTestEndpoint(router, token, - "/api/scenarios/1/users", "GET", nil, - 200, common.KeyModels{"users": []common.User{common.UserA, common.UserB}}) - assert.NoError(t, err) - - // test DELETE scenarios/:ScenarioID/user - err = common.NewTestEndpoint(router, token, - "/api/scenarios/1/user?username=User_B", "DELETE", nil, 200, common.MsgOK) - assert.NoError(t, err) - err = common.NewTestEndpoint(router, token, - "/api/scenarios/1/users", "GET", nil, - 200, common.KeyModels{"users": []common.User{common.UserA}}) - assert.NoError(t, err) - - // test PUT scenarios/:ScenarioID/user - err = common.NewTestEndpoint(router, token, - "/api/scenarios/1/user?username=User_B", "PUT", nil, 200, common.MsgOK) - assert.NoError(t, err) - err = common.NewTestEndpoint(router, token, - "/api/scenarios/1/users", "GET", nil, - 200, common.KeyModels{"users": []common.User{common.UserA, common.UserB}}) - assert.NoError(t, err) - - // test DELETE scenarios/:ScenarioID/user for logged in user User_A - err = common.NewTestEndpoint(router, token, - "/api/scenarios/1/user?username=User_A", "DELETE", nil, 200, common.MsgOK) - assert.NoError(t, err) - - // test if deletion of user from scenario has worked - w2 := httptest.NewRecorder() - req2, _ := http.NewRequest("GET", "/api/scenarios/1/users", nil) - req2.Header.Add("Authorization", "Bearer "+token) - router.ServeHTTP(w2, req2) - - assert.Equal(t, 422, w2.Code) - fmt.Println(w2.Body.String()) - assert.Equal(t, "\"Access denied (for scenario ID).\"", w2.Body.String()) - - // TODO add tests for other return codes + os.Exit(m.Run()) +} + +func TestAddScenario(t *testing.T) { + + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST scenarios/ $newScenario + newScenario := common.Request{ + Name: common.ScenarioA.Name, + Running: common.ScenarioA.Running, + StartParameters: common.ScenarioA.StartParameters, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare POST's response with the newScenario + err = common.CompareResponse(resp, common.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + + // Read newScenario's ID from the response + newScenarioID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // Get the newScenario + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v", newScenarioID), "GET", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare GET's response with the newScenario + err = common.CompareResponse(resp, common.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + + // try to POST a malformed scenario + // Required field StartParameters is missing + malformedNewScenario := common.Request{ + Username: "thisisnotneeded", + } + // this should NOT work and return a unprocessable entity 442 status code + code, resp, err = common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": malformedNewScenario}) + assert.NoError(t, err) + assert.Equalf(t, 422, code, "Response body: \n%v\n", resp) +} + +func TestUpdateScenario(t *testing.T) { + + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST scenarios/ $newScenario + newScenario := common.Request{ + Name: common.ScenarioA.Name, + Running: common.ScenarioA.Running, + StartParameters: common.ScenarioA.StartParameters, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare POST's response with the newScenario + err = common.CompareResponse(resp, common.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + + // Read newScenario's ID from the response + newScenarioID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + updatedScenario := common.Request{ + Name: "Updated name", + Running: !common.ScenarioA.Running, + StartParameters: common.ScenarioA.StartParameters, + } + + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v", newScenarioID), "PUT", common.KeyModels{"scenario": updatedScenario}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare PUT's response with the updatedScenario + err = common.CompareResponse(resp, common.KeyModels{"scenario": updatedScenario}) + assert.NoError(t, err) + + // Get the updatedScenario + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v", newScenarioID), "GET", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare GET's response with the newScenario + err = common.CompareResponse(resp, common.KeyModels{"scenario": updatedScenario}) + assert.NoError(t, err) + + // try to update a scenario that does not exist (should return not found 404 status code) + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v", newScenarioID+1), "PUT", common.KeyModels{"scenario": updatedScenario}) + assert.NoError(t, err) + assert.Equalf(t, 404, code, "Response body: \n%v\n", resp) + +} + +func TestGetAllScenariosAsAdmin(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 scenarios response for admin + initialNumber, err := common.LengthOfResponse(router, token, + "/api/scenarios", "GET", nil) + assert.NoError(t, err) + + // authenticate as normal userB + token, err = common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserBCredentials) + assert.NoError(t, err) + + // test POST scenarios/ $newScenarioB + newScenarioB := common.Request{ + Name: common.ScenarioB.Name, + Running: common.ScenarioB.Running, + StartParameters: common.ScenarioB.StartParameters, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": newScenarioB}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // authenticate as normal userA + token, err = common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST scenarios/ $newScenarioA + newScenarioA := common.Request{ + Name: common.ScenarioA.Name, + Running: common.ScenarioA.Running, + StartParameters: common.ScenarioA.StartParameters, + } + code, resp, err = common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": newScenarioA}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // authenticate as admin + token, err = common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.AdminCredentials) + assert.NoError(t, err) + + // get the length of the GET all scenarios response again + finalNumber, err := common.LengthOfResponse(router, token, + "/api/scenarios", "GET", nil) + assert.NoError(t, err) + + assert.Equal(t, finalNumber, initialNumber+2) +} + +func TestGetAllScenariosAsUser(t *testing.T) { + + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as normal userB + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserBCredentials) + assert.NoError(t, err) + + // get the length of the GET all scenarios response for userB + initialNumber, err := common.LengthOfResponse(router, token, + "/api/scenarios", "GET", nil) + assert.NoError(t, err) + + // test POST scenarios/ $newScenarioB + newScenarioB := common.Request{ + Name: common.ScenarioB.Name, + Running: common.ScenarioB.Running, + StartParameters: common.ScenarioB.StartParameters, + } + + code, resp, err := common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": newScenarioB}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // authenticate as normal userA + token, err = common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST scenarios/ $newScenarioA + newScenarioA := common.Request{ + Name: common.ScenarioA.Name, + Running: common.ScenarioA.Running, + StartParameters: common.ScenarioA.StartParameters, + } + code, resp, err = common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": newScenarioA}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // authenticate as normal userB + token, err = common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserBCredentials) + assert.NoError(t, err) + + // get the length of the GET all scenarios response again + finalNumber, err := common.LengthOfResponse(router, token, + "/api/scenarios", "GET", nil) + assert.NoError(t, err) + + assert.Equal(t, finalNumber, initialNumber+1) +} + +func TestDeleteScenario(t *testing.T) { + + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST scenarios/ $newScenario + newScenario := common.Request{ + Name: common.ScenarioA.Name, + Running: common.ScenarioA.Running, + StartParameters: common.ScenarioA.StartParameters, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newScenario's ID from the response + newScenarioID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // Count the number of all the scenarios returned for userA + initialNumber, err := common.LengthOfResponse(router, token, + "/api/scenarios", "GET", nil) + assert.NoError(t, err) + + // Delete the added newScenario + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v", newScenarioID), "DELETE", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare DELETE's response with the newScenario + err = common.CompareResponse(resp, common.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + + // Again count the number of all the users returned + finalNumber, err := common.LengthOfResponse(router, token, + "/api/scenarios", "GET", nil) + assert.NoError(t, err) + + assert.Equal(t, finalNumber, initialNumber-1) +} + +func TestAddUserToScenario(t *testing.T) { + + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST scenarios/ $newScenario + newScenario := common.Request{ + Name: common.ScenarioA.Name, + Running: common.ScenarioA.Running, + StartParameters: common.ScenarioA.StartParameters, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newScenario's ID from the response + newScenarioID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // Count the number of all the users returned for newScenario + initialNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/scenarios/%v/users", newScenarioID), "GET", nil) + assert.NoError(t, err) + assert.Equal(t, initialNumber, 1) + + // add userB to newScenario + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v/user?username=User_B", newScenarioID), "PUT", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare resp to userB + err = common.CompareResponse(resp, common.KeyModels{"user": common.UserB}) + assert.NoError(t, err) + + // Count AGAIN the number of all the users returned for newScenario + finalNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/scenarios/%v/users", newScenarioID), "GET", nil) + assert.NoError(t, err) + assert.Equal(t, finalNumber, initialNumber+1) + + // try to add a non-existing user to newScenario, should return a not found 404 + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil) + assert.NoError(t, err) + assert.Equalf(t, 404, code, "Response body: \n%v\n", resp) +} + +func TestGetAllUsersOfScenario(t *testing.T) { + + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST scenarios/ $newScenario + newScenario := common.Request{ + Name: common.ScenarioA.Name, + Running: common.ScenarioA.Running, + StartParameters: common.ScenarioA.StartParameters, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newScenario's ID from the response + newScenarioID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // Count the number of all the users returned for newScenario + initialNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/scenarios/%v/users", newScenarioID), "GET", nil) + assert.NoError(t, err) + assert.Equal(t, initialNumber, 1) + + // add userB to newScenario + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v/user?username=User_B", newScenarioID), "PUT", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Count AGAIN the number of all the users returned for newScenario + finalNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/scenarios/%v/users", newScenarioID), "GET", nil) + assert.NoError(t, err) + assert.Equal(t, finalNumber, initialNumber+1) +} + +func TestRemoveUserFromScenario(t *testing.T) { + + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST scenarios/ $newScenario + newScenario := common.Request{ + Name: common.ScenarioA.Name, + Running: common.ScenarioA.Running, + StartParameters: common.ScenarioA.StartParameters, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/scenarios", "POST", common.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newScenario's ID from the response + newScenarioID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // add userB to newScenario + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v/user?username=User_B", newScenarioID), "PUT", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Count the number of all the users returned for newScenario + initialNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/scenarios/%v/users", newScenarioID), "GET", nil) + assert.NoError(t, err) + assert.Equal(t, 2, initialNumber) + + // remove userB from newScenario + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v/user?username=User_B", newScenarioID), "DELETE", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare DELETE's response with UserB + err = common.CompareResponse(resp, common.KeyModels{"user": common.UserB}) + assert.NoError(t, err) + + // Count AGAIN the number of all the users returned for newScenario + finalNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/scenarios/%v/users", newScenarioID), "GET", nil) + assert.NoError(t, err) + assert.Equal(t, initialNumber-1, finalNumber) + + // Try to remove userA from new scenario + // This should fail since User_A is the last user of newScenario + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v/user?username=User_A", newScenarioID), "DELETE", nil) + assert.NoError(t, err) + assert.Equalf(t, 500, code, "Response body: \n%v\n", resp) + + // Try to remove a user that does not exist in DB + // This should fail with not found 404 status code + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v/user?username=User_C", newScenarioID), "DELETE", nil) + assert.NoError(t, err) + assert.Equalf(t, 404, code, "Response body: \n%v\n", resp) + + // Try to remove an admin user that is not explicitly a user of the scenario + // This should fail with not found 404 status code + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v/user?username=User_0", newScenarioID), "DELETE", nil) + assert.NoError(t, err) + assert.Equalf(t, 404, code, "Response body: \n%v\n", resp) + } diff --git a/routes/scenario/scenario_validators.go b/routes/scenario/scenario_validators.go new file mode 100644 index 0000000..ba56b2e --- /dev/null +++ b/routes/scenario/scenario_validators.go @@ -0,0 +1,65 @@ +package scenario + +import ( + "github.com/jinzhu/gorm/dialects/postgres" + "gopkg.in/go-playground/validator.v9" +) + +var validate *validator.Validate + +type validNewScenario struct { + Name string `form:"Name" validate:"required"` + Running bool `form:"Running" validate:"omitempty"` + StartParameters postgres.Jsonb `form:"StartParameters" validate:"required"` +} + +type validUpdatedScenario struct { + Name string `form:"Name" validate:"omitempty"` + Running bool `form:"Running" validate:"omitempty"` + StartParameters postgres.Jsonb `form:"StartParameters" validate:"omitempty"` +} + +type addScenarioRequest struct { + validNewScenario `json:"scenario"` +} + +type updateScenarioRequest struct { + validUpdatedScenario `json:"scenario"` +} + +func (r *addScenarioRequest) validate() error { + validate = validator.New() + errs := validate.Struct(r) + return errs +} + +func (r *validUpdatedScenario) validate() error { + validate = validator.New() + errs := validate.Struct(r) + return errs +} + +func (r *addScenarioRequest) createScenario() Scenario { + var s Scenario + + s.Name = r.Name + s.Running = r.Running + s.StartParameters = r.StartParameters + + return s +} + +func (r *updateScenarioRequest) updatedScenario(oldScenario Scenario) (Scenario, error) { + // Use the old Scenario as a basis for the updated Scenario `s` + s := oldScenario + + if r.Name != "" { + s.Name = r.Name + } + + s.Running = r.Running + // TODO check for empty start parameters? + s.StartParameters = r.StartParameters + + return s, nil +}