From 7544c863de75e3c3bc08f308a2b7528daa416afd Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 6 Sep 2019 16:01:59 +0200 Subject: [PATCH] - revise testing of signal endpoints - revise documentation for swaggo - clean up testdata, serializers and responses - add validators for signal endpoints - revise signal endpoint implementations --- common/responses.go | 16 -- common/serializers.go | 32 --- common/testdata.go | 56 ---- doc/api/responses.go | 8 + routes/signal/signal_endpoints.go | 154 ++++++----- routes/signal/signal_methods.go | 6 +- routes/signal/signal_middleware.go | 9 +- routes/signal/signal_test.go | 412 +++++++++++++++++++++++++---- routes/signal/signal_validators.go | 73 +++++ 9 files changed, 548 insertions(+), 218 deletions(-) create mode 100644 routes/signal/signal_validators.go diff --git a/common/responses.go b/common/responses.go index 0f3e284..8ced76a 100644 --- a/common/responses.go +++ b/common/responses.go @@ -12,28 +12,12 @@ type FileResponse struct { SimulationModelID uint `json:"simulationModelID"` } -type SignalResponse struct { - Name string `json:"name"` - Unit string `json:"unit"` - Index uint `json:"index"` - Direction string `json:"direction"` - SimulationModelID uint `json:"simulationModelID"` -} - // Response messages type ResponseMsg struct { Message string `json:"message"` } -type ResponseMsgSignals struct { - Signals []SignalResponse `json:"signals"` -} - -type ResponseMsgSignal struct { - Signal SignalResponse `json:"signal"` -} - type ResponseMsgFiles struct { Files []FileResponse `json:"files"` } diff --git a/common/serializers.go b/common/serializers.go index e143a5b..d3b1344 100644 --- a/common/serializers.go +++ b/common/serializers.go @@ -40,35 +40,3 @@ func (self *FileSerializerNoAssoc) Response() FileResponse { } return response } - -// Signal/s Serializers -type SignalsSerializer struct { - Ctx *gin.Context - Signals []Signal -} - -func (self *SignalsSerializer) Response() []SignalResponse { - response := []SignalResponse{} - for _, s := range self.Signals { - serializer := SignalSerializer{self.Ctx, s} - response = append(response, serializer.Response()) - } - return response - -} - -type SignalSerializer struct { - Ctx *gin.Context - Signal -} - -func (self *SignalSerializer) Response() SignalResponse { - response := SignalResponse{ - Name: self.Name, - Unit: self.Unit, - Direction: self.Direction, - SimulationModelID: self.SimulationModelID, - Index: self.Index, - } - return response -} diff --git a/common/testdata.go b/common/testdata.go index 4f93ebe..40ce8b7 100644 --- a/common/testdata.go +++ b/common/testdata.go @@ -117,13 +117,6 @@ var OutSignalA = Signal{ Unit: "V", } -var OutSignalA_response = SignalResponse{ - Name: OutSignalA.Name, - Direction: OutSignalA.Direction, - Index: OutSignalA.Index, - Unit: OutSignalA.Unit, -} - var OutSignalB = Signal{ Name: "outSignal_B", Direction: "out", @@ -131,13 +124,6 @@ var OutSignalB = Signal{ Unit: "V", } -var OutSignalB_response = SignalResponse{ - Name: OutSignalB.Name, - Direction: OutSignalB.Direction, - Index: OutSignalB.Index, - Unit: OutSignalB.Unit, -} - var InSignalA = Signal{ Name: "inSignal_A", Direction: "in", @@ -145,13 +131,6 @@ var InSignalA = Signal{ Unit: "A", } -var InSignalA_response = SignalResponse{ - Name: InSignalA.Name, - Direction: InSignalA.Direction, - Index: InSignalA.Index, - Unit: InSignalA.Unit, -} - var InSignalB = Signal{ Name: "inSignal_B", Direction: "in", @@ -159,41 +138,6 @@ var InSignalB = Signal{ Unit: "A", } -var InSignalB_response = SignalResponse{ - Name: InSignalB.Name, - Direction: InSignalB.Direction, - Index: InSignalB.Index, - Unit: InSignalB.Unit, -} - -var InSignalC = Signal{ - Name: "inSignal_C", - Direction: "in", - Index: 2, - Unit: "A", -} - -var InSignalC_response = SignalResponse{ - Name: InSignalC.Name, - Direction: InSignalC.Direction, - Index: InSignalC.Index, - Unit: InSignalC.Unit, -} - -var InSignalCUpdated = Signal{ - Name: "inSignalupdated_C", - Direction: InSignalC.Direction, - Index: InSignalC.Index, - Unit: "Ohm", -} - -var InSignalCUpdated_response = SignalResponse{ - Name: InSignalCUpdated.Name, - Direction: InSignalCUpdated.Direction, - Index: InSignalCUpdated.Index, - Unit: InSignalCUpdated.Unit, -} - // Dashboards var DashboardA = Dashboard{ diff --git a/doc/api/responses.go b/doc/api/responses.go index 3a0c40c..16956be 100644 --- a/doc/api/responses.go +++ b/doc/api/responses.go @@ -64,3 +64,11 @@ type ResponseWidgets struct { type ResponseWidget struct { widget common.Widget } + +type ResponseSignals struct { + signals []common.Signal +} + +type ResponseSignal struct { + signal common.Signal +} diff --git a/routes/signal/signal_endpoints.go b/routes/signal/signal_endpoints.go index e077baa..f293228 100644 --- a/routes/signal/signal_endpoints.go +++ b/routes/signal/signal_endpoints.go @@ -1,6 +1,7 @@ package signal import ( + "fmt" "net/http" "github.com/gin-gonic/gin" @@ -24,11 +25,10 @@ func RegisterSignalEndpoints(r *gin.RouterGroup) { // @Tags signals // @Param direction query string true "Direction of signal (in or out)" // @Param modelID query string true "Model ID of signals to be obtained" -// @Success 200 {array} common.Signal "Requested signals." -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Success 200 {object} docs.ResponseSignals "Signals which belong to simulation model" +// @Failure 404 {object} docs.ResponseError "Not found" +// @Failure 422 {object} docs.ResponseError "Unprocessable entity" +// @Failure 500 {object} docs.ResponseError "Internal server error" // @Router /signals [get] func getSignals(c *gin.Context) { @@ -44,9 +44,9 @@ func getSignals(c *gin.Context) { } else if direction == "out" { mapping = "OutputMapping" } else { - errormsg := "Bad request. Direction has to be in or out" c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "error": "Bad request. Direction has to be in or out", }) return } @@ -58,9 +58,8 @@ func getSignals(c *gin.Context) { return } - serializer := common.SignalsSerializer{c, sigs} c.JSON(http.StatusOK, gin.H{ - "signals": serializer.Response(), + "signals": sigs, }) } @@ -70,31 +69,35 @@ func getSignals(c *gin.Context) { // @Accept json // @Produce json // @Tags signals -// @Param inputSignal body common.ResponseMsgSignal true "A signal to be added to the model incl. direction and model ID to which signal shall be added" -// @Success 200 "OK." -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Param inputSignal body signal.validNewSignal true "A signal to be added to the model incl. direction and model ID to which signal shall be added" +// @Success 200 {object} docs.ResponseSignal "Signal 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 /signals [post] func addSignal(c *gin.Context) { - var newSignalData common.ResponseMsgSignal - err := c.BindJSON(&newSignalData) - if err != nil { - errormsg := "Bad request. Error binding form data to JSON: " + err.Error() + var req addSignalRequest + if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": fmt.Sprintf("%v", err), }) return } - var newSignal Signal - newSignal.Index = newSignalData.Signal.Index - newSignal.SimulationModelID = newSignalData.Signal.SimulationModelID - newSignal.Direction = newSignalData.Signal.Direction - newSignal.Unit = newSignalData.Signal.Unit - newSignal.Name = newSignalData.Signal.Name + // 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 signal from the request + newSignal := req.createSignal() ok, _ := simulationmodel.CheckPermissions(c, common.Update, "body", int(newSignal.SimulationModelID)) if !ok { @@ -102,12 +105,15 @@ func addSignal(c *gin.Context) { } // Add signal to model - err = newSignal.addToSimulationModel() - if common.ProvideErrorResponse(c, err) == false { - c.JSON(http.StatusOK, gin.H{ - "message": "OK.", - }) + err := newSignal.addToSimulationModel() + if err != nil { + common.ProvideErrorResponse(c, err) + return } + + c.JSON(http.StatusOK, gin.H{ + "signal": newSignal.Signal, + }) } // updateSignal godoc @@ -115,35 +121,58 @@ func addSignal(c *gin.Context) { // @ID updateSignal // @Tags signals // @Produce json -// @Success 200 "OK." -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" +// @Param inputSignal body signal.validUpdatedSignal true "A signal to be updated" +// @Success 200 {object} docs.ResponseSignal "Signal 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 signalID path int true "ID of signal to be updated" // @Router /signals/{signalID} [put] func updateSignal(c *gin.Context) { - ok, sig := checkPermissions(c, common.Delete) + ok, oldSignal := checkPermissions(c, common.Delete) if !ok { return } - var modifiedSignal common.ResponseMsgSignal - err := c.BindJSON(&modifiedSignal) - if err != nil { - errormsg := "Bad request. Error binding form data to JSON: " + err.Error() + var req updateSignalRequest + if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "message": fmt.Sprintf("%v", err), }) return } - err = sig.update(modifiedSignal.Signal) - 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 updatedSignal from oldDashboard + updatedSignal, err := req.updatedSignal(oldSignal) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) + return + } + + // Update the signal in the DB + err = oldSignal.update(updatedSignal) + if err != nil { + common.ProvideErrorResponse(c, err) + return + } + + c.JSON(http.StatusOK, gin.H{ + "signal": updatedSignal.Signal, + }) } // getSignal godoc @@ -151,11 +180,11 @@ func updateSignal(c *gin.Context) { // @ID getSignal // @Tags signals // @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.ResponseSignal "Signal 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 signalID path int true "ID of signal to be obtained" // @Router /signals/{signalID} [get] func getSignal(c *gin.Context) { @@ -164,9 +193,8 @@ func getSignal(c *gin.Context) { return } - serializer := common.SignalSerializer{c, sig.Signal} c.JSON(http.StatusOK, gin.H{ - "signal": serializer.Response(), + "signal": sig.Signal, }) } @@ -175,11 +203,11 @@ func getSignal(c *gin.Context) { // @ID deleteSignal // @Tags signals // @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.ResponseSignal "Signal 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 signalID path int true "ID of signal to be deleted" // @Router /signals/{signalID} [delete] func deleteSignal(c *gin.Context) { @@ -190,10 +218,12 @@ func deleteSignal(c *gin.Context) { } err := sig.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{ + "signal": sig.Signal, + }) } diff --git a/routes/signal/signal_methods.go b/routes/signal/signal_methods.go index fd08726..ec82730 100644 --- a/routes/signal/signal_methods.go +++ b/routes/signal/signal_methods.go @@ -1,8 +1,6 @@ package signal import ( - "fmt" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel" ) @@ -21,7 +19,7 @@ func (s *Signal) byID(id uint) error { db := common.GetDB() err := db.Find(s, id).Error if err != nil { - return fmt.Errorf("Signal with id=%v does not exist", id) + return err } return nil } @@ -64,7 +62,7 @@ func (s *Signal) addToSimulationModel() error { return err } -func (s *Signal) update(modifiedSignal common.SignalResponse) error { +func (s *Signal) update(modifiedSignal Signal) error { db := common.GetDB() err := db.Model(s).Updates(map[string]interface{}{ diff --git a/routes/signal/signal_middleware.go b/routes/signal/signal_middleware.go index 404b5bc..b4ff1e0 100644 --- a/routes/signal/signal_middleware.go +++ b/routes/signal/signal_middleware.go @@ -17,15 +17,18 @@ func checkPermissions(c *gin.Context, operation common.CRUD) (bool, Signal) { err := common.ValidateRole(c, common.ModelSignal, 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, sig } signalID, err := strconv.Atoi(c.Param("signalID")) if err != nil { - errormsg := fmt.Sprintf("Bad request. No or incorrect format of signalID path parameter") c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, + "success": false, + "error": fmt.Sprintf("Bad request. No or incorrect format of signalID path parameter"), }) return false, sig } diff --git a/routes/signal/signal_test.go b/routes/signal/signal_test.go index 5c4654c..800fe60 100644 --- a/routes/signal/signal_test.go +++ b/routes/signal/signal_test.go @@ -1,73 +1,395 @@ package signal 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/simulationmodel" + "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 TestSignalEndpoints(t *testing.T) { +var router *gin.Engine +var db *gorm.DB - var token string +type SignalRequest struct { + Name string `json:"name,omitempty"` + Unit string `json:"unit,omitempty"` + Index uint `json:"index,omitempty"` + Direction string `json:"direction,omitempty"` + SimulationModelID uint `json:"simulationModelID,omitempty"` +} - var myInSignals = []common.SignalResponse{common.InSignalA_response, common.InSignalB_response} - var myOutSignals = []common.SignalResponse{common.OutSignalA_response, common.OutSignalB_response} - var msgInSignals = common.ResponseMsgSignals{Signals: myInSignals} - var msgInSignalC = common.ResponseMsgSignal{Signal: common.InSignalC_response} - var msgInSignalCupdated = common.ResponseMsgSignal{Signal: common.InSignalCUpdated_response} - var msgOutSignals = common.ResponseMsgSignals{Signals: myOutSignals} +type SimulationModelRequest struct { + Name string `json:"name,omitempty"` + ScenarioID uint `json:"scenarioID,omitempty"` + SimulatorID uint `json:"simulatorID,omitempty"` + StartParameters postgres.Jsonb `json:"startParameters,omitempty"` +} - db := common.DummyInitDB() +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"` +} + +type ScenarioRequest struct { + Name string `json:"name,omitempty"` + Running bool `json:"running,omitempty"` + StartParameters postgres.Jsonb `json:"startParameters,omitempty"` +} + +func addScenarioAndSimulatorAndSimulationModel() (scenarioID uint, simulatorID uint, simulationModelID 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) + + // test POST models/ $newSimulationModel + newSimulationModel := SimulationModelRequest{ + Name: common.SimulationModelA.Name, + ScenarioID: uint(newScenarioID), + SimulatorID: uint(newSimulatorID), + StartParameters: common.SimulationModelA.StartParameters, + } + _, resp, _ = common.NewTestEndpoint(router, token, + "/api/models", "POST", common.KeyModels{"model": newSimulationModel}) + + // Read newSimulationModel's ID from the response + newSimulationModelID, _ := common.GetResponseID(resp) + + return uint(newScenarioID), uint(newSimulatorID), uint(newSimulationModelID) +} + +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)) - + // simulationmodel endpoints required here to first add a simulation to the DB + // that can be associated with a new signal model + simulationmodel.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")) RegisterSignalEndpoints(api.Group("/signals")) - credjson, _ := json.Marshal(common.CredUser) - msgOKjson, _ := json.Marshal(common.MsgOK) - msgInSignalsjson, _ := json.Marshal(msgInSignals) - msgOutSignalsjson, _ := json.Marshal(msgOutSignals) - inSignalCjson, _ := json.Marshal(msgInSignalC) - inSignalCupdatedjson, _ := json.Marshal(msgInSignalCupdated) + os.Exit(m.Run()) +} - token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200) +func TestAddSignal(t *testing.T) { + common.DropTables(db) + common.MigrateModels(db) + common.DummyAddOnlyUserTableWithAdminAndUsersDB(db) - // test GET signals - common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=in", "GET", nil, 200, msgInSignalsjson) - common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=out", "GET", nil, 200, msgOutSignalsjson) + // 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 + _, _, simulationModelID := addScenarioAndSimulatorAndSimulationModel() - // test POST signals - common.TestEndpoint(t, router, token, "/api/signals", "POST", inSignalCjson, 200, msgOKjson) + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) - // test GET signals/:signalID - common.TestEndpoint(t, router, token, "/api/signals/5", "GET", nil, 200, inSignalCjson) + // test POST signals/ $newSignal + newSignal := SignalRequest{ + Name: common.InSignalA.Name, + Unit: common.InSignalA.Unit, + Direction: common.InSignalA.Direction, + Index: 1, + SimulationModelID: simulationModelID, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/signals", "POST", common.KeyModels{"signal": newSignal}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) - // test PUT signals/:signalID - common.TestEndpoint(t, router, token, "/api/signals/5", "PUT", inSignalCupdatedjson, 200, msgOKjson) - common.TestEndpoint(t, router, token, "/api/signals/5", "GET", nil, 200, inSignalCupdatedjson) + // Compare POST's response with the newSignal + err = common.CompareResponse(resp, common.KeyModels{"signal": newSignal}) + assert.NoError(t, err) - // test DELETE signals/:signalID - common.TestEndpoint(t, router, token, "/api/signals/5", "DELETE", nil, 200, msgOKjson) - common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=in", "GET", nil, 200, msgInSignalsjson) - common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=out", "GET", nil, 200, msgOutSignalsjson) + // Read newSignal's ID from the response + newSignalID, err := common.GetResponseID(resp) + assert.NoError(t, err) - // TODO test GET models/:ModelID to check if POST and DELETE adapt InputLength correctly?? - //common.TestEndpoint(t, router, token, "/api/models/1", "GET", nil, 200, string(msgModelAUpdated2json)) + // Get the newSignal + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/signals/%v", newSignalID), "GET", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) - // TODO add testing for other return codes + // Compare GET's response with the newSSignal + err = common.CompareResponse(resp, common.KeyModels{"signal": newSignal}) + assert.NoError(t, err) + + // try to POST a malformed signal + // Required fields are missing + malformedNewSignal := SignalRequest{ + Name: "ThisIsAMalformedRequest", + } + // this should NOT work and return a unprocessable entity 442 status code + code, resp, err = common.NewTestEndpoint(router, token, + "/api/signals", "POST", common.KeyModels{"model": malformedNewSignal}) + assert.NoError(t, err) + assert.Equalf(t, 422, code, "Response body: \n%v\n", resp) +} + +func TestUpdateSignal(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 + _, _, simulationModelID := addScenarioAndSimulatorAndSimulationModel() + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST signals/ $newSignal + newSignal := SignalRequest{ + Name: common.InSignalA.Name, + Unit: common.InSignalA.Unit, + Direction: common.InSignalA.Direction, + Index: 1, + SimulationModelID: simulationModelID, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/signals", "POST", common.KeyModels{"signal": newSignal}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newSignal's ID from the response + newSignalID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + updatedSignal := SignalRequest{ + Name: common.InSignalB.Name, + Unit: common.InSignalB.Unit, + Index: 1, + } + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/signals/%v", newSignalID), "PUT", common.KeyModels{"signal": updatedSignal}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare PUT's response with the updatedSignal + err = common.CompareResponse(resp, common.KeyModels{"signal": updatedSignal}) + assert.NoError(t, err) + + // Get the updatedSignal + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/signals/%v", newSignalID), "GET", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare GET's response with the updatedSignal + err = common.CompareResponse(resp, common.KeyModels{"signal": updatedSignal}) + assert.NoError(t, err) + + // try to update a signal that does not exist (should return not found 404 status code) + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/signals/%v", newSignalID+1), "PUT", common.KeyModels{"signal": updatedSignal}) + assert.NoError(t, err) + assert.Equalf(t, 404, code, "Response body: \n%v\n", resp) + +} + +func TestDeleteSignal(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 + _, _, simulationModelID := addScenarioAndSimulatorAndSimulationModel() + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // test POST signals/ $newSignal + newSignal := SignalRequest{ + Name: common.InSignalA.Name, + Unit: common.InSignalA.Unit, + Direction: common.InSignalA.Direction, + Index: 1, + SimulationModelID: simulationModelID, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/signals", "POST", common.KeyModels{"signal": newSignal}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newSignal's ID from the response + newSignalID, err := common.GetResponseID(resp) + assert.NoError(t, err) + + // Count the number of all the input signals returned for simulation model + initialNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/signals?modelID=%v&direction=in", simulationModelID), "GET", nil) + assert.NoError(t, err) + + // add an output signal to make sure that counting of input signals works + newSignalout := SignalRequest{ + Name: common.OutSignalA.Name, + Unit: common.OutSignalA.Unit, + Direction: common.OutSignalA.Direction, + Index: 1, + SimulationModelID: simulationModelID, + } + code, resp, err = common.NewTestEndpoint(router, token, + "/api/signals", "POST", common.KeyModels{"signal": newSignalout}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Delete the added newSignal + code, resp, err = common.NewTestEndpoint(router, token, + fmt.Sprintf("/api/signals/%v", newSignalID), "DELETE", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare DELETE's response with the newSignal + err = common.CompareResponse(resp, common.KeyModels{"signal": newSignal}) + assert.NoError(t, err) + + // Again count the number of all the input signals returned for simulation model + finalNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/signals?modelID=%v&direction=in", simulationModelID), "GET", nil) + assert.NoError(t, err) + + assert.Equal(t, initialNumber-1, finalNumber) +} + +func TestGetAllInputSignalsOfSimulationModel(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 + _, _, simulationModelID := addScenarioAndSimulatorAndSimulationModel() + + // authenticate as normal user + token, err := common.NewAuthenticateForTest(router, + "/api/authenticate", "POST", common.UserACredentials) + assert.NoError(t, err) + + // Count the number of all the input signals returned for simulation model + initialNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/signals?modelID=%v&direction=in", simulationModelID), "GET", nil) + assert.NoError(t, err) + + // test POST signals/ $newSignal + newSignalA := SignalRequest{ + Name: common.InSignalA.Name, + Unit: common.InSignalA.Unit, + Direction: common.InSignalA.Direction, + Index: 1, + SimulationModelID: simulationModelID, + } + code, resp, err := common.NewTestEndpoint(router, token, + "/api/signals", "POST", common.KeyModels{"signal": newSignalA}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // add a second input signal + newSignalB := SignalRequest{ + Name: common.InSignalB.Name, + Unit: common.InSignalB.Unit, + Direction: common.InSignalB.Direction, + Index: 2, + SimulationModelID: simulationModelID, + } + code, resp, err = common.NewTestEndpoint(router, token, + "/api/signals", "POST", common.KeyModels{"signal": newSignalB}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // add an output signal + newSignalAout := SignalRequest{ + Name: common.OutSignalA.Name, + Unit: common.OutSignalA.Unit, + Direction: common.OutSignalA.Direction, + Index: 1, + SimulationModelID: simulationModelID, + } + code, resp, err = common.NewTestEndpoint(router, token, + "/api/signals", "POST", common.KeyModels{"signal": newSignalAout}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // add a second output signal + newSignalBout := SignalRequest{ + Name: common.OutSignalB.Name, + Unit: common.OutSignalB.Unit, + Direction: common.OutSignalB.Direction, + Index: 1, + SimulationModelID: simulationModelID, + } + code, resp, err = common.NewTestEndpoint(router, token, + "/api/signals", "POST", common.KeyModels{"signal": newSignalBout}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Again count the number of all the input signals returned for simulation model + finalNumber, err := common.LengthOfResponse(router, token, + fmt.Sprintf("/api/signals?modelID=%v&direction=in", simulationModelID), "GET", nil) + assert.NoError(t, err) + + assert.Equal(t, initialNumber+2, finalNumber) } diff --git a/routes/signal/signal_validators.go b/routes/signal/signal_validators.go new file mode 100644 index 0000000..2774662 --- /dev/null +++ b/routes/signal/signal_validators.go @@ -0,0 +1,73 @@ +package signal + +import ( + "gopkg.in/go-playground/validator.v9" +) + +var validate *validator.Validate + +type validNewSignal struct { + Name string `form:"Name" validate:"required"` + Unit string `form:"unit" validate:"omitempty"` + Index uint `form:"index" validate:"required"` + Direction string `form:"direction" validate:"required,oneof=in out"` + SimulationModelID uint `form:"simulationModelID" validate:"required"` +} + +type validUpdatedSignal struct { + Name string `form:"Name" validate:"omitempty"` + Unit string `form:"unit" validate:"omitempty"` + Index uint `form:"index" validate:"omitempty"` +} + +type addSignalRequest struct { + validNewSignal `json:"signal"` +} + +type updateSignalRequest struct { + validUpdatedSignal `json:"signal"` +} + +func (r *addSignalRequest) validate() error { + validate = validator.New() + errs := validate.Struct(r) + return errs +} + +func (r *validUpdatedSignal) validate() error { + validate = validator.New() + errs := validate.Struct(r) + return errs +} + +func (r *addSignalRequest) createSignal() Signal { + var s Signal + + s.Name = r.Name + s.Unit = r.Unit + s.Index = r.Index + s.Direction = r.Direction + s.SimulationModelID = r.SimulationModelID + + return s +} + +func (r *updateSignalRequest) updatedSignal(oldSignal Signal) (Signal, error) { + // Use the old Signal as a basis for the updated Signal `s` + s := oldSignal + + if r.Name != "" { + s.Name = r.Name + } + + if r.Index != 0 { + // TODO this implies that we start indexing at 1 + s.Index = r.Index + } + + if r.Unit != "" { + s.Unit = r.Unit + } + + return s, nil +}