fix bugs in scenario deletion, improve testing of scenario deletion

This commit is contained in:
Sonja Happ 2022-02-08 17:21:46 +01:00
parent 5c41175890
commit c00cb406eb
4 changed files with 418 additions and 43 deletions

View file

@ -22,6 +22,7 @@
package component_configuration
import (
"github.com/jinzhu/gorm"
"log"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
@ -126,12 +127,6 @@ func (m *ComponentConfiguration) delete() error {
return err
}
var ic database.InfrastructureComponent
err = db.Find(&ic, m.ICID).Error
if err != nil {
return err
}
// remove association between ComponentConfiguration and Scenario
log.Println("DELETE ASSOCIATION to scenario ", so.ID, "(name="+so.Name+")")
err = db.Model(&so).Association("ComponentConfigurations").Delete(m).Error
@ -139,13 +134,6 @@ func (m *ComponentConfiguration) delete() error {
return err
}
// remove association between Infrastructure component and config
log.Println("DELETE ASSOCIATION to IC ", ic.ID, "(name="+ic.Name+")")
err = db.Model(&ic).Association("ComponentConfigurations").Delete(m).Error
if err != nil {
return err
}
// Get Signals of InputMapping and delete them
var InputMappingSignals []database.Signal
err = db.Model(m).Related(&InputMappingSignals, "InputMapping").Error
@ -174,19 +162,36 @@ func (m *ComponentConfiguration) delete() error {
}
}
var ic database.InfrastructureComponent
err = db.Find(&ic, m.ICID).Error
if err == nil {
// remove association between Infrastructure component and config
log.Println("DELETE ASSOCIATION to IC ", ic.ID, "(name="+ic.Name+")")
err = db.Model(&ic).Association("ComponentConfigurations").Delete(m).Error
if err != nil {
return err
}
// if IC has state gone and there is no component configuration associated with it: delete IC
no_configs := db.Model(&ic).Association("ComponentConfigurations").Count()
if no_configs == 0 && ic.State == "gone" {
log.Println("DELETE IC with state gone, last component config deleted", ic.UUID)
err = db.Delete(&ic).Error
return err
}
} else {
if err == gorm.ErrRecordNotFound {
log.Printf("SKIPPING IC association removal, IC with id=%v not found\n", m.ICID)
} else {
return err
}
}
// delete component configuration
err = db.Delete(m).Error
if err != nil {
return err
}
// if IC has state gone and there is no component configuration associated with it: delete IC
no_configs := db.Model(&ic).Association("ComponentConfigurations").Count()
if no_configs == 0 && ic.State == "gone" {
log.Println("DELETE IC with state gone, last component config deleted", ic.UUID)
err = db.Delete(&ic).Error
return err
}
return nil
}

View file

