Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the Delete handler for the datasource service #5062

Merged
merged 3 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 55 additions & 4 deletions internal/datasources/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"fmt"

"github.com/google/uuid"
"github.com/rs/zerolog"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/encoding/protojson"

Expand Down Expand Up @@ -187,7 +188,7 @@ func (d *dataSourceService) Create(
defer func(stx serviceTX) {
err := stx.Rollback()
if err != nil {
fmt.Printf("failed to rollback transaction: %v", err)
zerolog.Ctx(ctx).Error().Err(err).Msg("failed to rollback transaction")
}
}(stx)

Expand Down Expand Up @@ -262,11 +263,61 @@ func (d *dataSourceService) Update(
panic("implement me")
}

// nolint:revive // there is a TODO
// Delete deletes a data source in the given project.
func (d *dataSourceService) Delete(
ctx context.Context, id uuid.UUID, project uuid.UUID, opts *Options) error {
//TODO implement me
panic("implement me")
stx, err := d.txBuilder(d, opts)
if err != nil {
return fmt.Errorf("failed to start transaction: %w", err)
}
defer func(stx serviceTX) {
err := stx.Rollback()
if err != nil {
zerolog.Ctx(ctx).Error().Err(err).Msg("failed to rollback transaction")
}
}(stx)

// Get the transaction querier
tx := stx.Q()

// List rule types referencing the data source
ret, err := tx.ListRuleTypesReferencesByDataSource(ctx, db.ListRuleTypesReferencesByDataSourceParams{
DataSourcesID: id,
ProjectID: project,
})
if err != nil {
return fmt.Errorf("failed to list rule types referencing data source %s: %w", id, err)
}

// Check if the data source is in use by any rule types
if len(ret) > 0 {
// Return an error with the rule types that are using the data source
var existingRefs []string
for _, r := range ret {
existingRefs = append(existingRefs, r.RuleTypeID.String())
}
return util.UserVisibleError(codes.FailedPrecondition,
"data source %s is in use by the following rule types: %v", id, existingRefs)
}

// Delete the data source record
_, err = tx.DeleteDataSource(ctx, db.DeleteDataSourceParams{
ID: id,
ProjectID: project,
})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return util.UserVisibleError(codes.NotFound,
"data source with id %s not found in project %s", id, project)
}
return fmt.Errorf("failed to delete data source with id %s: %w", id, err)
}

// Commit the transaction
if err := stx.Commit(); err != nil {
return fmt.Errorf("failed to commit transaction: %w", err)
}
return nil
}

// nolint:revive // there is a TODO
Expand Down
164 changes: 164 additions & 0 deletions internal/datasources/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -877,3 +877,167 @@ func restDriverToJson(t *testing.T, rs *minderv1.RestDataSource_Def) []byte {

return out
}

func TestDelete(t *testing.T) {
t.Parallel()

type args struct {
id uuid.UUID
project uuid.UUID
opts *Options
}

tests := []struct {
name string
args args
setup func(args args, mockDB *mockdb.MockStore)
wantErr bool
}{
{
name: "Successful deletion",
args: args{
id: uuid.New(),
project: uuid.New(),
opts: &Options{},
},
setup: func(args args, mockDB *mockdb.MockStore) {
// Mock ListRuleTypesReferencesByDataSource to return empty list
mockDB.EXPECT().
ListRuleTypesReferencesByDataSource(gomock.Any(), gomock.Eq(db.ListRuleTypesReferencesByDataSourceParams{
DataSourcesID: args.id,
ProjectID: args.project,
})).
Return([]db.RuleTypeDataSource{}, nil)

// Mock DeleteDataSource to succeed
mockDB.EXPECT().
DeleteDataSource(gomock.Any(), gomock.Eq(db.DeleteDataSourceParams{
ID: args.id,
ProjectID: args.project,
})).
Return(db.DataSource{}, nil)
},
wantErr: false,
},
{
name: "Data source not found",
args: args{
id: uuid.New(),
project: uuid.New(),
opts: &Options{},
},
setup: func(args args, mockDB *mockdb.MockStore) {
// Mock ListRuleTypesReferencesByDataSource to return empty list
mockDB.EXPECT().
ListRuleTypesReferencesByDataSource(gomock.Any(), gomock.Eq(db.ListRuleTypesReferencesByDataSourceParams{
DataSourcesID: args.id,
ProjectID: args.project,
})).
Return([]db.RuleTypeDataSource{}, nil)

// Mock DeleteDataSource to return sql.ErrNoRows
mockDB.EXPECT().
DeleteDataSource(gomock.Any(), gomock.Eq(db.DeleteDataSourceParams{
ID: args.id,
ProjectID: args.project,
})).
Return(db.DataSource{}, sql.ErrNoRows)
},
wantErr: true,
},
{
name: "Data source is in use",
args: args{
id: uuid.New(),
project: uuid.New(),
opts: &Options{},
},
setup: func(args args, mockDB *mockdb.MockStore) {
// Mock ListRuleTypesReferencesByDataSource to return non-empty list
mockDB.EXPECT().
ListRuleTypesReferencesByDataSource(gomock.Any(), gomock.Eq(db.ListRuleTypesReferencesByDataSourceParams{
DataSourcesID: args.id,
ProjectID: args.project,
})).
Return([]db.RuleTypeDataSource{
{RuleTypeID: uuid.New()},
}, nil)
},
wantErr: true,
},
{
name: "Database error when listing references",
args: args{
id: uuid.New(),
project: uuid.New(),
opts: &Options{},
},
setup: func(args args, mockDB *mockdb.MockStore) {
// Mock ListRuleTypesReferencesByDataSource to return an error
mockDB.EXPECT().
ListRuleTypesReferencesByDataSource(gomock.Any(), gomock.Eq(db.ListRuleTypesReferencesByDataSourceParams{
DataSourcesID: args.id,
ProjectID: args.project,
})).
Return(nil, fmt.Errorf("database error"))
},
wantErr: true,
},
{
name: "Database error when deleting data source",
args: args{
id: uuid.New(),
project: uuid.New(),
opts: &Options{},
},
setup: func(args args, mockDB *mockdb.MockStore) {
// Mock ListRuleTypesReferencesByDataSource to return empty list
mockDB.EXPECT().
ListRuleTypesReferencesByDataSource(gomock.Any(), gomock.Eq(db.ListRuleTypesReferencesByDataSourceParams{
DataSourcesID: args.id,
ProjectID: args.project,
})).
Return([]db.RuleTypeDataSource{}, nil)

// Mock DeleteDataSource to return an error
mockDB.EXPECT().
DeleteDataSource(gomock.Any(), gomock.Eq(db.DeleteDataSourceParams{
ID: args.id,
ProjectID: args.project,
})).
Return(db.DataSource{}, fmt.Errorf("database error"))
},
wantErr: true,
},
}

for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

ctrl := gomock.NewController(t)
defer ctrl.Finish()

// Setup
mockStore := mockdb.NewMockStore(ctrl)

svc := NewDataSourceService(mockStore)
svc.txBuilder = func(_ *dataSourceService, _ txGetter) (serviceTX, error) {
return &fakeTxBuilder{
store: mockStore,
}, nil
}

tt.setup(tt.args, mockStore)

err := svc.Delete(context.Background(), tt.args.id, tt.args.project, tt.args.opts)
if tt.wantErr {
assert.Error(t, err)
return
}

require.NoError(t, err)
})
}
}