- revise simulation model testing

- add validators for simulation model endpoints
- revise endpoint implementations and fix responses of simulation models
- clean up testdata, serializers and responses for simulation models
- change default values of output length and input length of signal mappings to 0
This commit is contained in:
Sonja Happ 2019-09-05 16:17:00 +02:00
parent 9c809e526a
commit 4914af6c47
10 changed files with 526 additions and 221 deletions

View file

@ -53,9 +53,9 @@ type SimulationModel struct {
// Name of simulation model // Name of simulation model
Name string `json:"name" gorm:"not null"` Name string `json:"name" gorm:"not null"`
// Number of output signals // Number of output signals
OutputLength int `json:"outputLength" gorm:"default:1"` OutputLength int `json:"outputLength" gorm:"default:0"`
// Number of input signals // Number of input signals
InputLength int `json:"inputLength" gorm:"default:1"` InputLength int `json:"inputLength" gorm:"default:0"`
// Start parameters of simulation model as JSON // Start parameters of simulation model as JSON
StartParameters postgres.Jsonb `json:"startParameters"` StartParameters postgres.Jsonb `json:"startParameters"`
// ID of Scenario to which simulation model belongs // ID of Scenario to which simulation model belongs

View file

@ -2,16 +2,6 @@ package common
import "github.com/jinzhu/gorm/dialects/postgres" import "github.com/jinzhu/gorm/dialects/postgres"
type SimulationModelResponse struct {
ID uint `json:"id"`
Name string `json:"name"`
OutputLength int `json:"outputLength"`
InputLength int `json:"inputLength"`
ScenarioID uint `json:"scenarioID"`
SimulatorID uint `json:"simulatorID"`
StartParameters postgres.Jsonb `json:"startParameters"`
}
type DashboardResponse struct { type DashboardResponse struct {
ID uint `json:"id"` ID uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@ -61,14 +51,6 @@ type ResponseMsg struct {
Message string `json:"message"` Message string `json:"message"`
} }
type ResponseMsgSimulationModels struct {
SimulationModels []SimulationModelResponse `json:"models"`
}
type ResponseMsgSimulationModel struct {
SimulationModel SimulationModelResponse `json:"model"`
}
type ResponseMsgSignals struct { type ResponseMsgSignals struct {
Signals []SignalResponse `json:"signals"` Signals []SignalResponse `json:"signals"`
} }

View file

@ -4,40 +4,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// Model/s Serializers
type SimulationModelsSerializer struct {
Ctx *gin.Context
SimulationModels []SimulationModel
}
func (self *SimulationModelsSerializer) Response() []SimulationModelResponse {
response := []SimulationModelResponse{}
for _, simulationmodel := range self.SimulationModels {
serializer := SimulationModelSerializer{self.Ctx, simulationmodel}
response = append(response, serializer.Response())
}
return response
}
type SimulationModelSerializer struct {
Ctx *gin.Context
SimulationModel
}
func (self *SimulationModelSerializer) Response() SimulationModelResponse {
response := SimulationModelResponse{
ID: self.ID,
Name: self.Name,
OutputLength: self.OutputLength,
InputLength: self.InputLength,
ScenarioID: self.ScenarioID,
SimulatorID: self.SimulatorID,
StartParameters: self.StartParameters,
}
return response
}
// Dashboard/s Serializers // Dashboard/s Serializers
type DashboardsSerializer struct { type DashboardsSerializer struct {

View file

@ -84,7 +84,6 @@ var SimulatorB = Simulator{
var startParametersA = json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`) var startParametersA = json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)
var startParametersB = json.RawMessage(`{"parameter1" : "testValue1B", "parameter2" : "testValue2B", "parameter3" : 43}`) var startParametersB = json.RawMessage(`{"parameter1" : "testValue1B", "parameter2" : "testValue2B", "parameter3" : 43}`)
var startParametersC = json.RawMessage(`{"parameter1" : "testValue1C", "parameter2" : "testValue2C", "parameter3" : 44}`)
var ScenarioA = Scenario{ var ScenarioA = Scenario{
Name: "Scenario_A", Name: "Scenario_A",
@ -101,72 +100,14 @@ var ScenarioB = Scenario{
var SimulationModelA = SimulationModel{ var SimulationModelA = SimulationModel{
Name: "SimulationModel_A", Name: "SimulationModel_A",
OutputLength: 1,
InputLength: 1,
StartParameters: postgres.Jsonb{startParametersA}, StartParameters: postgres.Jsonb{startParametersA},
} }
var SimulationModelA_response = SimulationModelResponse{
ID: 1,
Name: SimulationModelA.Name,
InputLength: SimulationModelA.InputLength,
OutputLength: SimulationModelA.OutputLength,
StartParameters: SimulationModelA.StartParameters,
}
var SimulationModelB = SimulationModel{ var SimulationModelB = SimulationModel{
Name: "SimulationModel_B", Name: "SimulationModel_B",
OutputLength: 1,
InputLength: 1,
StartParameters: postgres.Jsonb{startParametersB}, StartParameters: postgres.Jsonb{startParametersB},
} }
var SimulationModelB_response = SimulationModelResponse{
ID: 2,
Name: SimulationModelB.Name,
InputLength: SimulationModelB.InputLength,
OutputLength: SimulationModelB.OutputLength,
StartParameters: SimulationModelB.StartParameters,
}
var SimulationModelC = SimulationModel{
Name: "SimulationModel_C",
OutputLength: 1,
InputLength: 1,
StartParameters: postgres.Jsonb{startParametersC},
}
var SimulationModelC_response = SimulationModelResponse{
ID: 3,
Name: SimulationModelC.Name,
InputLength: SimulationModelC.InputLength,
OutputLength: SimulationModelC.OutputLength,
ScenarioID: SimulationModelC.ScenarioID,
SimulatorID: SimulationModelC.SimulatorID,
StartParameters: SimulationModelC.StartParameters,
}
var SimulationModelCUpdated = SimulationModel{
Name: "SimulationModel_CUpdated",
OutputLength: SimulationModelC.OutputLength,
InputLength: SimulationModelC.InputLength,
ScenarioID: SimulationModelC.ScenarioID,
SimulatorID: 2,
StartParameters: SimulationModelC.StartParameters,
InputMapping: SimulationModelC.InputMapping,
OutputMapping: SimulationModelC.OutputMapping,
}
var SimulationModelCUpdated_response = SimulationModelResponse{
ID: 3,
Name: SimulationModelCUpdated.Name,
InputLength: SimulationModelCUpdated.InputLength,
OutputLength: SimulationModelCUpdated.OutputLength,
ScenarioID: SimulationModelCUpdated.ScenarioID,
SimulatorID: SimulationModelCUpdated.SimulatorID,
StartParameters: SimulationModelCUpdated.StartParameters,
}
// Signals // Signals
var OutSignalA = Signal{ var OutSignalA = Signal{

View file

@ -152,8 +152,9 @@ func CompareResponse(resp *bytes.Buffer, expected interface{}) error {
} }
// Compare // Compare
opts := jsondiff.DefaultConsoleOptions() opts := jsondiff.DefaultConsoleOptions()
diff, _ := jsondiff.Compare(resp.Bytes(), expectedBytes, &opts) diff, text := jsondiff.Compare(resp.Bytes(), expectedBytes, &opts)
if diff.String() != "FullMatch" && diff.String() != "SupersetMatch" { if diff.String() != "FullMatch" && diff.String() != "SupersetMatch" {
fmt.Println(text)
return fmt.Errorf("Response: Expected \"%v\". Got \"%v\".", return fmt.Errorf("Response: Expected \"%v\". Got \"%v\".",
"(FullMatch OR SupersetMatch)", diff.String()) "(FullMatch OR SupersetMatch)", diff.String())
} }

View file

@ -1,6 +1,7 @@
package simulationmodel package simulationmodel
import ( import (
"fmt"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -22,11 +23,10 @@ func RegisterSimulationModelEndpoints(r *gin.RouterGroup) {
// @ID getSimulationModels // @ID getSimulationModels
// @Produce json // @Produce json
// @Tags models // @Tags models
// @Success 200 {array} common.SimulationModelResponse "Array of models to which belong to scenario" // @Success 200 {object} docs.ResponseSimulationModels "Simulation models which belong to scenario"
// @Failure 401 "Unauthorized Access" // @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 403 "Access forbidden." // @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 404 "Not found" // @Failure 500 {object} docs.ResponseError "Internal server error"
// @Failure 500 "Internal server error"
// @Param scenarioID query int true "Scenario ID" // @Param scenarioID query int true "Scenario ID"
// @Router /models [get] // @Router /models [get]
func getSimulationModels(c *gin.Context) { func getSimulationModels(c *gin.Context) {
@ -43,9 +43,8 @@ func getSimulationModels(c *gin.Context) {
return return
} }
serializer := common.SimulationModelsSerializer{c, models}
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"models": serializer.Response(), "models": models,
}) })
} }
@ -55,45 +54,54 @@ func getSimulationModels(c *gin.Context) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Tags models // @Tags models
// @Param inputSimulationModel body common.ResponseMsgSimulationModel true "Simulation model to be added incl. IDs of scenario and simulator" // @Param inputSimulationModel body simulationmodel.validNewSimulationModel true "Simulation model to be added incl. IDs of scenario and simulator"
// @Success 200 "OK." // @Success 200 {object} docs.ResponseSimulationModel "simulation model that was added"
// @Failure 401 "Unauthorized Access" // @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 403 "Access forbidden." // @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 404 "Not found" // @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 "Internal server error" // @Failure 500 {object} docs.ResponseError "Internal server error"
// @Router /models [post] // @Router /models [post]
func addSimulationModel(c *gin.Context) { func addSimulationModel(c *gin.Context) {
var newModelData common.ResponseMsgSimulationModel // Bind the request to JSON
err := c.BindJSON(&newModelData) var req addSimulationModelRequest
err := c.ShouldBindJSON(&req)
if err != nil { if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg, "success": false,
"message": "Bad request. Error binding form data to JSON: " + err.Error(),
}) })
return return
} }
var newModel SimulationModel // validate the request
newModel.ID = newModelData.SimulationModel.ID if err = req.validate(); err != nil {
newModel.Name = newModelData.SimulationModel.Name c.JSON(http.StatusUnprocessableEntity, gin.H{
newModel.SimulatorID = newModelData.SimulationModel.SimulatorID "success": false,
newModel.ScenarioID = newModelData.SimulationModel.ScenarioID "message": fmt.Sprintf("%v", err),
newModel.StartParameters = newModelData.SimulationModel.StartParameters })
newModel.OutputLength = 0 return
newModel.InputLength = 0 }
ok, _ := scenario.CheckPermissions(c, common.Create, "body", int(newModel.ScenarioID)) // Create the new simulation model from the request
newSimulationModel := req.createSimulationModel()
// check access to the scenario
ok, _ := scenario.CheckPermissions(c, common.Create, "body", int(newSimulationModel.ScenarioID))
if !ok { if !ok {
return return
} }
err = newModel.addToScenario() // add the new simulation model to the scenario
if common.ProvideErrorResponse(c, err) == false { err = newSimulationModel.addToScenario()
c.JSON(http.StatusOK, gin.H{ if err != nil {
"message": "OK.", common.ProvideErrorResponse(c, err)
}) return
} }
c.JSON(http.StatusOK, gin.H{
"model": newSimulationModel.SimulationModel,
})
} }
// updateSimulationModel godoc // updateSimulationModel godoc
@ -102,37 +110,61 @@ func addSimulationModel(c *gin.Context) {
// @Tags models // @Tags models
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param inputSimulationModel body common.ResponseMsgSimulationModel true "Simulation model to be updated" // @Param inputSimulationModel body simulationmodel.validUpdatedSimulationModel true "Simulation model to be updated"
// @Success 200 "OK." // @Success 200 {object} docs.ResponseSimulationModel "simulation model that was added"
// @Failure 401 "Unauthorized Access" // @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 403 "Access forbidden." // @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 404 "Not found" // @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 "Internal server error" // @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param modelID path int true "Model ID" // @Param modelID path int true "Model ID"
// @Router /models/{modelID} [put] // @Router /models/{modelID} [put]
func updateSimulationModel(c *gin.Context) { func updateSimulationModel(c *gin.Context) {
ok, m := CheckPermissions(c, common.Update, "path", -1) ok, oldSimulationModel := CheckPermissions(c, common.Update, "path", -1)
if !ok { if !ok {
return return
} }
var modifiedModel common.ResponseMsgSimulationModel var req updateSimulationModelRequest
err := c.BindJSON(&modifiedModel) err := c.BindJSON(&req)
if err != nil { if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg, "success": false,
"message": "Bad request. Error binding form data to JSON: " + err.Error(),
}) })
return return
} }
err = m.Update(modifiedModel.SimulationModel) // Validate the request
if common.ProvideErrorResponse(c, err) == false { if err := req.validate(); err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"message": "OK.", "success": false,
"message": fmt.Sprintf("%v", err),
}) })
return
} }
// Create the updatedSimulationModel from oldSimulationModel
updatedSimulationModel, err := req.updatedSimulationModel(oldSimulationModel)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": fmt.Sprintf("%v", err),
})
return
}
// Finally, update the simulation model
err = oldSimulationModel.Update(updatedSimulationModel)
if err != nil {
common.ProvideErrorResponse(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"model": updatedSimulationModel.SimulationModel,
})
} }
// getSimulationModel godoc // getSimulationModel godoc
@ -140,11 +172,11 @@ func updateSimulationModel(c *gin.Context) {
// @ID getSimulationModel // @ID getSimulationModel
// @Tags models // @Tags models
// @Produce json // @Produce json
// @Success 200 {object} common.SimulationModelResponse "Requested simulation model." // @Success 200 {object} docs.ResponseSimulationModel "simulation model that was requested"
// @Failure 401 "Unauthorized Access" // @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 403 "Access forbidden." // @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 404 "Not found" // @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 "Internal server error" // @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param modelID path int true "Model ID" // @Param modelID path int true "Model ID"
// @Router /models/{modelID} [get] // @Router /models/{modelID} [get]
func getSimulationModel(c *gin.Context) { func getSimulationModel(c *gin.Context) {
@ -154,9 +186,8 @@ func getSimulationModel(c *gin.Context) {
return return
} }
serializer := common.SimulationModelSerializer{c, m.SimulationModel}
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"model": serializer.Response(), "model": m.SimulationModel,
}) })
} }
@ -165,11 +196,11 @@ func getSimulationModel(c *gin.Context) {
// @ID deleteSimulationModel // @ID deleteSimulationModel
// @Tags models // @Tags models
// @Produce json // @Produce json
// @Success 200 "OK." // @Success 200 {object} docs.ResponseSimulationModel "simulation model that was deleted"
// @Failure 401 "Unauthorized Access" // @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 403 "Access forbidden." // @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 404 "Not found" // @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 "Internal server error" // @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param modelID path int true "Model ID" // @Param modelID path int true "Model ID"
// @Router /models/{modelID} [delete] // @Router /models/{modelID} [delete]
func deleteSimulationModel(c *gin.Context) { func deleteSimulationModel(c *gin.Context) {
@ -185,6 +216,6 @@ func deleteSimulationModel(c *gin.Context) {
} }
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"message": "OK.", "model": m.SimulationModel,
}) })
} }

View file

@ -1,8 +1,6 @@
package simulationmodel package simulationmodel
import ( import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "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/scenario"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator"
@ -22,7 +20,7 @@ func (m *SimulationModel) ByID(id uint) error {
db := common.GetDB() db := common.GetDB()
err := db.Find(m, id).Error err := db.Find(m, id).Error
if err != nil { if err != nil {
return fmt.Errorf("Simulation Model with id=%v does not exist", id) return err
} }
return nil return nil
} }
@ -55,7 +53,7 @@ func (m *SimulationModel) addToScenario() error {
return err return err
} }
func (m *SimulationModel) Update(modifiedSimulationModel common.SimulationModelResponse) error { func (m *SimulationModel) Update(modifiedSimulationModel SimulationModel) error {
db := common.GetDB() db := common.GetDB()
if m.SimulatorID != modifiedSimulationModel.SimulatorID { if m.SimulatorID != modifiedSimulationModel.SimulatorID {
@ -82,12 +80,15 @@ func (m *SimulationModel) Update(modifiedSimulationModel common.SimulationModelR
} }
} }
if m.ScenarioID != modifiedSimulationModel.ScenarioID {
// TODO do we allow this case?
}
err := db.Model(m).Updates(map[string]interface{}{ err := db.Model(m).Updates(map[string]interface{}{
"Name": modifiedSimulationModel.Name, "Name": modifiedSimulationModel.Name,
"OutputLength": modifiedSimulationModel.OutputLength,
"InputLength": modifiedSimulationModel.InputLength,
"StartParameters": modifiedSimulationModel.StartParameters, "StartParameters": modifiedSimulationModel.StartParameters,
"SimulatorID": modifiedSimulationModel.SimulatorID, "SimulatorID": modifiedSimulationModel.SimulatorID,
//"ScenarioID": modifiedSimulationModel.ScenarioID,
}).Error }).Error
return err return err

View file

@ -17,7 +17,10 @@ func CheckPermissions(c *gin.Context, operation common.CRUD, modelIDSource strin
err := common.ValidateRole(c, common.ModelSimulationModel, operation) err := common.ValidateRole(c, common.ModelSimulationModel, operation)
if err != nil { if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).") c.JSON(http.StatusUnprocessableEntity, gin.H{
"success": false,
"message": fmt.Sprintf("Access denied (role validation failed): %v", err),
})
return false, m return false, m
} }
@ -27,16 +30,17 @@ func CheckPermissions(c *gin.Context, operation common.CRUD, modelIDSource strin
if err != nil { if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of modelID path parameter") errormsg := fmt.Sprintf("Bad request. No or incorrect format of modelID path parameter")
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg, "success": false,
"message": errormsg,
}) })
return false, m return false, m
} }
} else if modelIDSource == "query" { } else if modelIDSource == "query" {
modelID, err = strconv.Atoi(c.Request.URL.Query().Get("modelID")) modelID, err = strconv.Atoi(c.Request.URL.Query().Get("modelID"))
if err != nil { if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of modelID query parameter")
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg, "success": false,
"message": fmt.Sprintf("Bad request. No or incorrect format of modelID query parameter"),
}) })
return false, m return false, m
} }

View file

@ -1,65 +1,364 @@
package simulationmodel package simulationmodel
import ( import (
"encoding/json" "fmt"
"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/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user" "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 var router *gin.Engine
func TestSimulationModelEndpoints(t *testing.T) { var db *gorm.DB
var token string type SimulationModelRequest struct {
Name string `json:"name,omitempty"`
ScenarioID uint `json:"scenarioID,omitempty"`
SimulatorID uint `json:"simulatorID,omitempty"`
StartParameters postgres.Jsonb `json:"startParameters,omitempty"`
}
var myModels = []common.SimulationModelResponse{common.SimulationModelA_response, common.SimulationModelB_response} type SimulatorRequest struct {
var msgModels = common.ResponseMsgSimulationModels{SimulationModels: myModels} UUID string `json:"uuid,omitempty"`
var msgModel = common.ResponseMsgSimulationModel{SimulationModel: common.SimulationModelC_response} Host string `json:"host,omitempty"`
var msgModelupdated = common.ResponseMsgSimulationModel{SimulationModel: common.SimulationModelCUpdated_response} Modeltype string `json:"modelType,omitempty"`
State string `json:"state,omitempty"`
Properties postgres.Jsonb `json:"properties,omitempty"`
}
db := common.DummyInitDB() type ScenarioRequest struct {
Name string `json:"name,omitempty"`
Running bool `json:"running,omitempty"`
StartParameters postgres.Jsonb `json:"startParameters,omitempty"`
}
func addScenarioAndSimulator() (scenarioID uint, simulatorID uint) {
// authenticate as admin
token, _ := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.AdminCredentials)
// POST $newSimulatorA
newSimulatorA := SimulatorRequest{
UUID: common.SimulatorA.UUID,
Host: common.SimulatorA.Host,
Modeltype: common.SimulatorA.Modeltype,
State: common.SimulatorA.State,
Properties: common.SimulatorA.Properties,
}
_, resp, _ := common.NewTestEndpoint(router, token,
"/api/simulators", "POST", common.KeyModels{"simulator": newSimulatorA})
// Read newSimulator's ID from the response
newSimulatorID, _ := common.GetResponseID(resp)
// authenticate as normal user
token, _ = common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
// POST $newScenario
newScenario := ScenarioRequest{
Name: common.ScenarioA.Name,
Running: common.ScenarioA.Running,
StartParameters: common.ScenarioA.StartParameters,
}
_, resp, _ = common.NewTestEndpoint(router, token,
"/api/scenarios", "POST", common.KeyModels{"scenario": newScenario})
// Read newScenario's ID from the response
newScenarioID, _ := common.GetResponseID(resp)
return uint(newScenarioID), uint(newSimulatorID)
}
func TestMain(m *testing.M) {
db = common.DummyInitDB()
defer db.Close() defer db.Close()
common.DummyPopulateDB(db)
router := gin.Default() router = gin.Default()
api := router.Group("/api") api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.RegisterAuthenticate(api.Group("/authenticate")) user.RegisterAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true)) api.Use(user.Authentication(true))
RegisterSimulationModelEndpoints(api.Group("/models")) RegisterSimulationModelEndpoints(api.Group("/models"))
// scenario endpoints required here to first add a scenario to the DB
// that can be associated with a new simulation model
scenario.RegisterScenarioEndpoints(api.Group("/scenarios"))
// simulator endpoints required here to first add a simulator to the DB
// that can be associated with a new simulation model
simulator.RegisterSimulatorEndpoints(api.Group("/simulators"))
credjson, _ := json.Marshal(common.CredUser) os.Exit(m.Run())
msgOKjson, _ := json.Marshal(common.MsgOK) }
msgModelsjson, _ := json.Marshal(msgModels)
msgModeljson, _ := json.Marshal(msgModel)
msgModelupdatedjson, _ := json.Marshal(msgModelupdated)
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200) func TestAddSimulationModel(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// test GET models // prepare the content of the DB for testing
common.TestEndpoint(t, router, token, "/api/models?scenarioID=1", "GET", nil, 200, msgModelsjson) // by adding a scenario and a simulator to the DB
// using the respective endpoints of the API
scenarioID, simulatorID := addScenarioAndSimulator()
// test POST models // authenticate as normal user
common.TestEndpoint(t, router, token, "/api/models", "POST", msgModeljson, 200, msgOKjson) token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
// test GET models/:ModelID to check if previous POST worked correctly // test POST models/ $newSimulationModel
common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, msgModeljson) newSimulationModel := SimulationModelRequest{
Name: common.SimulationModelA.Name,
ScenarioID: scenarioID,
SimulatorID: simulatorID,
StartParameters: common.SimulationModelA.StartParameters,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/models", "POST", common.KeyModels{"model": newSimulationModel})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// test PUT models/:ModelID // Compare POST's response with the newSimulationModel
common.TestEndpoint(t, router, token, "/api/models/3", "PUT", msgModelupdatedjson, 200, msgOKjson) err = common.CompareResponse(resp, common.KeyModels{"model": newSimulationModel})
common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, msgModelupdatedjson) assert.NoError(t, err)
// test DELETE models/:ModelID // Read newSimulationModel's ID from the response
common.TestEndpoint(t, router, token, "/api/models/3", "DELETE", nil, 200, msgOKjson) newSimulationModelID, err := common.GetResponseID(resp)
common.TestEndpoint(t, router, token, "/api/models?scenarioID=1", "GET", nil, 200, msgModelsjson) assert.NoError(t, err)
// TODO add testing for other return codes // Get the newSimulationModel
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/models/%v", newSimulationModelID), "GET", nil)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare GET's response with the newSimulationModel
err = common.CompareResponse(resp, common.KeyModels{"model": newSimulationModel})
assert.NoError(t, err)
// try to POST a malformed simulation model
// Required fields are missing
malformedNewSimulationModel := SimulationModelRequest{
Name: "ThisIsAMalformedRequest",
}
// this should NOT work and return a unprocessable entity 442 status code
code, resp, err = common.NewTestEndpoint(router, token,
"/api/models", "POST", common.KeyModels{"model": malformedNewSimulationModel})
assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
} }
func TestUpdateSimulationModel(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// prepare the content of the DB for testing
// by adding a scenario and a simulator to the DB
// using the respective endpoints of the API
scenarioID, simulatorID := addScenarioAndSimulator()
// authenticate as normal user
token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
// test POST models/ $newSimulationModel
newSimulationModel := SimulationModelRequest{
Name: common.SimulationModelA.Name,
ScenarioID: scenarioID,
SimulatorID: simulatorID,
StartParameters: common.SimulationModelA.StartParameters,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/models", "POST", common.KeyModels{"model": newSimulationModel})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newSimulationModel's ID from the response
newSimulationModelID, err := common.GetResponseID(resp)
assert.NoError(t, err)
updatedSimulationModel := SimulationModelRequest{
Name: common.SimulationModelB.Name,
StartParameters: common.SimulationModelB.StartParameters,
}
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/models/%v", newSimulationModelID), "PUT", common.KeyModels{"model": updatedSimulationModel})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare PUT's response with the updatedSimulationModel
err = common.CompareResponse(resp, common.KeyModels{"model": updatedSimulationModel})
assert.NoError(t, err)
// Get the updatedSimulationModel
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/models/%v", newSimulationModelID), "GET", nil)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare GET's response with the updatedSimulationModel
err = common.CompareResponse(resp, common.KeyModels{"model": updatedSimulationModel})
assert.NoError(t, err)
// try to update a simulation model that does not exist (should return not found 404 status code)
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/models/%v", newSimulationModelID+1), "PUT", common.KeyModels{"scenario": updatedSimulationModel})
assert.NoError(t, err)
assert.Equalf(t, 404, code, "Response body: \n%v\n", resp)
}
func TestDeleteSimulationModel(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// prepare the content of the DB for testing
// by adding a scenario and a simulator to the DB
// using the respective endpoints of the API
scenarioID, simulatorID := addScenarioAndSimulator()
// authenticate as normal user
token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
// test POST models/ $newSimulationModel
newSimulationModel := SimulationModelRequest{
Name: common.SimulationModelA.Name,
ScenarioID: scenarioID,
SimulatorID: simulatorID,
StartParameters: common.SimulationModelA.StartParameters,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/models", "POST", common.KeyModels{"model": newSimulationModel})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newSimulationModel's ID from the response
newSimulationModelID, err := common.GetResponseID(resp)
assert.NoError(t, err)
// Count the number of all the simulation models returned for scenario
initialNumber, err := common.LengthOfResponse(router, token,
fmt.Sprintf("/api/models?scenarioID=%v", scenarioID), "GET", nil)
assert.NoError(t, err)
// Delete the added newSimulationModel
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/models/%v", newSimulationModelID), "DELETE", nil)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare DELETE's response with the newSimulationModel
err = common.CompareResponse(resp, common.KeyModels{"model": newSimulationModel})
assert.NoError(t, err)
// Again count the number of all the simulation models returned
finalNumber, err := common.LengthOfResponse(router, token,
fmt.Sprintf("/api/models?scenarioID=%v", scenarioID), "GET", nil)
assert.NoError(t, err)
assert.Equal(t, initialNumber-1, finalNumber)
}
func TestGetAllSimulationModelsOfScenario(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// prepare the content of the DB for testing
// by adding a scenario and a simulator to the DB
// using the respective endpoints of the API
scenarioID, simulatorID := addScenarioAndSimulator()
// authenticate as normal user
token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
// test POST models/ $newSimulationModel
newSimulationModel := SimulationModelRequest{
Name: common.SimulationModelA.Name,
ScenarioID: scenarioID,
SimulatorID: simulatorID,
StartParameters: common.SimulationModelA.StartParameters,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/models", "POST", common.KeyModels{"model": newSimulationModel})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Count the number of all the simulation models returned for scenario
NumberOfSimulationModels, err := common.LengthOfResponse(router, token,
fmt.Sprintf("/api/models?scenarioID=%v", scenarioID), "GET", nil)
assert.NoError(t, err)
assert.Equal(t, 1, NumberOfSimulationModels)
}
//// Test /models endpoints
//func TestSimulationModelEndpoints(t *testing.T) {
//
// var token string
//
// var myModels = []common.SimulationModelResponse{common.SimulationModelA_response, common.SimulationModelB_response}
// var msgModels = common.ResponseMsgSimulationModels{SimulationModels: myModels}
// var msgModel = common.ResponseMsgSimulationModel{SimulationModel: common.SimulationModelC_response}
// var msgModelupdated = common.ResponseMsgSimulationModel{SimulationModel: common.SimulationModelCUpdated_response}
//
// db := common.DummyInitDB()
// defer db.Close()
// common.DummyPopulateDB(db)
//
// router := gin.Default()
// api := router.Group("/api")
//
// // All endpoints require authentication except when someone wants to
// // login (POST /authenticate)
// user.RegisterAuthenticate(api.Group("/authenticate"))
//
// api.Use(user.Authentication(true))
//
// RegisterSimulationModelEndpoints(api.Group("/models"))
//
// credjson, _ := json.Marshal(common.CredUser)
// msgOKjson, _ := json.Marshal(common.MsgOK)
// msgModelsjson, _ := json.Marshal(msgModels)
// msgModeljson, _ := json.Marshal(msgModel)
// msgModelupdatedjson, _ := json.Marshal(msgModelupdated)
//
// token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
//
// // test GET models
// common.TestEndpoint(t, router, token, "/api/models?scenarioID=1", "GET", nil, 200, msgModelsjson)
//
// // test POST models
// common.TestEndpoint(t, router, token, "/api/models", "POST", msgModeljson, 200, msgOKjson)
//
// // test GET models/:ModelID to check if previous POST worked correctly
// common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, msgModeljson)
//
// // test PUT models/:ModelID
// common.TestEndpoint(t, router, token, "/api/models/3", "PUT", msgModelupdatedjson, 200, msgOKjson)
// common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, msgModelupdatedjson)
//
// // test DELETE models/:ModelID
// common.TestEndpoint(t, router, token, "/api/models/3", "DELETE", nil, 200, msgOKjson)
// common.TestEndpoint(t, router, token, "/api/models?scenarioID=1", "GET", nil, 200, msgModelsjson)
//
// // TODO add testing for other return codes
//
//}

View file

@ -0,0 +1,80 @@
package simulationmodel
import (
"encoding/json"
"github.com/jinzhu/gorm/dialects/postgres"
"github.com/nsf/jsondiff"
"gopkg.in/go-playground/validator.v9"
)
var validate *validator.Validate
type validNewSimulationModel struct {
Name string `form:"Name" validate:"required"`
ScenarioID uint `form:"ScenarioID" validate:"required"`
SimulatorID uint `form:"SimulatorID" validate:"required"`
StartParameters postgres.Jsonb `form:"StartParameters" validate:"required"`
}
type validUpdatedSimulationModel struct {
Name string `form:"Name" validate:"omitempty"`
SimulatorID uint `form:"SimulatorID" validate:"omitempty"`
StartParameters postgres.Jsonb `form:"StartParameters" validate:"omitempty"`
}
type addSimulationModelRequest struct {
validNewSimulationModel `json:"model"`
}
type updateSimulationModelRequest struct {
validUpdatedSimulationModel `json:"model"`
}
func (r *addSimulationModelRequest) validate() error {
validate = validator.New()
errs := validate.Struct(r)
return errs
}
func (r *validUpdatedSimulationModel) validate() error {
validate = validator.New()
errs := validate.Struct(r)
return errs
}
func (r *addSimulationModelRequest) createSimulationModel() SimulationModel {
var s SimulationModel
s.Name = r.Name
s.ScenarioID = r.ScenarioID
s.SimulatorID = r.SimulatorID
s.StartParameters = r.StartParameters
return s
}
func (r *updateSimulationModelRequest) updatedSimulationModel(oldSimulationModel SimulationModel) (SimulationModel, error) {
// Use the old SimulationModel as a basis for the updated Simulation model
s := oldSimulationModel
if r.Name != "" {
s.Name = r.Name
}
if r.SimulatorID != 0 {
s.SimulatorID = r.SimulatorID
}
// only update Params if not empty
var emptyJson postgres.Jsonb
// Serialize empty json and params
emptyJson_ser, _ := json.Marshal(emptyJson)
startParams_ser, _ := json.Marshal(r.StartParameters)
opts := jsondiff.DefaultConsoleOptions()
diff, _ := jsondiff.Compare(emptyJson_ser, startParams_ser, &opts)
if diff.String() != "FullMatch" {
s.StartParameters = r.StartParameters
}
return s, nil
}