From 5ca6281a224c9bd88c408c0e272b08edf9d02df4 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Thu, 6 Jun 2019 16:36:12 +0200 Subject: [PATCH] - add testing for simulationmodel endpoints - add Signal serializer - Use Signals DB table again - remove / in some endpoint definitions --- .gitlab-ci.yml | 2 + common/database.go | 31 +- common/database_test.go | 12 +- common/models.go | 13 +- common/responses.go | 35 +- common/serializers.go | 35 +- routes/file/fileEndpoints.go | 4 +- routes/simulation/simulationEndpoints.go | 4 +- routes/simulation/simulationMethods.go | 54 ++- routes/simulation/simulation_test.go | 6 +- .../simulationmodelEndpoints.go | 129 +++---- .../simulationmodel/simulationmodelMethods.go | 66 ++-- .../simulationmodel/simulationmodel_test.go | 338 ++++++++++++++++++ routes/simulator/simulatorEndpoints.go | 4 +- .../visualization/visualizationEndpoints.go | 4 +- routes/widget/widgetEndpoints.go | 4 +- start.go | 2 +- 17 files changed, 576 insertions(+), 167 deletions(-) create mode 100644 routes/simulationmodel/simulationmodel_test.go diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c6736a5..ae3dce7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -91,6 +91,8 @@ test:backend:endpoints: - ~/go/bin/swag init -p pascalcase -g "start.go" -o "./doc/api/" - cd routes/simulation - go test -v -args -dbhost=/var/run/postgresql + - cd ../simulationmodel + - go test -v -args -dbhost=/var/run/postgresql dependencies: - build:backend diff --git a/common/database.go b/common/database.go index 19ef5c4..097eda7 100644 --- a/common/database.go +++ b/common/database.go @@ -54,7 +54,7 @@ func VerifyConnection(db *gorm.DB) error { // to the Dummy*() where it is called func DropTables(db *gorm.DB) { db.DropTableIfExists(&Simulator{}) - //db.DropTableIfExists(&Signal{}) + db.DropTableIfExists(&Signal{}) db.DropTableIfExists(&SimulationModel{}) db.DropTableIfExists(&File{}) db.DropTableIfExists(&Simulation{}) @@ -66,7 +66,7 @@ func DropTables(db *gorm.DB) { // AutoMigrate the models func MigrateModels(db *gorm.DB) { db.AutoMigrate(&Simulator{}) - //db.AutoMigrate(&Signal{}) + db.AutoMigrate(&Signal{}) db.AutoMigrate(&SimulationModel{}) db.AutoMigrate(&File{}) db.AutoMigrate(&Simulation{}) @@ -101,14 +101,14 @@ func DummyPopulateDB(test_db *gorm.DB) { checkErr(test_db.Create(&simr_A).Error) checkErr(test_db.Create(&simr_B).Error) - //outSig_A := Signal{Name: "outSignal_A", Direction: "out"} - //outSig_B := Signal{Name: "outSignal_B", Direction: "out"} - //inSig_A := Signal{Name: "inSignal_A", Direction: "in"} - //inSig_B := Signal{Name: "inSignal_B", Direction: "in"} - //checkErr(test_db.Create(&outSig_A).Error) - //checkErr(test_db.Create(&outSig_B).Error) - //checkErr(test_db.Create(&inSig_A).Error) - //checkErr(test_db.Create(&inSig_B).Error) + outSig_A := Signal{Name: "outSignal_A", Direction: "out", Index: 0, Unit: "V"} + outSig_B := Signal{Name: "outSignal_B", Direction: "out", Index: 1, Unit: "V"} + inSig_A := Signal{Name: "inSignal_A", Direction: "in", Index: 0, Unit: "A"} + inSig_B := Signal{Name: "inSignal_B", Direction: "in", Index: 1, Unit: "A"} + checkErr(test_db.Create(&outSig_A).Error) + checkErr(test_db.Create(&outSig_B).Error) + checkErr(test_db.Create(&inSig_A).Error) + checkErr(test_db.Create(&inSig_B).Error) mo_A := SimulationModel{Name: "SimulationModel_A"} mo_B := SimulationModel{Name: "SimulationModel_B"} @@ -179,11 +179,11 @@ func DummyPopulateDB(test_db *gorm.DB) { checkErr(test_db.Model(&vis_A).Association("Widgets").Append(&widg_A).Error) checkErr(test_db.Model(&vis_A).Association("Widgets").Append(&widg_B).Error) - // SimulationModel HM Signal - //checkErr(test_db.Model(&mo_A).Association("InputMapping").Append(&inSig_A).Error) - //checkErr(test_db.Model(&mo_A).Association("InputMapping").Append(&inSig_B).Error) - //checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_A).Error) - //checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_B).Error) + // SimulationModel HM Signals + checkErr(test_db.Model(&mo_A).Association("InputMapping").Append(&inSig_A).Error) + checkErr(test_db.Model(&mo_A).Association("InputMapping").Append(&inSig_B).Error) + checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_A).Error) + checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_B).Error) // SimulationModel HM Files checkErr(test_db.Model(&mo_A).Association("Files").Append(&file_A).Error) @@ -191,6 +191,7 @@ func DummyPopulateDB(test_db *gorm.DB) { // Simulator BT SimulationModel checkErr(test_db.Model(&mo_A).Association("Simulator").Append(&simr_A).Error) + checkErr(test_db.Model(&mo_B).Association("Simulator").Append(&simr_A).Error) // Widget HM Files checkErr(test_db.Model(&widg_A).Association("Files").Append(&file_A).Error) diff --git a/common/database_test.go b/common/database_test.go index 1492d9b..45dd69e 100644 --- a/common/database_test.go +++ b/common/database_test.go @@ -37,7 +37,7 @@ func TestDummyDBAssociations(t *testing.T) { var vis Visualization var widg Widget - //var sigs []Signal + var sigs []Signal var mos []SimulationModel var files []File var files_sm []File @@ -93,11 +93,11 @@ func TestDummyDBAssociations(t *testing.T) { a.NoError(db.Model(&mo).Association("Simulator").Find(&simr).Error) a.EqualValues("Host_A", simr.Host, "Expected Host_A") - //a.NoError(db.Model(&mo).Where("Direction = ?", "out").Related(&sigs, "OutputMapping").Error) - //if len(sigs) != 2 { - // a.Fail("Model Associations", - // "Expected to have %v Output AND Input Signals. Has %v.", 2, len(sigs)) - //} + a.NoError(db.Model(&mo).Where("Direction = ?", "out").Related(&sigs, "OutputMapping").Error) + if len(sigs) != 2 { + a.Fail("SimulationModel Associations", + "Expected to have %v Output Signals. Has %v.", 2, len(sigs)) + } a.NoError(db.Model(&mo).Related(&files_sm, "Files").Error) if len(files_sm) != 2 { diff --git a/common/models.go b/common/models.go index 94e6351..cc3ca1a 100644 --- a/common/models.go +++ b/common/models.go @@ -57,14 +57,17 @@ type SimulationModel struct { // ID of simulator associated with simulation model SimulatorID uint // Mapping of output signals of the simulation model, order of signals is important - OutputMapping []Signal + OutputMapping []Signal `gorm:"foreignkey:SimulationModelID"` // Mapping of input signals of the simulation model, order of signals is important - InputMapping []Signal + InputMapping []Signal `gorm:"foreignkey:SimulationModelID"` // Files of simulation model (can be CIM and other simulation model file formats) - Files []File `gorm:"foreignkey:ModelID"` + Files []File `gorm:"foreignkey:SimulationModelID"` } +// Signal data model type Signal struct { + // ID of simulation model + ID uint `gorm:"primary_key;auto_increment"` // Name of Signal Name string // Unit of Signal @@ -73,6 +76,8 @@ type Signal struct { Index uint // Direction of the signal (in or out) Direction string + // ID of simulation model + SimulationModelID uint } // Simulator data model @@ -162,7 +167,7 @@ type File struct { // Last modification time of file Date time.Time // ID of model to which file belongs - ModelID uint `gorm:""` + SimulationModelID uint `gorm:""` // ID of widget to which file belongs WidgetID uint `gorm:""` } diff --git a/common/responses.go b/common/responses.go index fcd54a5..1a75cb4 100644 --- a/common/responses.go +++ b/common/responses.go @@ -18,14 +18,13 @@ type SimulationResponse struct { } type SimulationModelResponse struct { - Name string `json:"Name"` - OutputLength int `json:"OutputLength"` - InputLength int `json:"InputLength"` - SimulationID uint `json:"SimulationID"` - SimulatorID uint `json:"SimulatorID"` - StartParams string `json:"StartParams"` - InputMapping []Signal `json:"InputMapping"` - OutputMapping []Signal `json:"OutputMapping"` + ID uint `json:"ID"` + Name string `json:"Name"` + OutputLength int `json:"OutputLength"` + InputLength int `json:"InputLength"` + SimulationID uint `json:"SimulationID"` + SimulatorID uint `json:"SimulatorID"` + StartParams string `json:"StartParams"` } type SimulatorResponse struct { @@ -71,6 +70,14 @@ type FileResponse struct { Date time.Time `json:"Date"` } +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 { @@ -92,3 +99,15 @@ type ResponseMsgSimulations struct { type ResponseMsgSimulation struct { Simulation SimulationResponse `json:"simulation"` } + +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 226dae8..c12a299 100644 --- a/common/serializers.go +++ b/common/serializers.go @@ -104,14 +104,13 @@ type SimulationModelSerializer struct { func (self *SimulationModelSerializer) Response() SimulationModelResponse { response := SimulationModelResponse{ + ID: self.ID, Name: self.Name, OutputLength: self.OutputLength, InputLength: self.InputLength, SimulationID: self.SimulationID, SimulatorID: self.SimulatorID, StartParams: self.StartParameters, - //InputMapping - //OutputMapping } return response } @@ -255,3 +254,35 @@ 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/routes/file/fileEndpoints.go b/routes/file/fileEndpoints.go index 98fe78e..4a3cc36 100644 --- a/routes/file/fileEndpoints.go +++ b/routes/file/fileEndpoints.go @@ -11,8 +11,8 @@ import ( ) func RegisterFileEndpoints(r *gin.RouterGroup) { - r.GET("/", getFiles) - r.POST("/", addFile) + r.GET("", getFiles) + r.POST("", addFile) r.GET("/:fileID", getFile) r.PUT("/:fileID", updateFile) r.DELETE("/:fileID", deleteFile) diff --git a/routes/simulation/simulationEndpoints.go b/routes/simulation/simulationEndpoints.go index 65c9dbe..f406113 100644 --- a/routes/simulation/simulationEndpoints.go +++ b/routes/simulation/simulationEndpoints.go @@ -10,8 +10,8 @@ import ( ) func RegisterSimulationEndpoints(r *gin.RouterGroup) { - r.GET("/", getSimulations) - r.POST("/", addSimulation) + r.GET("", getSimulations) + r.POST("", addSimulation) r.PUT("/:simulationID", updateSimulation) r.GET("/:simulationID", getSimulation) r.DELETE("/:simulationID", deleteSimulation) diff --git a/routes/simulation/simulationMethods.go b/routes/simulation/simulationMethods.go index 0d19031..7a3d5b1 100644 --- a/routes/simulation/simulationMethods.go +++ b/routes/simulation/simulationMethods.go @@ -77,42 +77,38 @@ func (s *Simulation) deleteUser(username string) error { func (s *Simulation) delete() error { db := common.GetDB() - no_models := db.Model(s).Association("SimulationModels").Count() - no_visualizations := db.Model(s).Association("Visualizations").Count() - if no_models > 0 || no_visualizations > 0 { - return fmt.Errorf("cannot delete simulation that contains models and/ or visualizations, doing nothing") - } else { - // delete simulation from all users and vice versa + // delete simulation from all users and vice versa - users, no_users, err := s.getUsers() - if err != nil { - return err - } + users, no_users, err := s.getUsers() + if err != nil { + return err + } - if no_users > 0 { - for _, u := range users { - // remove user from simulation - err = db.Model(s).Association("Users").Delete(&u).Error - if err != nil { - return err - } - // remove simulation from user - err = db.Model(&u).Association("Simulations").Delete(s).Error - if err != nil { - return err - } + if no_users > 0 { + for _, u := range users { + // remove user from simulation + err = db.Model(s).Association("Users").Delete(&u).Error + if err != nil { + return err + } + // remove simulation from user + err = db.Model(&u).Association("Simulations").Delete(s).Error + if err != nil { + return err } } - - // Delete simulation - err = db.Delete(s).Error - if err != nil { - return err - } - } + // Simulation is not deleted from DB, only associations with users are removed + // Simulation remains "dangling" in DB + + // Delete simulation + //err = db.Delete(s).Error + //if err != nil { + // return err + //} + return nil } diff --git a/routes/simulation/simulation_test.go b/routes/simulation/simulation_test.go index 3824042..f1327e2 100644 --- a/routes/simulation/simulation_test.go +++ b/routes/simulation/simulation_test.go @@ -147,17 +147,17 @@ func TestSimulationEndpoints(t *testing.T) { token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200) // test GET simulations/ - common.TestEndpoint(t, router, token, "/api/simulations/", "GET", nil, 200, string(msgSimulationsjson)) + common.TestEndpoint(t, router, token, "/api/simulations", "GET", nil, 200, string(msgSimulationsjson)) // test POST simulations/ - common.TestEndpoint(t, router, token, "/api/simulations/", "POST", simulationCjson, 200, string(msgOKjson)) + common.TestEndpoint(t, router, token, "/api/simulations", "POST", simulationCjson, 200, string(msgOKjson)) // test GET simulations/:SimulationID common.TestEndpoint(t, router, token, "/api/simulations/3", "GET", nil, 200, string(msgSimulationjson)) // test DELETE simulations/:SimulationID common.TestEndpoint(t, router, token, "/api/simulations/3", "DELETE", nil, 200, string(msgOKjson)) - common.TestEndpoint(t, router, token, "/api/simulations/", "GET", nil, 200, string(msgSimulationsjson)) + common.TestEndpoint(t, router, token, "/api/simulations", "GET", nil, 200, string(msgSimulationsjson)) // test GET simulations/:SimulationID/users common.TestEndpoint(t, router, token, "/api/simulations/1/users", "GET", nil, 200, string(msgUsersjson)) diff --git a/routes/simulationmodel/simulationmodelEndpoints.go b/routes/simulationmodel/simulationmodelEndpoints.go index eb8ca99..4f00120 100644 --- a/routes/simulationmodel/simulationmodelEndpoints.go +++ b/routes/simulationmodel/simulationmodelEndpoints.go @@ -9,15 +9,15 @@ import ( "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation" ) -func RegisterModelEndpoints(r *gin.RouterGroup) { - r.GET("/", getSimulationModels) - r.POST("/", addSimulationModel) +func RegisterSimulationModelEndpoints(r *gin.RouterGroup) { + r.GET("", getSimulationModels) + r.POST("", addSimulationModel) //r.POST("/:modelID", cloneSimulationModel) r.PUT("/:modelID", updateSimulationModel) r.GET("/:modelID", getSimulationModel) r.DELETE("/:modelID", deleteSimulationModel) r.GET("/:modelID/signals", getSignals) - r.POST("/:modelID/signals", addSignal) + r.PUT("/:modelID/signals", addSignal) r.DELETE("/:modelID/signals", deleteSignals) } @@ -83,7 +83,7 @@ func addSimulationModel(c *gin.Context) { return } - err = newModel.addToSimulation(newModel.SimulationID) + err = newModel.addToSimulation() if common.ProvideErrorResponse(c, err) == false { c.JSON(http.StatusOK, gin.H{ "message": "OK.", @@ -203,63 +203,19 @@ func getSimulationModel(c *gin.Context) { // @Router /models/{modelID} [delete] func deleteSimulationModel(c *gin.Context) { - //ok, m := checkPermissions(c, common.Delete) - //if !ok { - // return - //} - - c.JSON(http.StatusOK, gin.H{ - "message": "Not implemented.", - }) -} - -// AddSignal godoc -// @Summary Add a signal to a signal mapping of a model -// @ID AddSignal -// @Accept json -// @Produce json -// @Tags models -// @Param inputSignal body common.Signal true "A signal to be added to the model" -// @Param direction query string true "Direction of signal (in or out)" -// @Success 200 "OK." -// @Failure 401 "Unauthorized Access" -// @Failure 403 "Access forbidden." -// @Failure 404 "Not found" -// @Failure 500 "Internal server error" -// @Router /models/{modelID}/signals [post] -func addSignal(c *gin.Context) { - - ok, m := checkPermissions(c, common.Update) + ok, m := checkPermissions(c, common.Delete) if !ok { return } - direction := c.Request.URL.Query().Get("direction") - if !(direction == "out") && !(direction == "in") { - errormsg := "Bad request. Direction has to be in or out" - c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, - }) + err := m.delete() + if common.ProvideErrorResponse(c, err) { return } - var sig common.Signal - err := c.BindJSON(&sig) - if err != nil { - errormsg := "Bad request. Error binding form data to JSON: " + err.Error() - c.JSON(http.StatusBadRequest, gin.H{ - "error": errormsg, - }) - return - } - - // Add signal to model - err = m.addSignal(sig, direction) - if common.ProvideErrorResponse(c, err) == false { - c.JSON(http.StatusOK, gin.H{ - "message": "OK.", - }) - } + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + }) } // getSignals godoc @@ -281,8 +237,13 @@ func getSignals(c *gin.Context) { return } - direction := c.Param("direction") - if !(direction == "out") && !(direction == "in") { + var mapping string + direction := c.Request.URL.Query().Get("direction") + if direction == "in" { + mapping = "InputMapping" + } 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, @@ -290,18 +251,58 @@ func getSignals(c *gin.Context) { return } - var signals []common.Signal - if direction == "in" { - signals = m.InputMapping - } else { - signals = m.OutputMapping + db := common.GetDB() + var sigs []common.Signal + err := db.Order("ID asc").Model(m).Where("Direction = ?", direction).Related(&sigs, mapping).Error + if common.ProvideErrorResponse(c, err) { + return } + serializer := common.SignalsSerializer{c, sigs} c.JSON(http.StatusOK, gin.H{ - "signals": signals, + "signals": serializer.Response(), }) } +// AddSignal godoc +// @Summary Add a signal to a signal mapping of a model +// @ID AddSignal +// @Accept json +// @Produce json +// @Tags models +// @Param inputSignal body common.Signal true "A signal to be added to the model incl. direction" +// @Success 200 "OK." +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Router /models/{modelID}/signals [put] +func addSignal(c *gin.Context) { + + ok, m := checkPermissions(c, common.Update) + if !ok { + return + } + + var sig common.Signal + err := c.BindJSON(&sig) + if err != nil { + errormsg := "Bad request. Error binding form data to JSON: " + err.Error() + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return + } + + // Add signal to model + err = m.addSignal(sig) + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + }) + } +} + // deleteSignals godoc // @Summary Delete all signals of a direction // @ID deleteSignals @@ -322,7 +323,7 @@ func deleteSignals(c *gin.Context) { return } - direction := c.Param("direction") + direction := c.Request.URL.Query().Get("direction") if !(direction == "out") && !(direction == "in") { errormsg := "Bad request. Direction has to be in or out" c.JSON(http.StatusBadRequest, gin.H{ diff --git a/routes/simulationmodel/simulationmodelMethods.go b/routes/simulationmodel/simulationmodelMethods.go index 5c21a81..c86ee82 100644 --- a/routes/simulationmodel/simulationmodelMethods.go +++ b/routes/simulationmodel/simulationmodelMethods.go @@ -27,10 +27,10 @@ func (m *SimulationModel) ByID(id uint) error { return nil } -func (m *SimulationModel) addToSimulation(simID uint) error { +func (m *SimulationModel) addToSimulation() error { db := common.GetDB() var sim simulation.Simulation - err := sim.ByID(simID) + err := sim.ByID(m.SimulationID) if err != nil { return err } @@ -54,55 +54,70 @@ func (m *SimulationModel) addToSimulation(simID uint) error { func (m *SimulationModel) update(modifiedSimulationModel SimulationModel) error { db := common.GetDB() - err := db.Model(m).Update(modifiedSimulationModel).Error - if err != nil { - return err - } if m.SimulatorID != modifiedSimulationModel.SimulatorID { // update simulator var s simulator.Simulator - err = s.ByID(modifiedSimulationModel.SimulatorID) - + err := s.ByID(modifiedSimulationModel.SimulatorID) + if err != nil { + return err + } err = db.Model(m).Association("Simulator").Replace(s).Error } - return err -} - -func (m *SimulationModel) updateSignals(signals []common.Signal, direction string) error { - - db := common.GetDB() - var err error - - if direction == "in" { - err = db.Model(m).Select("InputMapping").Update("InputMapping", signals).Error - } else { - err = db.Model(m).Select("OutputMapping").Update("OutputMapping", signals).Error + err := db.Model(m).Updates(map[string]interface{}{"Name": modifiedSimulationModel.Name, + "OutputLength": modifiedSimulationModel.OutputLength, + "InputLength": modifiedSimulationModel.InputLength, + "StartParameters": modifiedSimulationModel.StartParameters, + "SimulatorID": modifiedSimulationModel.SimulatorID, + }).Error + if err != nil { + return err } return err } -func (m *SimulationModel) addSignal(signal common.Signal, direction string) error { +func (m *SimulationModel) delete() error { + + db := common.GetDB() + var sim simulation.Simulation + err := sim.ByID(m.SimulationID) + if err != nil { + return err + } + + // remove association between SimulationModel and Simulation + // SimulationModel itself is not deleted from DB, it remains as "dangling" + err = db.Model(&sim).Association("SimulationModels").Delete(m).Error + + return err +} + +func (m *SimulationModel) addSignal(signal common.Signal) error { db := common.GetDB() var err error - if direction == "in" { + if signal.Direction == "in" { err = db.Model(m).Association("InputMapping").Append(signal).Error if err != nil { return err } // adapt length of mapping - m.InputLength = db.Model(m).Association("InputMapping").Count() + m.InputLength = db.Model(m).Where("Direction = ?", "in").Association("InputMapping").Count() + err = m.update(*m) } else { err = db.Model(m).Association("OutputMapping").Append(signal).Error + if err != nil { + return err + } // adapt length of mapping - m.OutputLength = db.Model(m).Association("OutputMapping").Count() + m.OutputLength = db.Model(m).Where("Direction = ?", "out").Association("OutputMapping").Count() + err = m.update(*m) } @@ -124,7 +139,7 @@ func (m *SimulationModel) deleteSignals(direction string) error { } var signals []common.Signal - err = db.Order("ID asc").Model(m).Related(&signals, columnName).Error + err = db.Order("ID asc").Model(m).Where("Direction = ?", direction).Related(&signals, columnName).Error if err != nil { return err } @@ -147,6 +162,7 @@ func (m *SimulationModel) deleteSignals(direction string) error { } else { m.OutputLength = 0 } + err = m.update(*m) return err } diff --git a/routes/simulationmodel/simulationmodel_test.go b/routes/simulationmodel/simulationmodel_test.go new file mode 100644 index 0000000..ec6901d --- /dev/null +++ b/routes/simulationmodel/simulationmodel_test.go @@ -0,0 +1,338 @@ +package simulationmodel + +import ( + "encoding/json" + "testing" + + "github.com/gin-gonic/gin" + + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user" +) + +var token string + +type credentials struct { + Username string `json:"username"` + Password string `json:"password"` +} + +var cred = credentials{ + Username: "User_A", + Password: "abc123", +} + +var msgOK = common.ResponseMsg{ + Message: "OK.", +} + +var modelA = common.SimulationModelResponse{ + ID: 1, + Name: "SimulationModel_A", + OutputLength: 1, + InputLength: 1, + SimulationID: 1, + SimulatorID: 1, + StartParams: "", +} + +var modelAUpdated = common.SimulationModelResponse{ + ID: 1, + Name: "SimulationModel_A", + OutputLength: 1, + InputLength: 3, + SimulationID: 1, + SimulatorID: 1, + StartParams: "", +} + +var modelAUpdated2 = common.SimulationModelResponse{ + ID: 1, + Name: "SimulationModel_A", + OutputLength: 1, + InputLength: 0, + SimulationID: 1, + SimulatorID: 1, + StartParams: "", +} + +var modelB = common.SimulationModelResponse{ + ID: 2, + Name: "SimulationModel_B", + OutputLength: 1, + InputLength: 1, + SimulationID: 1, + SimulatorID: 1, + StartParams: "", +} + +var modelC = common.SimulationModel{ + ID: 3, + Name: "SimulationModel_C", + OutputLength: 1, + InputLength: 1, + SimulationID: 1, + SimulatorID: 1, + StartParameters: "test", + InputMapping: nil, + OutputMapping: nil, +} + +var modelCupdated = common.SimulationModel{ + ID: modelC.ID, + Name: "SimulationModel_CUpdated", + OutputLength: modelC.OutputLength, + InputLength: modelC.InputLength, + SimulationID: modelC.SimulationID, + SimulatorID: 2, + StartParameters: modelC.StartParameters, + InputMapping: modelC.InputMapping, + OutputMapping: modelC.OutputMapping, +} + +var modelC_response = common.SimulationModelResponse{ + ID: modelC.ID, + Name: modelC.Name, + InputLength: modelC.InputLength, + OutputLength: modelC.OutputLength, + SimulationID: modelC.SimulationID, + SimulatorID: modelC.SimulatorID, + StartParams: modelC.StartParameters, +} + +var modelC_responseUpdated = common.SimulationModelResponse{ + ID: modelC.ID, + Name: modelCupdated.Name, + InputLength: modelC.InputLength, + OutputLength: modelC.OutputLength, + SimulationID: modelC.SimulationID, + SimulatorID: modelCupdated.SimulatorID, + StartParams: modelC.StartParameters, +} + +var myModels = []common.SimulationModelResponse{ + modelA, + modelB, +} + +var msgModels = common.ResponseMsgSimulationModels{ + SimulationModels: myModels, +} + +var msgModel = common.ResponseMsgSimulationModel{ + SimulationModel: modelC_response, +} + +var msgModelAUpdated = common.ResponseMsgSimulationModel{ + SimulationModel: modelAUpdated, +} + +var msgModelAUpdated2 = common.ResponseMsgSimulationModel{ + SimulationModel: modelAUpdated2, +} + +var msgModelupdated = common.ResponseMsgSimulationModel{ + SimulationModel: modelC_responseUpdated, +} + +var inSignalA = common.SignalResponse{ + Name: "inSignal_A", + Direction: "in", + Index: 0, + Unit: "A", + SimulationModelID: 1, +} + +var inSignalB = common.SignalResponse{ + Name: "inSignal_B", + Direction: "in", + Index: 1, + Unit: "A", + SimulationModelID: 1, +} + +var inSignalC = common.SignalResponse{ + Name: "inSignal_C", + Direction: "in", + Index: 2, + Unit: "A", + SimulationModelID: 1, +} + +var outSignalA = common.SignalResponse{ + Name: "outSignal_A", + Direction: "out", + Index: 0, + Unit: "V", + SimulationModelID: 1, +} + +var outSignalB = common.SignalResponse{ + Name: "outSignal_B", + Direction: "out", + Index: 1, + Unit: "V", + SimulationModelID: 1, +} + +var myInSignals = []common.SignalResponse{ + inSignalA, + inSignalB, +} + +var myInSignalsUpdated = []common.SignalResponse{ + inSignalA, + inSignalB, + inSignalC, +} + +var myOutSignals = []common.SignalResponse{ + outSignalA, + outSignalB, +} + +var msgSignalsEmpty = common.ResponseMsgSignals{ + Signals: []common.SignalResponse{}, +} + +var msgInSignals = common.ResponseMsgSignals{ + Signals: myInSignals, +} + +var msgInSignalsUpdated = common.ResponseMsgSignals{ + Signals: myInSignalsUpdated, +} + +var msgOutSignals = common.ResponseMsgSignals{ + Signals: myOutSignals, +} + +// Test /models endpoints +func TestSimulationModelEndpoints(t *testing.T) { + + 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.VisitorAuthenticate(api.Group("/authenticate")) + + api.Use(user.Authentication(true)) + + RegisterSimulationModelEndpoints(api.Group("/models")) + + credjson, err := json.Marshal(cred) + if err != nil { + panic(err) + } + + msgOKjson, err := json.Marshal(msgOK) + if err != nil { + panic(err) + } + + msgModelsjson, err := json.Marshal(msgModels) + if err != nil { + panic(err) + } + + msgModeljson, err := json.Marshal(msgModel) + if err != nil { + panic(err) + } + + msgModelupdatedjson, err := json.Marshal(msgModelupdated) + if err != nil { + panic(err) + } + + modelCjson, err := json.Marshal(modelC) + if err != nil { + panic(err) + } + + modelCupdatedjson, err := json.Marshal(modelCupdated) + if err != nil { + panic(err) + } + + msgModelAUpdatedjson, err := json.Marshal(msgModelAUpdated) + if err != nil { + panic(err) + } + + msgModelAUpdated2json, err := json.Marshal(msgModelAUpdated2) + if err != nil { + panic(err) + } + + msgSignalsEmptyjson, err := json.Marshal(msgSignalsEmpty) + if err != nil { + panic(err) + } + + msgInSignalsjson, err := json.Marshal(msgInSignals) + if err != nil { + panic(err) + } + + msgInSignalsUpdatedjson, err := json.Marshal(msgInSignalsUpdated) + if err != nil { + panic(err) + } + + msgOutSignalsjson, err := json.Marshal(msgOutSignals) + if err != nil { + panic(err) + } + + inSignalCjson, err := json.Marshal(inSignalC) + if err != nil { + panic(err) + } + + token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200) + + // test GET models + common.TestEndpoint(t, router, token, "/api/models?simulationID=1", "GET", nil, 200, string(msgModelsjson)) + + // test POST models + common.TestEndpoint(t, router, token, "/api/models", "POST", modelCjson, 200, string(msgOKjson)) + + // test GET models/:ModelID to check if previous POST worked correctly + common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, string(msgModeljson)) + + // test PUT models/:ModelID + common.TestEndpoint(t, router, token, "/api/models/3", "PUT", modelCupdatedjson, 200, string(msgOKjson)) + common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, string(msgModelupdatedjson)) + + // test DELETE models/:ModelID + common.TestEndpoint(t, router, token, "/api/models/3", "DELETE", nil, 200, string(msgOKjson)) + common.TestEndpoint(t, router, token, "/api/models?simulationID=1", "GET", nil, 200, string(msgModelsjson)) + + // test GET models/:ModelID/signals + common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=in", "GET", nil, 200, string(msgInSignalsjson)) + common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=out", "GET", nil, 200, string(msgOutSignalsjson)) + + // test PUT models/:ModelID/signals + common.TestEndpoint(t, router, token, "/api/models/1/signals", "PUT", inSignalCjson, 200, string(msgOKjson)) + common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=in", "GET", nil, 200, string(msgInSignalsUpdatedjson)) + + // test GET models/:ModelID to check if PUT adapted InputLength correctly + common.TestEndpoint(t, router, token, "/api/models/1", "GET", nil, 200, string(msgModelAUpdatedjson)) + + // test DELETE models/:ModelID/signals + common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=in", "DELETE", nil, 200, string(msgOKjson)) + common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=in", "GET", nil, 200, string(msgSignalsEmptyjson)) + common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=out", "GET", nil, 200, string(msgOutSignalsjson)) + + // test GET models/:ModelID to check if DELETE adapted InputLength correctly + common.TestEndpoint(t, router, token, "/api/models/1", "GET", nil, 200, string(msgModelAUpdated2json)) + + // TODO add testing for other return codes + +} diff --git a/routes/simulator/simulatorEndpoints.go b/routes/simulator/simulatorEndpoints.go index dfb6de7..a43aa80 100644 --- a/routes/simulator/simulatorEndpoints.go +++ b/routes/simulator/simulatorEndpoints.go @@ -9,8 +9,8 @@ import ( ) func RegisterSimulatorEndpoints(r *gin.RouterGroup) { - r.GET("/", GetSimulators) - r.POST("/", AddSimulator) + r.GET("", GetSimulators) + r.POST("", AddSimulator) r.PUT("/:simulatorID", UpdateSimulator) r.GET("/:simulatorID", GetSimulator) r.DELETE("/:simulatorID", DeleteSimulator) diff --git a/routes/visualization/visualizationEndpoints.go b/routes/visualization/visualizationEndpoints.go index d75826b..db45527 100644 --- a/routes/visualization/visualizationEndpoints.go +++ b/routes/visualization/visualizationEndpoints.go @@ -13,8 +13,8 @@ import ( func RegisterVisualizationEndpoints(r *gin.RouterGroup) { - r.GET("/", getVisualizations) - r.POST("/", addVisualization) + r.GET("", getVisualizations) + r.POST("", addVisualization) //r.POST("/:visualizationID", cloneVisualization) r.PUT("/:visualizationID", updateVisualization) r.GET("/:visualizationID", getVisualization) diff --git a/routes/widget/widgetEndpoints.go b/routes/widget/widgetEndpoints.go index fe0fac4..0d9558e 100644 --- a/routes/widget/widgetEndpoints.go +++ b/routes/widget/widgetEndpoints.go @@ -10,8 +10,8 @@ import ( ) func RegisterWidgetEndpoints(r *gin.RouterGroup) { - r.GET("/", getWidgets) - r.POST("/", addWidget) + r.GET("", getWidgets) + r.POST("", addWidget) //r.POST("/:widgetID", cloneWidget) r.PUT("/:widgetID", updateWidget) r.GET("/:widgetID", getWidget) diff --git a/start.go b/start.go index f94da26..9e6cd6c 100644 --- a/start.go +++ b/start.go @@ -51,7 +51,7 @@ func main() { api.Use(user.Authentication(true)) simulation.RegisterSimulationEndpoints(api.Group("/simulations")) - simulationmodel.RegisterModelEndpoints(api.Group("/models")) + simulationmodel.RegisterSimulationModelEndpoints(api.Group("/models")) visualization.RegisterVisualizationEndpoints(api.Group("/visualizations")) widget.RegisterWidgetEndpoints(api.Group("/widgets")) file.RegisterFileEndpoints(api.Group("/files"))