Orgs: Remove dependency on dashboard table for deletion (#98501)

This commit is contained in:
Stephanie Hingtgen 2025-01-06 10:05:22 -07:00 committed by GitHub
parent 14369c53ed
commit 68479d844b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 212 additions and 16 deletions

View File

@ -205,6 +205,7 @@ type HTTPServer struct {
tempUserService tempUser.Service tempUserService tempUser.Service
loginAttemptService loginAttempt.Service loginAttemptService loginAttempt.Service
orgService org.Service orgService org.Service
orgDeletionService org.DeletionService
TeamService team.Service TeamService team.Service
accesscontrolService accesscontrol.Service accesscontrolService accesscontrol.Service
annotationsRepo annotations.Repository annotationsRepo annotations.Repository
@ -264,7 +265,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
secretsMigrator secrets.Migrator, secretsPluginManager plugins.SecretsPluginManager, secretsService secrets.Service, secretsMigrator secrets.Migrator, secretsPluginManager plugins.SecretsPluginManager, secretsService secrets.Service,
secretsPluginMigrator spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore, secretsPluginMigrator spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore,
publicDashboardsApi *publicdashboardsApi.Api, userService user.Service, tempUserService tempUser.Service, publicDashboardsApi *publicdashboardsApi.Api, userService user.Service, tempUserService tempUser.Service,
loginAttemptService loginAttempt.Service, orgService org.Service, teamService team.Service, loginAttemptService loginAttempt.Service, orgService org.Service, orgDeletionService org.DeletionService, teamService team.Service,
accesscontrolService accesscontrol.Service, navTreeService navtree.Service, accesscontrolService accesscontrol.Service, navTreeService navtree.Service,
annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService, oauthTokenService oauthtoken.OAuthTokenService, annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService, oauthTokenService oauthtoken.OAuthTokenService,
statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service, promGatherer prometheus.Gatherer, statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service, promGatherer prometheus.Gatherer,
@ -356,6 +357,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
tempUserService: tempUserService, tempUserService: tempUserService,
loginAttemptService: loginAttemptService, loginAttemptService: loginAttemptService,
orgService: orgService, orgService: orgService,
orgDeletionService: orgDeletionService,
TeamService: teamService, TeamService: teamService,
navTreeService: navTreeService, navTreeService: navTreeService,
accesscontrolService: accesscontrolService, accesscontrolService: accesscontrolService,

View File

@ -297,7 +297,7 @@ func (hs *HTTPServer) DeleteOrgByID(c *contextmodel.ReqContext) response.Respons
return response.Error(http.StatusBadRequest, "Can not delete org for current user", nil) return response.Error(http.StatusBadRequest, "Can not delete org for current user", nil)
} }
if err := hs.orgService.Delete(c.Req.Context(), &org.DeleteOrgCommand{ID: orgID}); err != nil { if err := hs.orgDeletionService.Delete(c.Req.Context(), &org.DeleteOrgCommand{ID: orgID}); err != nil {
if errors.Is(err, org.ErrOrgNotFound) { if errors.Is(err, org.ErrOrgNotFound) {
return response.Error(http.StatusNotFound, "Failed to delete organization. ID not found", nil) return response.Error(http.StatusNotFound, "Failed to delete organization. ID not found", nil)
} }

View File

@ -228,6 +228,7 @@ func TestAPIEndpoint_DeleteOrgs(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) { server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg() hs.Cfg = setting.NewCfg()
hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}} hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}}
hs.orgDeletionService = &orgtest.FakeOrgDeletionService{}
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: &user.SignedInUser{OrgID: 1}} hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: &user.SignedInUser{OrgID: 1}}
hs.accesscontrolService = actest.FakeService{ExpectedPermissions: tt.permission} hs.accesscontrolService = actest.FakeService{ExpectedPermissions: tt.permission}
hs.authnService = &authntest.FakeService{} hs.authnService = &authntest.FakeService{}

View File