@ -242,7 +242,9 @@ func deleteScenario(c *gin.Context) {
if len(errs) > 0 {
var errorString = "DB errors:"
for _, e := range errs {
errorString += ", " + e.Error()
if e != nil {
errorString += ", " + e.Error()
}
}
helper.InternalServerError(c, errorString)
return

View file

@ -210,18 +210,6 @@ func (s *Scenario) delete() []error {
}
for _, config := range configs {
var ic database.InfrastructureComponent
err = db.Find(&ic, config.ICID).Error
if err != nil {
errs = append(errs, err)
}
// remove association between Infrastructure component and config
log.Println("DELETE ASSOCIATION to IC ", ic.ID, "(name="+ic.Name+")")
err = db.Model(&ic).Association("ComponentConfigurations").Delete(&config).Error
if err != nil {
errs = append(errs, err)
}
// Get Signals of InputMapping and delete them
var InputMappingSignals []database.Signal
@ -251,20 +239,40 @@ func (s *Scenario) delete() []error {
}
}
var ic database.InfrastructureComponent
err = db.Find(&ic, config.ICID).Error
if err == nil {
// remove association between Infrastructure component and config
log.Println("DELETE ASSOCIATION to IC ", ic.ID, "(name="+ic.Name+")")
err = db.Model(&ic).Association("ComponentConfigurations").Delete(&config).Error
if err != nil {
errs = append(errs, err)
}
// if IC has state gone and there is no component configuration associated with it: delete IC
no_configs := db.Model(&ic).Association("ComponentConfigurations").Count()
if no_configs == 0 && ic.State == "gone" {
log.Println("DELETE IC with state gone, last component config deleted", ic.UUID)
err = db.Delete(&ic).Error
if err != nil {
errs = append(errs, err)
}
}
} else {
if err == gorm.ErrRecordNotFound {
log.Printf("SKIPPING IC association removal, IC with id=%v not found\n", config.ICID)
} else {
errs = append(errs, err)
}
}
// delete component configuration
log.Println("DELETE component config ", config.ID, "(name="+config.Name+")")
err = db.Delete(&config).Error
if err != nil {
errs = append(errs, err)
}
// if IC has state gone and there is no component configuration associated with it: delete IC
no_configs := db.Model(&ic).Association("ComponentConfigurations").Count()
if no_configs == 0 && ic.State == "gone" {
log.Println("DELETE IC with state gone, last component config deleted", ic.UUID)
err = db.Delete(&ic).Error
errs = append(errs, err)
}
}
// delete scenario from all users and vice versa

View file

@ -22,8 +22,22 @@
package scenario
import (
"bytes"
"encoding/json"
"fmt"
component_configuration "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/component-configuration"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/dashboard"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/file"
infrastructure_component "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/result"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/signal"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/widget"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"testing"
@ -88,6 +102,13 @@ func TestMain(m *testing.M) {
// user endpoints required to set user to inactive
user.RegisterUserEndpoints(api.Group("/users"))
file.RegisterFileEndpoints(api.Group("/files"))
component_configuration.RegisterComponentConfigurationEndpoints(api.Group("/configs"))
signal.RegisterSignalEndpoints(api.Group("/signals"))
dashboard.RegisterDashboardEndpoints(api.Group("/dashboards"))
widget.RegisterWidgetEndpoints(api.Group("/widgets"))
result.RegisterResultEndpoints(api.Group("/results"))
infrastructure_component.RegisterICEndpoints(api.Group("/ic"))
RegisterScenarioEndpoints(api.Group("/scenarios"))
os.Exit(m.Run())
@ -414,8 +435,14 @@ func TestDeleteScenario(t *testing.T) {
database.MigrateModels()
assert.NoError(t, database.AddTestUsers())
// authenticate as admin user to add ICs
token, err := helper.AuthenticateForTest(router, database.AdminCredentials)
assert.NoError(t, err)
ic1ID, ic2ID := addICs(t, token)
// authenticate as normal user
token, err := helper.AuthenticateForTest(router, database.UserACredentials)
token, err = helper.AuthenticateForTest(router, database.UserACredentials)
assert.NoError(t, err)
// test POST scenarios/ $newScenario
@ -429,16 +456,22 @@ func TestDeleteScenario(t *testing.T) {
assert.NoError(t, err)
// add file to the scenario
fileID := addFile(t, token, newScenarioID)
// add result to the scenario
resultID := addResult(t, token, newScenarioID)
// add dashboard to the scenario
dashboardID := addDashboard(t, token, newScenarioID)
// add widget to the dashboard
widgetID := addWidget(t, token, dashboardID)
// add component config to the scenario
componentConfig1ID, componentConfig2ID := addComponentConfigs(t, token, newScenarioID, ic1ID, ic2ID)
// add signal to the component config
signalInID, signalOutID := addSignals(t, token, componentConfig1ID)
// add guest user to new scenario
code, resp, err = helper.TestEndpoint(router, token,
@ -482,6 +515,10 @@ func TestDeleteScenario(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, finalNumber, initialNumber-1)
// TODO check if dashboard, result, file, etc still exists (use API)
// TODO make sure everything is properly deleted
log.Println(fileID, resultID, dashboardID, widgetID, componentConfig1ID, componentConfig2ID, signalInID, signalOutID, ic1ID, ic2ID)
}
func TestAddUserToScenario(t *testing.T) {
@ -726,3 +763,326 @@ func TestRemoveUserFromScenario(t *testing.T) {
assert.Equalf(t, 404, code, "Response body: \n%v\n", resp)
}
func addFile(t *testing.T, token string, scenarioID int) int {
// create a testfile.txt in local folder
c1 := []byte("This is my testfile\n")
err := ioutil.WriteFile("testfile.txt", c1, 0644)
assert.NoError(t, err)
// test POST files
bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)
fileWriter, err := bodyWriter.CreateFormFile("file", "testuploadfile.txt")
assert.NoError(t, err, "writing to buffer")
// open file handle
fh, err := os.Open("testfile.txt")
assert.NoError(t, err, "opening file")
defer fh.Close()
// io copy
_, err = io.Copy(fileWriter, fh)
assert.NoError(t, err, "IO copy")
contentType := bodyWriter.FormDataContentType()
bodyWriter.Close()
// Create the request
w := httptest.NewRecorder()
req, err := http.NewRequest("POST", fmt.Sprintf("/api/v2/files?scenarioID=%v", scenarioID), bodyBuf)
assert.NoError(t, err, "create request")
req.Header.Set("Content-Type", contentType)
req.Header.Add("Authorization", "Bearer "+token)
router.ServeHTTP(w, req)
assert.Equalf(t, 200, w.Code, "Response body: \n%v\n", w.Body)
newFileID, err := helper.GetResponseID(w.Body)
assert.NoError(t, err)
return newFileID
}
func addResult(t *testing.T, token string, scenarioID int) int {
type ResultRequest struct {
Description string `json:"description,omitempty"`
ScenarioID uint `json:"scenarioID,omitempty"`
ConfigSnapshots postgres.Jsonb `json:"configSnapshots,omitempty"`
}
var newResult = ResultRequest{
Description: "This is a test result.",
}
configSnapshot1 := json.RawMessage(`{"configs": [ {"Name" : "conf1", "scenarioID" : 1}, {"Name" : "conf2", "scenarioID" : 1}]}`)
confSnapshots := postgres.Jsonb{
RawMessage: configSnapshot1,
}
newResult.ScenarioID = uint(scenarioID)
newResult.ConfigSnapshots = confSnapshots
code, resp, err := helper.TestEndpoint(router, token,
"/api/v2/results", "POST", helper.KeyModels{"result": newResult})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newResults's ID from the response
newResultID, err := helper.GetResponseID(resp)
assert.NoError(t, err)
return newResultID
}
func addDashboard(t *testing.T, token string, scenarioID int) int {
type DashboardRequest struct {
Name string `json:"name,omitempty"`
Grid int `json:"grid,omitempty"`
Height int `json:"height,omitempty"`
ScenarioID uint `json:"scenarioID,omitempty"`
}
var newDashboard = DashboardRequest{
Name: "Dashboard_A",
Grid: 15,
}
// test POST dashboards/ $newDashboad
newDashboard.ScenarioID = uint(scenarioID)
code, resp, err := helper.TestEndpoint(router, token,
"/api/v2/dashboards", "POST", helper.KeyModels{"dashboard": newDashboard})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newDashboard's ID from the response
newDashboardID, err := helper.GetResponseID(resp)
assert.NoError(t, err)
return newDashboardID
}
func addWidget(t *testing.T, token string, dashboardID int) int {
type WidgetRequest struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Width uint `json:"width,omitempty"`
Height uint `json:"height,omitempty"`
MinWidth uint `json:"minWidth,omitempty"`
MinHeight uint `json:"minHeight,omitempty"`
X int `json:"x,omitempty"`
Y int `json:"y,omitempty"`
Z int `json:"z,omitempty"`
DashboardID uint `json:"dashboardID,omitempty"`
IsLocked bool `json:"isLocked,omitempty"`
CustomProperties postgres.Jsonb `json:"customProperties,omitempty"`
SignalIDs []int64 `json:"signalIDs,omitempty"`
}
var newWidget = WidgetRequest{
Name: "My label",
Type: "Label",
Width: 100,
Height: 50,
MinWidth: 40,
MinHeight: 80,
X: 10,
Y: 10,
Z: 200,
IsLocked: false,
CustomProperties: postgres.Jsonb{RawMessage: json.RawMessage(`{"textSize" : "20", "fontColor" : "#4287f5", "fontColor_opacity": 1}`)},
SignalIDs: []int64{},
}
newWidget.DashboardID = uint(dashboardID)
code, resp, err := helper.TestEndpoint(router, token,
"/api/v2/widgets", "POST", helper.KeyModels{"widget": newWidget})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newWidget's ID from the response
newWidgetID, err := helper.GetResponseID(resp)
assert.NoError(t, err)
return newWidgetID
}
func addComponentConfigs(t *testing.T, token string, scenarioID int, ic1ID int, ic2ID int) (int, int) {
type ConfigRequest struct {
Name string `json:"name,omitempty"`
ScenarioID uint `json:"scenarioID,omitempty"`
ICID uint `json:"icID,omitempty"`
StartParameters postgres.Jsonb `json:"startParameters,omitempty"`
FileIDs []int64 `json:"fileIDs,omitempty"`
}
var newConfig1 = ConfigRequest{
Name: "Example for Signal generator",
StartParameters: postgres.Jsonb{RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)},
FileIDs: []int64{},
}
var newConfig2 = ConfigRequest{
Name: "Config example 2",
StartParameters: postgres.Jsonb{RawMessage: json.RawMessage(`{"parameter1" : "testValue1A"}`)},
FileIDs: []int64{},
}
newConfig1.ScenarioID = uint(scenarioID)
newConfig1.ICID = uint(ic1ID)
newConfig2.ScenarioID = uint(scenarioID)
newConfig2.ICID = uint(ic2ID)
code, resp, err := helper.TestEndpoint(router, token,
"/api/v2/configs", "POST", helper.KeyModels{"config": newConfig1})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newConfig's ID from the response
newConfig1ID, err := helper.GetResponseID(resp)
assert.NoError(t, err)
code, resp, err = helper.TestEndpoint(router, token,
"/api/v2/configs", "POST", helper.KeyModels{"config": newConfig2})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newConfig's ID from the response
newConfig2ID, err := helper.GetResponseID(resp)
assert.NoError(t, err)
return newConfig1ID, newConfig2ID
}
func addSignals(t *testing.T, token string, componentConfigID int) (int, int) {
type SignalRequest struct {
Name string `json:"name,omitempty"`
Unit string `json:"unit,omitempty"`
Index *uint `json:"index,omitempty"`
Direction string `json:"direction,omitempty"`
ScalingFactor float32 `json:"scalingFactor,omitempty"`
ConfigID uint `json:"configID,omitempty"`
}
var signalIndex0 uint = 0
var newSignalOut = SignalRequest{
Name: "outSignal_A",
Unit: "V",
Direction: "out",
Index: &signalIndex0,
}
var newSignalIn = SignalRequest{
Name: "inSignal_A",
Unit: "V",
Direction: "in",
Index: &signalIndex0,
}
newSignalOut.ConfigID = uint(componentConfigID)
newSignalIn.ConfigID = uint(componentConfigID)
code, resp, err := helper.TestEndpoint(router, token,
"/api/v2/signals", "POST", helper.KeyModels{"signal": newSignalOut})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newSignal's ID from the response
newSignalIDOut, err := helper.GetResponseID(resp)
assert.NoError(t, err)
code, resp, err = helper.TestEndpoint(router, token,
"/api/v2/signals", "POST", helper.KeyModels{"signal": newSignalIn})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newSignal's ID from the response
newSignalIDIn, err := helper.GetResponseID(resp)
assert.NoError(t, err)
return newSignalIDIn, newSignalIDOut
}
func addICs(t *testing.T, token string) (int, int) {
type ICRequest struct {
UUID string `json:"uuid,omitempty"`
WebsocketURL string `json:"websocketurl,omitempty"`
APIURL string `json:"apiurl,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
State string `json:"state,omitempty"`
Location string `json:"location,omitempty"`
Description string `json:"description,omitempty"`
StartParameterSchema postgres.Jsonb `json:"startparameterschema,omitempty"`
CreateParameterSchema postgres.Jsonb `json:"createparameterschema,omitempty"`
ManagedExternally *bool `json:"managedexternally"`
Manager string `json:"manager,omitempty"`
}
var newIC1 = ICRequest{
UUID: "7be0322d-354e-431e-84bd-ae4c9633138b",
WebsocketURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/ws_sig",
APIURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/api/v2",
Type: "villas-node",
Name: "ACS Demo Signals",
Category: "gateway",
State: "idle",
Location: "k8s",
Description: "A signal generator for testing purposes",
StartParameterSchema: postgres.Jsonb{RawMessage: json.RawMessage(`{"startprop1" : "a nice prop"}`)},
CreateParameterSchema: postgres.Jsonb{RawMessage: json.RawMessage(`{"createprop1" : "a really nice prop"}`)},
ManagedExternally: newFalse(),
Manager: "7be0322d-354e-431e-84bd-ae4c9633beef",
}
// IC with state gone
var newIC2 = ICRequest{
UUID: "4854af30-325f-44a5-ad59-b67b2597de68",
WebsocketURL: "xxx.yyy.zzz.aaa",
APIURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/api/v2",
Type: "dpsim",
Name: "Test DPsim Simulator",
Category: "simulator",
State: "gone",
Location: "k8s",
Description: "This is a test description",
StartParameterSchema: postgres.Jsonb{RawMessage: json.RawMessage(`{"startprop1" : "a nice prop"}`)},
CreateParameterSchema: postgres.Jsonb{RawMessage: json.RawMessage(`{"createprop1" : "a really nice prop"}`)},
ManagedExternally: newFalse(),
Manager: "4854af30-325f-44a5-ad59-b67b2597de99",
}
code, resp, err := helper.TestEndpoint(router, token,
"/api/v2/ic", "POST", helper.KeyModels{"ic": newIC1})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newIC's ID from the response
newIC1ID, err := helper.GetResponseID(resp)
assert.NoError(t, err)
code, resp, err = helper.TestEndpoint(router, token,
"/api/v2/ic", "POST", helper.KeyModels{"ic": newIC2})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newIC's ID from the response
newIC2ID, err := helper.GetResponseID(resp)
assert.NoError(t, err)
return newIC1ID, newIC2ID
}
func newFalse() *bool {
b := false
return &b
}