diff --git a/db/events_test.go b/db/events_test.go index 7a733e867..76d3da7ce 100644 --- a/db/events_test.go +++ b/db/events_test.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "encoding/json" "fmt" - "io/ioutil" "strings" "sync" "testing" @@ -14,7 +13,6 @@ import ( "github.com/google/uuid" "github.com/tinkerbell/tink/client/informers" "github.com/tinkerbell/tink/db" - "github.com/tinkerbell/tink/pkg" "github.com/tinkerbell/tink/protos/events" "github.com/tinkerbell/tink/protos/hardware" ) @@ -30,10 +28,6 @@ func TestCreateEventsForHardware(t *testing.T) { // Expectation is the function used to apply the assertions. // You can use it to validate if the Input are created as you expect Expectation func(*testing.T, []*hardware.Hardware, *db.TinkDB) - // ExpectedErr is used to check for error during - // createHardware execution. If you expect a particular error - // and you want to assert it, you can use this function - ExpectedErr func(*testing.T, error) }{ { Name: "single-hardware-create-event", @@ -77,10 +71,220 @@ func TestCreateEventsForHardware(t *testing.T) { } return input }(), - ExpectedErr: func(t *testing.T, err error) { + Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) { + count := 0 + err := tinkDB.Events(&events.WatchRequest{}, func(n informers.Notification) error { + event, err := n.ToEvent() + if err != nil { + return err + } + + if event.EventType != events.EventType_EVENT_TYPE_CREATED { + return fmt.Errorf("unexpected event type: %s", event.EventType) + } + count = count + 1 + return nil + }) + if err != nil { + t.Error(err) + } + if len(input) != count { + t.Errorf("expected %d events stored in the database but we got %d", len(input), count) + } + }, + }, + } + + ctx := context.Background() + for _, s := range tests { + t.Run(s.Name, func(t *testing.T) { + t.Parallel() + _, tinkDB, cl := NewPostgresDatabaseClient(t, ctx, NewPostgresDatabaseRequest{ + ApplyMigration: true, + }) + defer func() { + err := cl() + if err != nil { + t.Error(err) + } + }() + var wg sync.WaitGroup + wg.Add(len(s.Input)) + for _, hw := range s.Input { + if s.InputAsync { + go func(ctx context.Context, tinkDB *db.TinkDB, hw *hardware.Hardware) { + defer wg.Done() + err := createHardware(ctx, tinkDB, hw) + if err != nil { + t.Error(err) + } + }(ctx, tinkDB, hw) + } else { + wg.Done() + err := createHardware(ctx, tinkDB, hw) + if err != nil { + t.Error(err) + } + } + } + wg.Wait() + s.Expectation(t, s.Input, tinkDB) + }) + } +} + +func TestUpdateEventsForHardware(t *testing.T) { + tests := []struct { + // Name identifies the single test in a table test scenario + Name string + // Input is a list of hardwares that will be used to pre-populate the database + Input []*hardware.Hardware + // InputAsync if set to true inserts all the input concurrently + InputAsync bool + // Expectation is the function used to apply the assertions. + // You can use it to validate if the Input are created as you expect + Expectation func(*testing.T, []*hardware.Hardware, *db.TinkDB) + }{ + { + Name: "update-single-hardware", + Input: []*hardware.Hardware{ + readHardwareData("./testdata/hardware.json"), + }, + Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) { + err := tinkDB.Events( + &events.WatchRequest{ + EventTypes: []events.EventType{events.EventType_EVENT_TYPE_UPDATED}, + }, + func(n informers.Notification) error { + event, err := n.ToEvent() + if err != nil { + return err + } + + if event.EventType != events.EventType_EVENT_TYPE_UPDATED { + return fmt.Errorf("unexpected event type: %s", event.EventType) + } + + hw, err := getHardwareFromEventData(event) + if err != nil { + return err + } + if dif := cmp.Diff(input[0], hw, cmp.Comparer(hardwareComparer)); dif != "" { + t.Errorf(dif) + } + return nil + }) + if err != nil { + t.Error(err) + } + }, + }, + { + Name: "update-stress-test", + InputAsync: true, + Input: func() []*hardware.Hardware { + input := []*hardware.Hardware{} + for ii := 0; ii < 10; ii++ { + hw := readHardwareData("./testdata/hardware.json") + hw.Id = uuid.New().String() + hw.Network.Interfaces[0].Dhcp.Mac = strings.Replace(hw.Network.Interfaces[0].Dhcp.Mac, "00", fmt.Sprintf("0%d", ii), 1) + } + return input + }(), + Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) { + count := 0 + err := tinkDB.Events( + &events.WatchRequest{ + EventTypes: []events.EventType{events.EventType_EVENT_TYPE_UPDATED}, + }, + func(n informers.Notification) error { + event, err := n.ToEvent() + if err != nil { + return err + } + + if event.EventType != events.EventType_EVENT_TYPE_UPDATED { + return fmt.Errorf("unexpected event type: %s", event.EventType) + } + count = count + 1 + return nil + }) + if err != nil { + t.Error(err) + } + if len(input) != count { + t.Errorf("expected %d events stored in the database but we got %d", len(input), count) + } + }, + }, + } + + ctx := context.Background() + for _, s := range tests { + t.Run(s.Name, func(t *testing.T) { + t.Parallel() + _, tinkDB, cl := NewPostgresDatabaseClient(t, ctx, NewPostgresDatabaseRequest{ + ApplyMigration: true, + }) + defer func() { + err := cl() if err != nil { t.Error(err) } + }() + + for _, hw := range s.Input { + go func(ctx context.Context, tinkDB *db.TinkDB, hw *hardware.Hardware) { + err := createHardware(ctx, tinkDB, hw) + if err != nil { + t.Error(err) + } + }(ctx, tinkDB, hw) + } + + var wg sync.WaitGroup + wg.Add(len(s.Input)) + for _, hw := range s.Input { + if s.InputAsync { + go func(ctx context.Context, tinkDB *db.TinkDB, hw *hardware.Hardware) { + defer wg.Done() + hw.Id = uuid.New().String() + err := createHardware(ctx, tinkDB, hw) + if err != nil { + t.Error(err) + } + }(ctx, tinkDB, hw) + } else { + wg.Done() + hw.Id = uuid.New().String() + err := createHardware(ctx, tinkDB, hw) + if err != nil { + t.Error(err) + } + } + } + wg.Wait() + s.Expectation(t, s.Input, tinkDB) + }) + } +} + +func TestDeleteEventsForHardware(t *testing.T) { + tests := []struct { + // Name identifies the single test in a table test scenario + Name string + // Input is a list of hardwares that will be used to pre-populate the database + Input []*hardware.Hardware + // InputAsync if set to true inserts all the input concurrently + InputAsync bool + // Expectation is the function used to apply the assertions. + // You can use it to validate if the Input are created as you expect + Expectation func(*testing.T, []*hardware.Hardware, *db.TinkDB) + }{ + { + Name: "delete-single-hardware", + Input: []*hardware.Hardware{ + readHardwareData("./testdata/hardware.json"), }, Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) { err := tinkDB.Events(&events.WatchRequest{}, func(n informers.Notification) error { @@ -89,7 +293,7 @@ func TestCreateEventsForHardware(t *testing.T) { return err } - if event.EventType != events.EventType_EVENT_TYPE_CREATED { + if event.EventType != events.EventType_EVENT_TYPE_DELETED { return fmt.Errorf("unexpected event type: %s", event.EventType) } @@ -107,6 +311,44 @@ func TestCreateEventsForHardware(t *testing.T) { } }, }, + { + Name: "delete-stress-test", + InputAsync: true, + Input: func() []*hardware.Hardware { + input := []*hardware.Hardware{} + for ii := 0; ii < 10; ii++ { + hw := readHardwareData("./testdata/hardware.json") + hw.Id = uuid.New().String() + hw.Network.Interfaces[0].Dhcp.Mac = strings.Replace(hw.Network.Interfaces[0].Dhcp.Mac, "00", fmt.Sprintf("0%d", ii), 1) + } + return input + }(), + Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) { + count := 0 + err := tinkDB.Events( + &events.WatchRequest{ + EventTypes: []events.EventType{events.EventType_EVENT_TYPE_DELETED}, + }, + func(n informers.Notification) error { + event, err := n.ToEvent() + if err != nil { + return err + } + + if event.EventType != events.EventType_EVENT_TYPE_DELETED { + return fmt.Errorf("unexpected event type: %s", event.EventType) + } + count = count + 1 + return nil + }) + if err != nil { + t.Error(err) + } + if len(input) != count { + t.Errorf("expected %d events stored in the database but we got %d", len(input), count) + } + }, + }, } ctx := context.Background() @@ -122,22 +364,32 @@ func TestCreateEventsForHardware(t *testing.T) { t.Error(err) } }() + + for _, hw := range s.Input { + go func(ctx context.Context, tinkDB *db.TinkDB, hw *hardware.Hardware) { + err := createHardware(ctx, tinkDB, hw) + if err != nil { + t.Error(err) + } + }(ctx, tinkDB, hw) + } + var wg sync.WaitGroup wg.Add(len(s.Input)) for _, hw := range s.Input { if s.InputAsync { go func(ctx context.Context, tinkDB *db.TinkDB, hw *hardware.Hardware) { defer wg.Done() - err := createHardware(ctx, tinkDB, hw) + err := tinkDB.DeleteFromDB(ctx, hw.Id) if err != nil { - s.ExpectedErr(t, err) + t.Error(err) } }(ctx, tinkDB, hw) } else { wg.Done() - err := createHardware(ctx, tinkDB, hw) + err := tinkDB.DeleteFromDB(ctx, hw.Id) if err != nil { - s.ExpectedErr(t, err) + t.Error(err) } } } @@ -147,27 +399,6 @@ func TestCreateEventsForHardware(t *testing.T) { } } -func readHardwareData(file string) *hardware.Hardware { - data, err := ioutil.ReadFile(file) - if err != nil { - panic(err) - } - var hw pkg.HardwareWrapper - err = json.Unmarshal([]byte(data), &hw) - if err != nil { - panic(err) - } - return hw.Hardware -} - -func createHardware(ctx context.Context, db *db.TinkDB, hw *hardware.Hardware) error { - data, err := json.Marshal(hw) - if err != nil { - return err - } - return db.InsertIntoDB(ctx, string(data)) -} - func getHardwareFromEventData(event *events.Event) (*hardware.Hardware, error) { d, err := base64.StdEncoding.DecodeString(strings.Trim(string(event.Data), "\"")) if err != nil { diff --git a/db/hardware_test.go b/db/hardware_test.go new file mode 100644 index 000000000..6f77b9b1e --- /dev/null +++ b/db/hardware_test.go @@ -0,0 +1,233 @@ +package db_test + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "strings" + "sync" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + _ "github.com/lib/pq" + "github.com/tinkerbell/tink/db" + "github.com/tinkerbell/tink/pkg" + "github.com/tinkerbell/tink/protos/hardware" +) + +func TestCreateHardware(t *testing.T) { + ctx := context.Background() + tests := []struct { + // Name identifies the single test in a table test scenario + Name string + // InputAsync if set to true inserts all the input concurrently + InputAsync bool + // Input is a hardware that will be used to pre-populate the database + Input []*hardware.Hardware + // Expectation is the function used to apply the assertions. + // You can use it to validate if the Input are created as you expect + Expectation func(*testing.T, []*hardware.Hardware, *db.TinkDB) + // ExpectedErr is used to check for error during + // CreateTemplate execution. If you expect a particular error + // and you want to assert it, you can use this function + ExpectedErr func(*testing.T, error) + }{ + { + Name: "create-single-hardware", + Input: []*hardware.Hardware{readHardwareData("./testdata/hardware.json")}, + Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) { + data, err := tinkDB.GetByID(ctx, input[0].Id) + if err != nil { + t.Error(err) + } + hw := &hardware.Hardware{} + if err := json.Unmarshal([]byte(data), hw); err != nil { + t.Error(err) + } + if dif := cmp.Diff(input[0], hw, cmp.Comparer(hardwareComparer)); dif != "" { + t.Errorf(dif) + } + }, + }, + { + Name: "two-hardware-with-same-mac", + Input: []*hardware.Hardware{ + func() *hardware.Hardware { + hw := readHardwareData("./testdata/hardware.json") + hw.Id = uuid.New().String() + return hw + }(), + func() *hardware.Hardware { + hw := readHardwareData("./testdata/hardware.json") + hw.Id = uuid.New().String() + return hw + }(), + }, + ExpectedErr: func(t *testing.T, err error) { + if err == nil { + t.Error("expected error, got nil") + } + if !strings.Contains(err.Error(), "conflicting hardware MAC address 08:00:27:00:00:01 provided with hardware data/info") { + t.Errorf("\nexpected err: %s\ngot: %s", "conflicting hardware MAC address 08:00:27:00:00:01 provided with hardware data/info", err) + } + }, + }, + { + Name: "update-on-create", + Input: []*hardware.Hardware{ + func() *hardware.Hardware { + hw := readHardwareData("./testdata/hardware.json") + hw.Id = "d71b659c-3db8-404e-be0e-2fb3c2a482bd" + return hw + }(), + func() *hardware.Hardware { + hw := readHardwareData("./testdata/hardware.json") + hw.Id = "d71b659c-3db8-404e-be0e-2fb3c2a482bd" + hw.Network.Interfaces[0].Dhcp.Hostname = "updated-hostname" + return hw + }(), + }, + Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) { + data, err := tinkDB.GetByID(ctx, input[0].Id) + if err != nil { + t.Error(err) + } + hw := &hardware.Hardware{} + if err := json.Unmarshal([]byte(data), hw); err != nil { + t.Error(err) + } + hostName := hw.Network.Interfaces[0].Dhcp.Hostname + if hostName != "updated-hostname" { + t.Errorf("expected hostname to be \"%s\", got \"%s\"", "updated-hostname", hostName) + } + }, + }, + { + Name: "create-stress-test", + InputAsync: true, + Input: func() []*hardware.Hardware { + input := []*hardware.Hardware{} + for ii := 0; ii < 10; ii++ { + hw := readHardwareData("./testdata/hardware.json") + hw.Id = uuid.New().String() + hw.Network.Interfaces[0].Dhcp.Mac = strings.Replace(hw.Network.Interfaces[0].Dhcp.Mac, "00", fmt.Sprintf("0%d", ii), 1) + } + return input + }(), + Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) { + count := 0 + err := tinkDB.GetAll(func(b []byte) error { + count = count + 1 + return nil + }) + if err != nil { + t.Error(err) + } + if len(input) != count { + t.Errorf("expected %d hardwares stored in the database but we got %d", len(input), count) + } + }, + ExpectedErr: func(t *testing.T, err error) { + if err != nil { + t.Error(err) + } + }, + }, + } + + for _, s := range tests { + t.Run(s.Name, func(t *testing.T) { + t.Parallel() + _, tinkDB, cl := NewPostgresDatabaseClient(t, ctx, NewPostgresDatabaseRequest{ + ApplyMigration: true, + }) + defer func() { + err := cl() + if err != nil { + t.Error(err) + } + }() + + var wg sync.WaitGroup + wg.Add(len(s.Input)) + for _, hw := range s.Input { + if s.InputAsync { + go func(ctx context.Context, tinkDB *db.TinkDB, hw *hardware.Hardware) { + defer wg.Done() + err := createHardware(ctx, tinkDB, hw) + if err != nil { + s.ExpectedErr(t, err) + } + }(ctx, tinkDB, hw) + } else { + wg.Done() + err := createHardware(ctx, tinkDB, hw) + if err != nil { + s.ExpectedErr(t, err) + } + } + } + wg.Wait() + s.Expectation(t, s.Input, tinkDB) + }) + } +} + +func TestDeleteHardware(t *testing.T) { + t.Parallel() + ctx := context.Background() + _, tinkDB, cl := NewPostgresDatabaseClient(t, ctx, NewPostgresDatabaseRequest{ + ApplyMigration: true, + }) + defer func() { + err := cl() + if err != nil { + t.Error(err) + } + }() + + hw := readHardwareData("./testdata/hardware.json") + err := createHardware(ctx, tinkDB, hw) + if err != nil { + t.Error(err) + } + err = tinkDB.DeleteFromDB(ctx, hw.Id) + if err != nil { + t.Error(err) + } + + count := 0 + err = tinkDB.GetAll(func(b []byte) error { + count = count + 1 + return nil + }) + if err != nil { + t.Error(err) + } + if count != 0 { + t.Errorf("expected 0 hardwares stored in the database after delete, but we got %d", count) + } +} + +func readHardwareData(file string) *hardware.Hardware { + data, err := ioutil.ReadFile(file) + if err != nil { + panic(err) + } + var hw pkg.HardwareWrapper + err = json.Unmarshal([]byte(data), &hw) + if err != nil { + panic(err) + } + return hw.Hardware +} + +func createHardware(ctx context.Context, db *db.TinkDB, hw *hardware.Hardware) error { + data, err := json.Marshal(hw) + if err != nil { + return err + } + return db.InsertIntoDB(ctx, string(data)) +} diff --git a/db/template_test.go b/db/template_test.go index cc332fc87..d989bec86 100644 --- a/db/template_test.go +++ b/db/template_test.go @@ -209,6 +209,45 @@ func TestCreateTemplate_TwoTemplateWithSameNameButFirstOneIsDeleted(t *testing.T } } +func TestDeleteTemplate(t *testing.T) { + t.Parallel() + ctx := context.Background() + _, tinkDB, cl := NewPostgresDatabaseClient(t, ctx, NewPostgresDatabaseRequest{ + ApplyMigration: true, + }) + defer func() { + err := cl() + if err != nil { + t.Error(err) + } + }() + + w := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml") + w.ID = uuid.New().String() + w.Name = fmt.Sprintf("id_%d", rand.Int()) + + err := createTemplateFromWorkflowType(ctx, tinkDB, w) + if err != nil { + t.Error(err) + } + err = tinkDB.DeleteTemplate(ctx, w.ID) + if err != nil { + t.Error(err) + } + + count := 0 + err = tinkDB.ListTemplates("%", func(id, n string, in, del *timestamp.Timestamp) error { + count = count + 1 + return nil + }) + if err != nil { + t.Error(err) + } + if count != 0 { + t.Errorf("expected 0 templates stored in the database after delete, but we got %d", count) + } +} + func createTemplateFromWorkflowType(ctx context.Context, tinkDB *db.TinkDB, tt *workflow.Workflow) error { uID := uuid.MustParse(tt.ID) content, err := yaml.Marshal(tt) diff --git a/db/workflow_test.go b/db/workflow_test.go new file mode 100644 index 000000000..6d2d6b9e9 --- /dev/null +++ b/db/workflow_test.go @@ -0,0 +1,250 @@ +package db_test + +import ( + "context" + "fmt" + "math/rand" + "sync" + "testing" + + "github.com/google/uuid" + "github.com/tinkerbell/tink/db" + "github.com/tinkerbell/tink/protos/hardware" + pb "github.com/tinkerbell/tink/protos/workflow" + + "github.com/tinkerbell/tink/workflow" +) + +type input struct { + devices string + template *workflow.Workflow + hardware *hardware.Hardware + workflowCount int +} + +func TestCreateWorkflow(t *testing.T) { + tests := []struct { + // Name identifies the single test in a table test scenario + Name string + // InputAsync if set to true inserts all the input concurrently + InputAsync bool + // Input is a struct that will be used to create a workflow and pre-populate the database + Input *input + // Expectation is the function used to apply the assertions. + // You can use it to validate if the Input are created as you expect + Expectation func(t *testing.T, in *input, tinkDB *db.TinkDB) + // ExpectedErr is used to check for error during + // CreateWorkflow execution. If you expect a particular error + // and you want to assert it, you can use this function + ExpectedErr func(*testing.T, error) + }{ + { + Name: "create-single-workflow", + Input: &input{ + workflowCount: 1, + devices: "{\"device_1\":\"08:00:27:00:00:01\"}", + hardware: readHardwareData("./testdata/hardware.json"), + template: func() *workflow.Workflow { + tmp := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml") + tmp.ID = uuid.New().String() + tmp.Name = fmt.Sprintf("id_%d", rand.Int()) + return tmp + }(), + }, + Expectation: func(t *testing.T, in *input, tinkDB *db.TinkDB) { + count := 0 + err := tinkDB.ListWorkflows(func(wf db.Workflow) error { + count = count + 1 + return nil + }) + if err != nil { + t.Error(err) + } + if count != in.workflowCount { + t.Errorf("expected %d workflows stored in the database but we got %d", in.workflowCount, count) + } + }, + }, + { + Name: "create-fails-invalid-worker-address", + Input: &input{ + workflowCount: 0, + devices: "{\"invalid_device\":\"08:00:27:00:00:01\"}", + hardware: readHardwareData("./testdata/hardware.json"), + template: func() *workflow.Workflow { + tmp := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml") + tmp.ID = uuid.New().String() + tmp.Name = fmt.Sprintf("id_%d", rand.Int()) + return tmp + }(), + }, + Expectation: func(t *testing.T, in *input, tinkDB *db.TinkDB) { + count := 0 + err := tinkDB.ListWorkflows(func(wf db.Workflow) error { + count = count + 1 + return nil + }) + if err != nil { + t.Error(err) + } + if count != in.workflowCount { + t.Errorf("expected %d workflows stored in the database but we got %d", in.workflowCount, count) + } + }, + }, + { + Name: "stress-create-workflow", + InputAsync: true, + Input: &input{ + workflowCount: 20, + devices: "{\"device_1\":\"08:00:27:00:00:01\"}", + hardware: readHardwareData("./testdata/hardware.json"), + template: func() *workflow.Workflow { + tmp := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml") + tmp.ID = uuid.New().String() + tmp.Name = fmt.Sprintf("id_%d", rand.Int()) + return tmp + }(), + }, + Expectation: func(t *testing.T, in *input, tinkDB *db.TinkDB) { + count := 0 + err := tinkDB.ListWorkflows(func(wf db.Workflow) error { + count = count + 1 + return nil + }) + if err != nil { + t.Error(err) + } + if count != in.workflowCount { + t.Errorf("expected %d workflows stored in the database but we got %d", in.workflowCount, count) + } + }, + }, + } + + ctx := context.Background() + for _, s := range tests { + t.Run(s.Name, func(t *testing.T) { + t.Parallel() + _, tinkDB, cl := NewPostgresDatabaseClient(t, ctx, NewPostgresDatabaseRequest{ + ApplyMigration: true, + }) + defer func() { + err := cl() + if err != nil { + t.Error(err) + } + }() + + err := createHardware(ctx, tinkDB, s.Input.hardware) + if err != nil { + t.Error(err) + } + err = createTemplateFromWorkflowType(ctx, tinkDB, s.Input.template) + if err != nil { + t.Error(err) + } + + var wg sync.WaitGroup + wg.Add(s.Input.workflowCount) + for i := 0; i < s.Input.workflowCount; i++ { + if s.InputAsync { + go func(ctx context.Context, tinkDB *db.TinkDB, in *input) { + defer wg.Done() + _, err := createWorkflow(ctx, tinkDB, in) + if err != nil { + s.ExpectedErr(t, err) + } + }(ctx, tinkDB, s.Input) + } else { + wg.Done() + _, err := createWorkflow(ctx, tinkDB, s.Input) + if err != nil { + s.ExpectedErr(t, err) + } + } + } + wg.Wait() + s.Expectation(t, s.Input, tinkDB) + }) + } +} + +func TestDeleteWorkflow(t *testing.T) { + t.Parallel() + ctx := context.Background() + _, tinkDB, cl := NewPostgresDatabaseClient(t, ctx, NewPostgresDatabaseRequest{ + ApplyMigration: true, + }) + defer func() { + err := cl() + if err != nil { + t.Error(err) + } + }() + + in := &input{ + devices: "{\"device_1\":\"08:00:27:00:00:01\"}", + hardware: readHardwareData("./testdata/hardware.json"), + template: func() *workflow.Workflow { + tmp := workflow.MustParseFromFile("./testdata/template_happy_path_1.yaml") + tmp.ID = uuid.New().String() + tmp.Name = fmt.Sprintf("id_%d", rand.Int()) + return tmp + }(), + } + err := createHardware(ctx, tinkDB, in.hardware) + if err != nil { + t.Error(err) + } + err = createTemplateFromWorkflowType(ctx, tinkDB, in.template) + if err != nil { + t.Error(err) + } + + wfID, err := createWorkflow(ctx, tinkDB, in) + if err != nil { + t.Error(err) + } + + err = tinkDB.DeleteWorkflow(ctx, wfID, pb.State_value[pb.State_STATE_PENDING.String()]) + if err != nil { + t.Error(err) + } + + count := 0 + err = tinkDB.ListWorkflows(func(wf db.Workflow) error { + count = count + 1 + return nil + }) + if err != nil { + t.Error(err) + } + if count != 0 { + t.Errorf("expected 0 workflows stored in the database after delete, but we got %d", count) + } +} + +func createWorkflow(ctx context.Context, tinkDB *db.TinkDB, in *input) (string, error) { + _, _, tmpData, err := tinkDB.GetTemplate(context.Background(), map[string]string{"id": in.template.ID}, false) + if err != nil { + return "", err + } + + data, err := workflow.RenderTemplate(in.template.ID, tmpData, []byte(in.devices)) + if err != nil { + return "", err + } + + id := uuid.New() + wf := db.Workflow{ + ID: id.String(), + Template: in.template.ID, + Hardware: in.devices, + } + err = tinkDB.CreateWorkflow(ctx, wf, data, id) + if err != nil { + return "", err + } + return id.String(), nil +}