@ -337,6 +337,7 @@ var wireBasicSet = wire.NewSet(
starApi.ProvideApi, starApi.ProvideApi,
userimpl.ProvideService, userimpl.ProvideService,
orgimpl.ProvideService, orgimpl.ProvideService,
orgimpl.ProvideDeletionService,
statsimpl.ProvideService, statsimpl.ProvideService,
grpccontext.ProvideContextHandler, grpccontext.ProvideContextHandler,
grpcserver.ProvideService, grpcserver.ProvideService,

View File

@ -16,6 +16,7 @@ import (
type DashboardService interface { type DashboardService interface {
BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, validateProvisionedDashboard bool) (*SaveDashboardCommand, error) BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, validateProvisionedDashboard bool) (*SaveDashboardCommand, error)
DeleteDashboard(ctx context.Context, dashboardId int64, dashboardUID string, orgId int64) error DeleteDashboard(ctx context.Context, dashboardId int64, dashboardUID string, orgId int64) error
DeleteAllDashboards(ctx context.Context, orgID int64) error
FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
// GetDashboard fetches a dashboard. // GetDashboard fetches a dashboard.
// To fetch a dashboard under root by title should set the folder UID to point to an empty string // To fetch a dashboard under root by title should set the folder UID to point to an empty string
@ -60,6 +61,7 @@ type DashboardProvisioningService interface {
//go:generate mockery --name Store --structname FakeDashboardStore --inpackage --filename store_mock.go //go:generate mockery --name Store --structname FakeDashboardStore --inpackage --filename store_mock.go
type Store interface { type Store interface {
DeleteDashboard(ctx context.Context, cmd *DeleteDashboardCommand) error DeleteDashboard(ctx context.Context, cmd *DeleteDashboardCommand) error
DeleteAllDashboards(ctx context.Context, orgID int64) error
DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *DeleteOrphanedProvisionedDashboardsCommand) error DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *DeleteOrphanedProvisionedDashboardsCommand) error
FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
GetDashboard(ctx context.Context, query *GetDashboardQuery) (*Dashboard, error) GetDashboard(ctx context.Context, query *GetDashboardQuery) (*Dashboard, error)

View File

@ -120,6 +120,24 @@ func (_m *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId
return r0 return r0
} }
// DeleteAllDashboards provides a mock function with given fields: ctx, orgID
func (_m *FakeDashboardService) DeleteAllDashboards(ctx context.Context, orgID int64) error {
ret := _m.Called(ctx, orgID)
if len(ret) == 0 {
panic("no return value specified for DeleteDashboard")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, orgID)
} else {
r0 = ret.Error(0)
}
return r0
}
// FindDashboards provides a mock function with given fields: ctx, query // FindDashboards provides a mock function with given fields: ctx, query
func (_m *FakeDashboardService) FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) { func (_m *FakeDashboardService) FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
ret := _m.Called(ctx, query) ret := _m.Called(ctx, query)

View File

@ -667,6 +667,16 @@ func (d *dashboardStore) deleteDashboard(cmd *dashboards.DeleteDashboardCommand,
return nil return nil
} }
func (d *dashboardStore) DeleteAllDashboards(ctx context.Context, orgID int64) error {
ctx, span := tracer.Start(ctx, "dashboards.database.DeleteAllDashboards")
defer span.End()
return d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
_, err := sess.Where("org_id = ?", orgID).Delete(&dashboards.Dashboard{})
return err
})
}
// FIXME: Remove me and handle nested deletions in the service with the DashboardPermissionsService // FIXME: Remove me and handle nested deletions in the service with the DashboardPermissionsService
func (d *dashboardStore) deleteResourcePermissions(sess *db.Session, orgID int64, resourceScope string) error { func (d *dashboardStore) deleteResourcePermissions(sess *db.Session, orgID int64, resourceScope string) error {
// retrieve all permissions for the resource scope and org id // retrieve all permissions for the resource scope and org id

View File

@ -240,6 +240,32 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
require.NotContains(t, terms, "delete this") require.NotContains(t, terms, "delete this")
}) })
t.Run("Should be able to delete all dashboards for an org", func(t *testing.T) {
setup()
dash1 := insertTestDashboard(t, dashboardStore, "delete me", 1, 0, "", false, "delete this")
dash2 := insertTestDashboard(t, dashboardStore, "delete me2", 1, 0, "", false, "delete this2")
dash3 := insertTestDashboard(t, dashboardStore, "dont delete me", 2, 0, "", false, "dont delete me")
err := dashboardStore.DeleteAllDashboards(context.Background(), 1)
require.NoError(t, err)
// no dashboards should exist for org 1
queryResult, err := dashboardStore.GetDashboards(context.Background(), &dashboards.GetDashboardsQuery{
OrgID: 1,
DashboardUIDs: []string{dash1.UID, dash2.UID},
})
require.NoError(t, err)
assert.Equal(t, len(queryResult), 0)
// but we should still have one for org 2
queryResult, err = dashboardStore.GetDashboards(context.Background(), &dashboards.GetDashboardsQuery{
OrgID: 2,
DashboardUIDs: []string{dash3.UID},
})
require.NoError(t, err)
assert.Equal(t, len(queryResult), 1)
})
t.Run("Should be able to create dashboard", func(t *testing.T) { t.Run("Should be able to create dashboard", func(t *testing.T) {
setup() setup()
cmd := dashboards.SaveDashboardCommand{ cmd := dashboards.SaveDashboardCommand{

View File

@ -511,6 +511,15 @@ func (dr *DashboardServiceImpl) DeleteDashboard(ctx context.Context, dashboardId
return dr.deleteDashboard(ctx, dashboardId, dashboardUID, orgId, true) return dr.deleteDashboard(ctx, dashboardId, dashboardUID, orgId, true)
} }
// DeleteAllDashboards will delete all dashboards within a given org.
func (dr *DashboardServiceImpl) DeleteAllDashboards(ctx context.Context, orgId int64) error {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
return dr.deleteAllDashboardThroughK8s(ctx, orgId)
}
return dr.dashboardStore.DeleteAllDashboards(ctx, orgId)
}
func (dr *DashboardServiceImpl) GetDashboardByPublicUid(ctx context.Context, dashboardPublicUid string) (*dashboards.Dashboard, error) { func (dr *DashboardServiceImpl) GetDashboardByPublicUid(ctx context.Context, dashboardPublicUid string) (*dashboards.Dashboard, error) {
return nil, nil return nil, nil
} }
@ -1178,6 +1187,29 @@ func (dr *DashboardServiceImpl) saveDashboardThroughK8s(ctx context.Context, cmd
return finalDash, nil return finalDash, nil
} }
func (dr *DashboardServiceImpl) deleteAllDashboardThroughK8s(ctx context.Context, orgID int64) error {
// create a new context - prevents issues when the request stems from the k8s api itself
// otherwise the context goes through the handlers twice and causes issues
newCtx, cancel, err := dr.getK8sContext(ctx)
if err != nil {
return err
} else if cancel != nil {
defer cancel()
}
client, ok := dr.k8sclient.getClient(newCtx, orgID)
if !ok {
return fmt.Errorf("could not get k8s client")
}
err = client.DeleteCollection(newCtx, v1.DeleteOptions{}, v1.ListOptions{})
if err != nil {
return err
}
return nil
}
func (dr *DashboardServiceImpl) deleteDashboardThroughK8s(ctx context.Context, cmd *dashboards.DeleteDashboardCommand) error { func (dr *DashboardServiceImpl) deleteDashboardThroughK8s(ctx context.Context, cmd *dashboards.DeleteDashboardCommand) error {
// create a new context - prevents issues when the request stems from the k8s api itself // create a new context - prevents issues when the request stems from the k8s api itself
// otherwise the context goes through the handlers twice and causes issues // otherwise the context goes through the handlers twice and causes issues

View File

@ -305,6 +305,11 @@ func (m *mockResourceInterface) Delete(ctx context.Context, name string, options
return args.Error(0) return args.Error(0)
} }
func (m *mockResourceInterface) DeleteCollection(ctx context.Context, options metav1.DeleteOptions, listOptions metav1.ListOptions) error {
args := m.Called(ctx, options, listOptions)
return args.Error(0)
}
func setupK8sDashboardTests(service *DashboardServiceImpl) (context.Context, *mockDashK8sCli, *mockResourceInterface) { func setupK8sDashboardTests(service *DashboardServiceImpl) (context.Context, *mockDashK8sCli, *mockResourceInterface) {
k8sClientMock := new(mockDashK8sCli) k8sClientMock := new(mockDashK8sCli)
k8sResourceMock := new(mockResourceInterface) k8sResourceMock := new(mockResourceInterface)
@ -644,6 +649,33 @@ func TestDeleteDashboard(t *testing.T) {
}) })
} }
func TestDeleteAllDashboards(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("DeleteAllDashboards", mock.Anything, mock.Anything).Return(nil).Once()
err := service.DeleteAllDashboards(context.Background(), 1)
require.NoError(t, err)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true).Once()
k8sResourceMock.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
err := service.DeleteAllDashboards(ctx, 1)
require.NoError(t, err)
k8sClientMock.AssertExpectations(t)
})
}
func TestSearchDashboards(t *testing.T) { func TestSearchDashboards(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{} fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t) defer fakeStore.AssertExpectations(t)

View File

@ -94,6 +94,24 @@ func (_m *FakeDashboardStore) DeleteDashboard(ctx context.Context, cmd *DeleteDa
return r0 return r0
} }
// DeleteAllDashboards provides a mock function with given fields: ctx, orgID
func (_m *FakeDashboardStore) DeleteAllDashboards(ctx context.Context, orgID int64) error {
ret := _m.Called(ctx, orgID)
if len(ret) == 0 {
panic("no return value specified for DeleteDashboard")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, orgID)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteDashboardsInFolders provides a mock function with given fields: ctx, request // DeleteDashboardsInFolders provides a mock function with given fields: ctx, request
func (_m *FakeDashboardStore) DeleteDashboardsInFolders(ctx context.Context, request *DeleteDashboardsInFolderRequest) error { func (_m *FakeDashboardStore) DeleteDashboardsInFolders(ctx context.Context, request *DeleteDashboardsInFolderRequest) error {
ret := _m.Called(ctx, request) ret := _m.Called(ctx, request)

View File

@ -11,10 +11,12 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/org/orgimpl"
@ -975,10 +977,14 @@ func CreateOrg(t *testing.T, db db.DB, cfg *setting.Cfg) int64 {
orgService, err := orgimpl.ProvideService(db, cfg, quotatest.New(false, nil)) orgService, err := orgimpl.ProvideService(db, cfg, quotatest.New(false, nil))
require.NoError(t, err) require.NoError(t, err)
dashSvc := &dashboards.FakeDashboardService{}
dashSvc.On("DeleteAllDashboards", mock.Anything, mock.Anything).Return(nil)
deleteOrgService, err := orgimpl.ProvideDeletionService(db, cfg, dashSvc)
require.NoError(t, err)
orgID, err := orgService.GetOrCreate(context.Background(), "test-org") orgID, err := orgService.GetOrCreate(context.Background(), "test-org")
require.NoError(t, err) require.NoError(t, err)
t.Cleanup(func() { t.Cleanup(func() {
err = orgService.Delete(context.Background(), &org.DeleteOrgCommand{ID: orgID}) err = deleteOrgService.Delete(context.Background(), &org.DeleteOrgCommand{ID: orgID})
require.NoError(t, err) require.NoError(t, err)
}) })

View File

@ -16,7 +16,6 @@ type Service interface {
GetByName(context.Context, *GetOrgByNameQuery) (*Org, error) GetByName(context.Context, *GetOrgByNameQuery) (*Org, error)
CreateWithMember(context.Context, *CreateOrgCommand) (*Org, error) CreateWithMember(context.Context, *CreateOrgCommand) (*Org, error)
UpdateAddress(context.Context, *UpdateOrgAddressCommand) error UpdateAddress(context.Context, *UpdateOrgAddressCommand) error
Delete(context.Context, *DeleteOrgCommand) error
GetOrCreate(context.Context, string) (int64, error) GetOrCreate(context.Context, string) (int64, error)
AddOrgUser(context.Context, *AddOrgUserCommand) error AddOrgUser(context.Context, *AddOrgUserCommand) error
UpdateOrgUser(context.Context, *UpdateOrgUserCommand) error UpdateOrgUser(context.Context, *UpdateOrgUserCommand) error
@ -25,3 +24,7 @@ type Service interface {
SearchOrgUsers(context.Context, *SearchOrgUsersQuery) (*SearchOrgUsersQueryResult, error) SearchOrgUsers(context.Context, *SearchOrgUsersQuery) (*SearchOrgUsersQueryResult, error)
RegisterDelete(query string) RegisterDelete(query string)
} }
type DeletionService interface {
Delete(context.Context, *DeleteOrgCommand) error
}

View File

@ -138,11 +138,6 @@ func (s *Service) UpdateAddress(ctx context.Context, cmd *org.UpdateOrgAddressCo
return s.store.UpdateAddress(ctx, cmd) return s.store.UpdateAddress(ctx, cmd)
} }
// TODO: refactor service to call store CRUD method
func (s *Service) Delete(ctx context.Context, cmd *org.DeleteOrgCommand) error {
return s.store.Delete(ctx, cmd)
}
func (s *Service) GetOrCreate(ctx context.Context, orgName string) (int64, error) { func (s *Service) GetOrCreate(ctx context.Context, orgName string) (int64, error) {
var orga = &org.Org{} var orga = &org.Org{}
var err error var err error

View File

@ -0,0 +1,43 @@
package orgimpl
import (
"context"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/setting"
)
type DeletionService struct {
store store
cfg *setting.Cfg
log log.Logger
dashSvc dashboards.DashboardService
}
func ProvideDeletionService(db db.DB, cfg *setting.Cfg, dashboardService dashboards.DashboardService) (org.DeletionService, error) {
log := log.New("org deletion service")
s := &DeletionService{
store: &sqlStore{
db: db,
dialect: db.GetDialect(),
log: log,
},
cfg: cfg,
dashSvc: dashboardService,
log: log,
}
return s, nil
}
func (s *DeletionService) Delete(ctx context.Context, cmd *org.DeleteOrgCommand) error {
err := s.dashSvc.DeleteAllDashboards(ctx, cmd.ID)
if err != nil {
return err
}
return s.store.Delete(ctx, cmd)
}

View File

@ -220,9 +220,8 @@ func (ss *sqlStore) Delete(ctx context.Context, cmd *org.DeleteOrgCommand) error
} }
deletes := []string{ deletes := []string{
"DELETE FROM star WHERE EXISTS (SELECT 1 FROM dashboard WHERE org_id = ? AND star.dashboard_uid = dashboard.uid)", "DELETE FROM star WHERE org_id = ?",
"DELETE FROM dashboard_tag WHERE EXISTS (SELECT 1 FROM dashboard WHERE org_id = ? AND dashboard_tag.dashboard_id = dashboard.id)", "DELETE FROM dashboard_tag WHERE org_id = ?",
"DELETE FROM dashboard WHERE org_id = ?",
"DELETE FROM api_key WHERE org_id = ?", "DELETE FROM api_key WHERE org_id = ?",
"DELETE FROM data_source WHERE org_id = ?", "DELETE FROM data_source WHERE org_id = ?",
"DELETE FROM org_user WHERE org_id = ?", "DELETE FROM org_user WHERE org_id = ?",

View File

@ -75,10 +75,6 @@ func (f *FakeOrgService) UpdateAddress(ctx context.Context, cmd *org.UpdateOrgAd
return f.ExpectedError return f.ExpectedError
} }
func (f *FakeOrgService) Delete(ctx context.Context, cmd *org.DeleteOrgCommand) error {
return f.ExpectedError
}
func (f *FakeOrgService) GetOrCreate(ctx context.Context, orgName string) (int64, error) { func (f *FakeOrgService) GetOrCreate(ctx context.Context, orgName string) (int64, error) {
return f.ExpectedOrg.ID, f.ExpectedError return f.ExpectedOrg.ID, f.ExpectedError
} }
@ -107,3 +103,15 @@ func (f *FakeOrgService) SearchOrgUsers(ctx context.Context, query *org.SearchOr
func (f *FakeOrgService) RegisterDelete(query string) { func (f *FakeOrgService) RegisterDelete(query string) {
} }
type FakeOrgDeletionService struct {
ExpectedError error
}
func NewOrgDeletionServiceFake() *FakeOrgDeletionService {
return &FakeOrgDeletionService{}
}
func (f *FakeOrgDeletionService) Delete(ctx context.Context, cmd *org.DeleteOrgCommand) error {
return f.ExpectedError
}