diff --git a/.mockery.yaml b/.mockery.yaml index 500cd3fa1..aa4f7ac51 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -91,3 +91,7 @@ packages: config: dir: "core/deleter/mocks" all: true + github.com/raystack/frontier/core/serviceuser: + config: + dir: "core/serviceuser/mocks" + all: true diff --git a/core/serviceuser/mocks/credential_repository.go b/core/serviceuser/mocks/credential_repository.go new file mode 100644 index 000000000..661e1b181 --- /dev/null +++ b/core/serviceuser/mocks/credential_repository.go @@ -0,0 +1,257 @@ +// Code generated by mockery v2.53.5. DO NOT EDIT. + +package mocks + +import ( + context "context" + + serviceuser "github.com/raystack/frontier/core/serviceuser" + mock "github.com/stretchr/testify/mock" +) + +// CredentialRepository is an autogenerated mock type for the CredentialRepository type +type CredentialRepository struct { + mock.Mock +} + +type CredentialRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *CredentialRepository) EXPECT() *CredentialRepository_Expecter { + return &CredentialRepository_Expecter{mock: &_m.Mock} +} + +// Create provides a mock function with given fields: ctx, credential +func (_m *CredentialRepository) Create(ctx context.Context, credential serviceuser.Credential) (serviceuser.Credential, error) { + ret := _m.Called(ctx, credential) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 serviceuser.Credential + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, serviceuser.Credential) (serviceuser.Credential, error)); ok { + return rf(ctx, credential) + } + if rf, ok := ret.Get(0).(func(context.Context, serviceuser.Credential) serviceuser.Credential); ok { + r0 = rf(ctx, credential) + } else { + r0 = ret.Get(0).(serviceuser.Credential) + } + + if rf, ok := ret.Get(1).(func(context.Context, serviceuser.Credential) error); ok { + r1 = rf(ctx, credential) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CredentialRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type CredentialRepository_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - ctx context.Context +// - credential serviceuser.Credential +func (_e *CredentialRepository_Expecter) Create(ctx interface{}, credential interface{}) *CredentialRepository_Create_Call { + return &CredentialRepository_Create_Call{Call: _e.mock.On("Create", ctx, credential)} +} + +func (_c *CredentialRepository_Create_Call) Run(run func(ctx context.Context, credential serviceuser.Credential)) *CredentialRepository_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(serviceuser.Credential)) + }) + return _c +} + +func (_c *CredentialRepository_Create_Call) Return(_a0 serviceuser.Credential, _a1 error) *CredentialRepository_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CredentialRepository_Create_Call) RunAndReturn(run func(context.Context, serviceuser.Credential) (serviceuser.Credential, error)) *CredentialRepository_Create_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function with given fields: ctx, id +func (_m *CredentialRepository) Delete(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CredentialRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type CredentialRepository_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *CredentialRepository_Expecter) Delete(ctx interface{}, id interface{}) *CredentialRepository_Delete_Call { + return &CredentialRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, id)} +} + +func (_c *CredentialRepository_Delete_Call) Run(run func(ctx context.Context, id string)) *CredentialRepository_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *CredentialRepository_Delete_Call) Return(_a0 error) *CredentialRepository_Delete_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *CredentialRepository_Delete_Call) RunAndReturn(run func(context.Context, string) error) *CredentialRepository_Delete_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function with given fields: ctx, id +func (_m *CredentialRepository) Get(ctx context.Context, id string) (serviceuser.Credential, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 serviceuser.Credential + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (serviceuser.Credential, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) serviceuser.Credential); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(serviceuser.Credential) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CredentialRepository_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type CredentialRepository_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *CredentialRepository_Expecter) Get(ctx interface{}, id interface{}) *CredentialRepository_Get_Call { + return &CredentialRepository_Get_Call{Call: _e.mock.On("Get", ctx, id)} +} + +func (_c *CredentialRepository_Get_Call) Run(run func(ctx context.Context, id string)) *CredentialRepository_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *CredentialRepository_Get_Call) Return(_a0 serviceuser.Credential, _a1 error) *CredentialRepository_Get_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CredentialRepository_Get_Call) RunAndReturn(run func(context.Context, string) (serviceuser.Credential, error)) *CredentialRepository_Get_Call { + _c.Call.Return(run) + return _c +} + +// List provides a mock function with given fields: ctx, flt +func (_m *CredentialRepository) List(ctx context.Context, flt serviceuser.Filter) ([]serviceuser.Credential, error) { + ret := _m.Called(ctx, flt) + + if len(ret) == 0 { + panic("no return value specified for List") + } + + var r0 []serviceuser.Credential + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, serviceuser.Filter) ([]serviceuser.Credential, error)); ok { + return rf(ctx, flt) + } + if rf, ok := ret.Get(0).(func(context.Context, serviceuser.Filter) []serviceuser.Credential); ok { + r0 = rf(ctx, flt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]serviceuser.Credential) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, serviceuser.Filter) error); ok { + r1 = rf(ctx, flt) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CredentialRepository_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' +type CredentialRepository_List_Call struct { + *mock.Call +} + +// List is a helper method to define mock.On call +// - ctx context.Context +// - flt serviceuser.Filter +func (_e *CredentialRepository_Expecter) List(ctx interface{}, flt interface{}) *CredentialRepository_List_Call { + return &CredentialRepository_List_Call{Call: _e.mock.On("List", ctx, flt)} +} + +func (_c *CredentialRepository_List_Call) Run(run func(ctx context.Context, flt serviceuser.Filter)) *CredentialRepository_List_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(serviceuser.Filter)) + }) + return _c +} + +func (_c *CredentialRepository_List_Call) Return(_a0 []serviceuser.Credential, _a1 error) *CredentialRepository_List_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CredentialRepository_List_Call) RunAndReturn(run func(context.Context, serviceuser.Filter) ([]serviceuser.Credential, error)) *CredentialRepository_List_Call { + _c.Call.Return(run) + return _c +} + +// NewCredentialRepository creates a new instance of CredentialRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewCredentialRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *CredentialRepository { + mock := &CredentialRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/serviceuser/mocks/membership_service.go b/core/serviceuser/mocks/membership_service.go new file mode 100644 index 000000000..a5ad7c519 --- /dev/null +++ b/core/serviceuser/mocks/membership_service.go @@ -0,0 +1,135 @@ +// Code generated by mockery v2.53.5. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// MembershipService is an autogenerated mock type for the MembershipService type +type MembershipService struct { + mock.Mock +} + +type MembershipService_Expecter struct { + mock *mock.Mock +} + +func (_m *MembershipService) EXPECT() *MembershipService_Expecter { + return &MembershipService_Expecter{mock: &_m.Mock} +} + +// AddOrganizationMember provides a mock function with given fields: ctx, orgID, principalID, principalType, roleID +func (_m *MembershipService) AddOrganizationMember(ctx context.Context, orgID string, principalID string, principalType string, roleID string) error { + ret := _m.Called(ctx, orgID, principalID, principalType, roleID) + + if len(ret) == 0 { + panic("no return value specified for AddOrganizationMember") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok { + r0 = rf(ctx, orgID, principalID, principalType, roleID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MembershipService_AddOrganizationMember_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddOrganizationMember' +type MembershipService_AddOrganizationMember_Call struct { + *mock.Call +} + +// AddOrganizationMember is a helper method to define mock.On call +// - ctx context.Context +// - orgID string +// - principalID string +// - principalType string +// - roleID string +func (_e *MembershipService_Expecter) AddOrganizationMember(ctx interface{}, orgID interface{}, principalID interface{}, principalType interface{}, roleID interface{}) *MembershipService_AddOrganizationMember_Call { + return &MembershipService_AddOrganizationMember_Call{Call: _e.mock.On("AddOrganizationMember", ctx, orgID, principalID, principalType, roleID)} +} + +func (_c *MembershipService_AddOrganizationMember_Call) Run(run func(ctx context.Context, orgID string, principalID string, principalType string, roleID string)) *MembershipService_AddOrganizationMember_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string)) + }) + return _c +} + +func (_c *MembershipService_AddOrganizationMember_Call) Return(_a0 error) *MembershipService_AddOrganizationMember_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MembershipService_AddOrganizationMember_Call) RunAndReturn(run func(context.Context, string, string, string, string) error) *MembershipService_AddOrganizationMember_Call { + _c.Call.Return(run) + return _c +} + +// RemoveOrganizationMember provides a mock function with given fields: ctx, orgID, principalID, principalType +func (_m *MembershipService) RemoveOrganizationMember(ctx context.Context, orgID string, principalID string, principalType string) error { + ret := _m.Called(ctx, orgID, principalID, principalType) + + if len(ret) == 0 { + panic("no return value specified for RemoveOrganizationMember") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { + r0 = rf(ctx, orgID, principalID, principalType) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MembershipService_RemoveOrganizationMember_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveOrganizationMember' +type MembershipService_RemoveOrganizationMember_Call struct { + *mock.Call +} + +// RemoveOrganizationMember is a helper method to define mock.On call +// - ctx context.Context +// - orgID string +// - principalID string +// - principalType string +func (_e *MembershipService_Expecter) RemoveOrganizationMember(ctx interface{}, orgID interface{}, principalID interface{}, principalType interface{}) *MembershipService_RemoveOrganizationMember_Call { + return &MembershipService_RemoveOrganizationMember_Call{Call: _e.mock.On("RemoveOrganizationMember", ctx, orgID, principalID, principalType)} +} + +func (_c *MembershipService_RemoveOrganizationMember_Call) Run(run func(ctx context.Context, orgID string, principalID string, principalType string)) *MembershipService_RemoveOrganizationMember_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) + }) + return _c +} + +func (_c *MembershipService_RemoveOrganizationMember_Call) Return(_a0 error) *MembershipService_RemoveOrganizationMember_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MembershipService_RemoveOrganizationMember_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MembershipService_RemoveOrganizationMember_Call { + _c.Call.Return(run) + return _c +} + +// NewMembershipService creates a new instance of MembershipService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMembershipService(t interface { + mock.TestingT + Cleanup(func()) +}) *MembershipService { + mock := &MembershipService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/serviceuser/mocks/relation_service.go b/core/serviceuser/mocks/relation_service.go new file mode 100644 index 000000000..027c253ac --- /dev/null +++ b/core/serviceuser/mocks/relation_service.go @@ -0,0 +1,316 @@ +// Code generated by mockery v2.53.5. DO NOT EDIT. + +package mocks + +import ( + context "context" + + relation "github.com/raystack/frontier/core/relation" + mock "github.com/stretchr/testify/mock" +) + +// RelationService is an autogenerated mock type for the RelationService type +type RelationService struct { + mock.Mock +} + +type RelationService_Expecter struct { + mock *mock.Mock +} + +func (_m *RelationService) EXPECT() *RelationService_Expecter { + return &RelationService_Expecter{mock: &_m.Mock} +} + +// BatchCheckPermission provides a mock function with given fields: ctx, rel +func (_m *RelationService) BatchCheckPermission(ctx context.Context, rel []relation.Relation) ([]relation.CheckPair, error) { + ret := _m.Called(ctx, rel) + + if len(ret) == 0 { + panic("no return value specified for BatchCheckPermission") + } + + var r0 []relation.CheckPair + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []relation.Relation) ([]relation.CheckPair, error)); ok { + return rf(ctx, rel) + } + if rf, ok := ret.Get(0).(func(context.Context, []relation.Relation) []relation.CheckPair); ok { + r0 = rf(ctx, rel) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]relation.CheckPair) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []relation.Relation) error); ok { + r1 = rf(ctx, rel) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RelationService_BatchCheckPermission_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BatchCheckPermission' +type RelationService_BatchCheckPermission_Call struct { + *mock.Call +} + +// BatchCheckPermission is a helper method to define mock.On call +// - ctx context.Context +// - rel []relation.Relation +func (_e *RelationService_Expecter) BatchCheckPermission(ctx interface{}, rel interface{}) *RelationService_BatchCheckPermission_Call { + return &RelationService_BatchCheckPermission_Call{Call: _e.mock.On("BatchCheckPermission", ctx, rel)} +} + +func (_c *RelationService_BatchCheckPermission_Call) Run(run func(ctx context.Context, rel []relation.Relation)) *RelationService_BatchCheckPermission_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]relation.Relation)) + }) + return _c +} + +func (_c *RelationService_BatchCheckPermission_Call) Return(_a0 []relation.CheckPair, _a1 error) *RelationService_BatchCheckPermission_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RelationService_BatchCheckPermission_Call) RunAndReturn(run func(context.Context, []relation.Relation) ([]relation.CheckPair, error)) *RelationService_BatchCheckPermission_Call { + _c.Call.Return(run) + return _c +} + +// CheckPermission provides a mock function with given fields: ctx, rel +func (_m *RelationService) CheckPermission(ctx context.Context, rel relation.Relation) (bool, error) { + ret := _m.Called(ctx, rel) + + if len(ret) == 0 { + panic("no return value specified for CheckPermission") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, relation.Relation) (bool, error)); ok { + return rf(ctx, rel) + } + if rf, ok := ret.Get(0).(func(context.Context, relation.Relation) bool); ok { + r0 = rf(ctx, rel) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, relation.Relation) error); ok { + r1 = rf(ctx, rel) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RelationService_CheckPermission_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckPermission' +type RelationService_CheckPermission_Call struct { + *mock.Call +} + +// CheckPermission is a helper method to define mock.On call +// - ctx context.Context +// - rel relation.Relation +func (_e *RelationService_Expecter) CheckPermission(ctx interface{}, rel interface{}) *RelationService_CheckPermission_Call { + return &RelationService_CheckPermission_Call{Call: _e.mock.On("CheckPermission", ctx, rel)} +} + +func (_c *RelationService_CheckPermission_Call) Run(run func(ctx context.Context, rel relation.Relation)) *RelationService_CheckPermission_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(relation.Relation)) + }) + return _c +} + +func (_c *RelationService_CheckPermission_Call) Return(_a0 bool, _a1 error) *RelationService_CheckPermission_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RelationService_CheckPermission_Call) RunAndReturn(run func(context.Context, relation.Relation) (bool, error)) *RelationService_CheckPermission_Call { + _c.Call.Return(run) + return _c +} + +// Create provides a mock function with given fields: ctx, rel +func (_m *RelationService) Create(ctx context.Context, rel relation.Relation) (relation.Relation, error) { + ret := _m.Called(ctx, rel) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 relation.Relation + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, relation.Relation) (relation.Relation, error)); ok { + return rf(ctx, rel) + } + if rf, ok := ret.Get(0).(func(context.Context, relation.Relation) relation.Relation); ok { + r0 = rf(ctx, rel) + } else { + r0 = ret.Get(0).(relation.Relation) + } + + if rf, ok := ret.Get(1).(func(context.Context, relation.Relation) error); ok { + r1 = rf(ctx, rel) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RelationService_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type RelationService_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - ctx context.Context +// - rel relation.Relation +func (_e *RelationService_Expecter) Create(ctx interface{}, rel interface{}) *RelationService_Create_Call { + return &RelationService_Create_Call{Call: _e.mock.On("Create", ctx, rel)} +} + +func (_c *RelationService_Create_Call) Run(run func(ctx context.Context, rel relation.Relation)) *RelationService_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(relation.Relation)) + }) + return _c +} + +func (_c *RelationService_Create_Call) Return(_a0 relation.Relation, _a1 error) *RelationService_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RelationService_Create_Call) RunAndReturn(run func(context.Context, relation.Relation) (relation.Relation, error)) *RelationService_Create_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function with given fields: ctx, rel +func (_m *RelationService) Delete(ctx context.Context, rel relation.Relation) error { + ret := _m.Called(ctx, rel) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, relation.Relation) error); ok { + r0 = rf(ctx, rel) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RelationService_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type RelationService_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - ctx context.Context +// - rel relation.Relation +func (_e *RelationService_Expecter) Delete(ctx interface{}, rel interface{}) *RelationService_Delete_Call { + return &RelationService_Delete_Call{Call: _e.mock.On("Delete", ctx, rel)} +} + +func (_c *RelationService_Delete_Call) Run(run func(ctx context.Context, rel relation.Relation)) *RelationService_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(relation.Relation)) + }) + return _c +} + +func (_c *RelationService_Delete_Call) Return(_a0 error) *RelationService_Delete_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *RelationService_Delete_Call) RunAndReturn(run func(context.Context, relation.Relation) error) *RelationService_Delete_Call { + _c.Call.Return(run) + return _c +} + +// LookupSubjects provides a mock function with given fields: ctx, rel +func (_m *RelationService) LookupSubjects(ctx context.Context, rel relation.Relation) ([]string, error) { + ret := _m.Called(ctx, rel) + + if len(ret) == 0 { + panic("no return value specified for LookupSubjects") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, relation.Relation) ([]string, error)); ok { + return rf(ctx, rel) + } + if rf, ok := ret.Get(0).(func(context.Context, relation.Relation) []string); ok { + r0 = rf(ctx, rel) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, relation.Relation) error); ok { + r1 = rf(ctx, rel) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RelationService_LookupSubjects_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LookupSubjects' +type RelationService_LookupSubjects_Call struct { + *mock.Call +} + +// LookupSubjects is a helper method to define mock.On call +// - ctx context.Context +// - rel relation.Relation +func (_e *RelationService_Expecter) LookupSubjects(ctx interface{}, rel interface{}) *RelationService_LookupSubjects_Call { + return &RelationService_LookupSubjects_Call{Call: _e.mock.On("LookupSubjects", ctx, rel)} +} + +func (_c *RelationService_LookupSubjects_Call) Run(run func(ctx context.Context, rel relation.Relation)) *RelationService_LookupSubjects_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(relation.Relation)) + }) + return _c +} + +func (_c *RelationService_LookupSubjects_Call) Return(_a0 []string, _a1 error) *RelationService_LookupSubjects_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RelationService_LookupSubjects_Call) RunAndReturn(run func(context.Context, relation.Relation) ([]string, error)) *RelationService_LookupSubjects_Call { + _c.Call.Return(run) + return _c +} + +// NewRelationService creates a new instance of RelationService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRelationService(t interface { + mock.TestingT + Cleanup(func()) +}) *RelationService { + mock := &RelationService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/serviceuser/mocks/repository.go b/core/serviceuser/mocks/repository.go new file mode 100644 index 000000000..96b76be0d --- /dev/null +++ b/core/serviceuser/mocks/repository.go @@ -0,0 +1,316 @@ +// Code generated by mockery v2.53.5. DO NOT EDIT. + +package mocks + +import ( + context "context" + + serviceuser "github.com/raystack/frontier/core/serviceuser" + mock "github.com/stretchr/testify/mock" +) + +// Repository is an autogenerated mock type for the Repository type +type Repository struct { + mock.Mock +} + +type Repository_Expecter struct { + mock *mock.Mock +} + +func (_m *Repository) EXPECT() *Repository_Expecter { + return &Repository_Expecter{mock: &_m.Mock} +} + +// Create provides a mock function with given fields: ctx, serviceUser +func (_m *Repository) Create(ctx context.Context, serviceUser serviceuser.ServiceUser) (serviceuser.ServiceUser, error) { + ret := _m.Called(ctx, serviceUser) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 serviceuser.ServiceUser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, serviceuser.ServiceUser) (serviceuser.ServiceUser, error)); ok { + return rf(ctx, serviceUser) + } + if rf, ok := ret.Get(0).(func(context.Context, serviceuser.ServiceUser) serviceuser.ServiceUser); ok { + r0 = rf(ctx, serviceUser) + } else { + r0 = ret.Get(0).(serviceuser.ServiceUser) + } + + if rf, ok := ret.Get(1).(func(context.Context, serviceuser.ServiceUser) error); ok { + r1 = rf(ctx, serviceUser) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Repository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type Repository_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - ctx context.Context +// - serviceUser serviceuser.ServiceUser +func (_e *Repository_Expecter) Create(ctx interface{}, serviceUser interface{}) *Repository_Create_Call { + return &Repository_Create_Call{Call: _e.mock.On("Create", ctx, serviceUser)} +} + +func (_c *Repository_Create_Call) Run(run func(ctx context.Context, serviceUser serviceuser.ServiceUser)) *Repository_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(serviceuser.ServiceUser)) + }) + return _c +} + +func (_c *Repository_Create_Call) Return(_a0 serviceuser.ServiceUser, _a1 error) *Repository_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Repository_Create_Call) RunAndReturn(run func(context.Context, serviceuser.ServiceUser) (serviceuser.ServiceUser, error)) *Repository_Create_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function with given fields: ctx, id +func (_m *Repository) Delete(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Repository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type Repository_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *Repository_Expecter) Delete(ctx interface{}, id interface{}) *Repository_Delete_Call { + return &Repository_Delete_Call{Call: _e.mock.On("Delete", ctx, id)} +} + +func (_c *Repository_Delete_Call) Run(run func(ctx context.Context, id string)) *Repository_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *Repository_Delete_Call) Return(_a0 error) *Repository_Delete_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Repository_Delete_Call) RunAndReturn(run func(context.Context, string) error) *Repository_Delete_Call { + _c.Call.Return(run) + return _c +} + +// GetByID provides a mock function with given fields: ctx, id +func (_m *Repository) GetByID(ctx context.Context, id string) (serviceuser.ServiceUser, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetByID") + } + + var r0 serviceuser.ServiceUser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (serviceuser.ServiceUser, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) serviceuser.ServiceUser); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(serviceuser.ServiceUser) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Repository_GetByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByID' +type Repository_GetByID_Call struct { + *mock.Call +} + +// GetByID is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *Repository_Expecter) GetByID(ctx interface{}, id interface{}) *Repository_GetByID_Call { + return &Repository_GetByID_Call{Call: _e.mock.On("GetByID", ctx, id)} +} + +func (_c *Repository_GetByID_Call) Run(run func(ctx context.Context, id string)) *Repository_GetByID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *Repository_GetByID_Call) Return(_a0 serviceuser.ServiceUser, _a1 error) *Repository_GetByID_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Repository_GetByID_Call) RunAndReturn(run func(context.Context, string) (serviceuser.ServiceUser, error)) *Repository_GetByID_Call { + _c.Call.Return(run) + return _c +} + +// GetByIDs provides a mock function with given fields: ctx, id +func (_m *Repository) GetByIDs(ctx context.Context, id []string) ([]serviceuser.ServiceUser, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetByIDs") + } + + var r0 []serviceuser.ServiceUser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]serviceuser.ServiceUser, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []serviceuser.ServiceUser); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]serviceuser.ServiceUser) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Repository_GetByIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByIDs' +type Repository_GetByIDs_Call struct { + *mock.Call +} + +// GetByIDs is a helper method to define mock.On call +// - ctx context.Context +// - id []string +func (_e *Repository_Expecter) GetByIDs(ctx interface{}, id interface{}) *Repository_GetByIDs_Call { + return &Repository_GetByIDs_Call{Call: _e.mock.On("GetByIDs", ctx, id)} +} + +func (_c *Repository_GetByIDs_Call) Run(run func(ctx context.Context, id []string)) *Repository_GetByIDs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]string)) + }) + return _c +} + +func (_c *Repository_GetByIDs_Call) Return(_a0 []serviceuser.ServiceUser, _a1 error) *Repository_GetByIDs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Repository_GetByIDs_Call) RunAndReturn(run func(context.Context, []string) ([]serviceuser.ServiceUser, error)) *Repository_GetByIDs_Call { + _c.Call.Return(run) + return _c +} + +// List provides a mock function with given fields: ctx, flt +func (_m *Repository) List(ctx context.Context, flt serviceuser.Filter) ([]serviceuser.ServiceUser, error) { + ret := _m.Called(ctx, flt) + + if len(ret) == 0 { + panic("no return value specified for List") + } + + var r0 []serviceuser.ServiceUser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, serviceuser.Filter) ([]serviceuser.ServiceUser, error)); ok { + return rf(ctx, flt) + } + if rf, ok := ret.Get(0).(func(context.Context, serviceuser.Filter) []serviceuser.ServiceUser); ok { + r0 = rf(ctx, flt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]serviceuser.ServiceUser) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, serviceuser.Filter) error); ok { + r1 = rf(ctx, flt) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Repository_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' +type Repository_List_Call struct { + *mock.Call +} + +// List is a helper method to define mock.On call +// - ctx context.Context +// - flt serviceuser.Filter +func (_e *Repository_Expecter) List(ctx interface{}, flt interface{}) *Repository_List_Call { + return &Repository_List_Call{Call: _e.mock.On("List", ctx, flt)} +} + +func (_c *Repository_List_Call) Run(run func(ctx context.Context, flt serviceuser.Filter)) *Repository_List_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(serviceuser.Filter)) + }) + return _c +} + +func (_c *Repository_List_Call) Return(_a0 []serviceuser.ServiceUser, _a1 error) *Repository_List_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Repository_List_Call) RunAndReturn(run func(context.Context, serviceuser.Filter) ([]serviceuser.ServiceUser, error)) *Repository_List_Call { + _c.Call.Return(run) + return _c +} + +// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *Repository { + mock := &Repository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/serviceuser/service.go b/core/serviceuser/service.go index 1c2de6931..0ae9212ee 100644 --- a/core/serviceuser/service.go +++ b/core/serviceuser/service.go @@ -6,6 +6,7 @@ import ( "crypto/subtle" "encoding/base64" "encoding/hex" + "errors" "fmt" "io" "log/slog" @@ -169,7 +170,9 @@ func (s Service) Delete(ctx context.Context, id string) error { ) } - // delete remaining SpiceDB relations (platform relations, identity link, etc.) + // SU may appear as Subject (e.g. platform sudo) or as Object (e.g. the + // serviceuser#org identity link). Sweep both sides so deletion is symmetric + // with creation regardless of whether the membership cascade ran above. if err := s.relationService.Delete(ctx, relation.Relation{ Subject: relation.Subject{ ID: id, @@ -178,6 +181,15 @@ func (s Service) Delete(ctx context.Context, id string) error { }); err != nil { return err } + if err := s.relationService.Delete(ctx, relation.Relation{ + Object: relation.Object{ + ID: id, + Namespace: schema.ServiceUserPrincipal, + }, + }); err != nil && !errors.Is(err, relation.ErrNotExist) { + return err + } + return s.repo.Delete(ctx, id) } diff --git a/core/serviceuser/service_test.go b/core/serviceuser/service_test.go new file mode 100644 index 000000000..86d2cc289 --- /dev/null +++ b/core/serviceuser/service_test.go @@ -0,0 +1,115 @@ +package serviceuser_test + +import ( + "context" + "errors" + "io" + "log/slog" + "testing" + + "github.com/raystack/frontier/core/relation" + "github.com/raystack/frontier/core/serviceuser" + "github.com/raystack/frontier/core/serviceuser/mocks" + "github.com/raystack/frontier/internal/bootstrap/schema" +) + +func newTestService(t *testing.T) (*serviceuser.Service, *mocks.Repository, *mocks.CredentialRepository, *mocks.RelationService, *mocks.MembershipService) { + t.Helper() + repo := mocks.NewRepository(t) + credRepo := mocks.NewCredentialRepository(t) + relSvc := mocks.NewRelationService(t) + memSvc := mocks.NewMembershipService(t) + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + svc := serviceuser.NewService(logger, repo, credRepo, relSvc) + svc.SetMembershipService(memSvc) + return svc, repo, credRepo, relSvc, memSvc +} + +func TestService_Delete(t *testing.T) { + ctx := context.Background() + const suID = "su-id" + const orgID = "org-id" + + subjectFilter := relation.Relation{ + Subject: relation.Subject{ID: suID, Namespace: schema.ServiceUserPrincipal}, + } + objectFilter := relation.Relation{ + Object: relation.Object{ID: suID, Namespace: schema.ServiceUserPrincipal}, + } + + tests := []struct { + name string + setup func(*mocks.Repository, *mocks.CredentialRepository, *mocks.RelationService, *mocks.MembershipService) + wantErr bool + }{ + { + name: "sweeps SU as subject and as object", + setup: func(repo *mocks.Repository, cred *mocks.CredentialRepository, rel *mocks.RelationService, mem *mocks.MembershipService) { + repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, OrgID: orgID}, nil) + cred.On("List", ctx, serviceuser.Filter{ServiceUserID: suID}).Return([]serviceuser.Credential{}, nil) + mem.On("RemoveOrganizationMember", ctx, orgID, suID, schema.ServiceUserPrincipal).Return(nil) + rel.On("Delete", ctx, subjectFilter).Return(nil) + rel.On("Delete", ctx, objectFilter).Return(nil) + repo.On("Delete", ctx, suID).Return(nil) + }, + }, + { + name: "membership failure is swallowed and both sweeps still run", + setup: func(repo *mocks.Repository, cred *mocks.CredentialRepository, rel *mocks.RelationService, mem *mocks.MembershipService) { + repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, OrgID: orgID}, nil) + cred.On("List", ctx, serviceuser.Filter{ServiceUserID: suID}).Return([]serviceuser.Credential{}, nil) + // covers the path where membership returns early without reaching its + // cascade cleanup (e.g. SU has no remaining org policies) + mem.On("RemoveOrganizationMember", ctx, orgID, suID, schema.ServiceUserPrincipal).Return(errors.New("not a member")) + rel.On("Delete", ctx, subjectFilter).Return(nil) + rel.On("Delete", ctx, objectFilter).Return(nil) + repo.On("Delete", ctx, suID).Return(nil) + }, + }, + { + name: "tolerates ErrNotExist from Object-side sweep", + setup: func(repo *mocks.Repository, cred *mocks.CredentialRepository, rel *mocks.RelationService, mem *mocks.MembershipService) { + repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, OrgID: orgID}, nil) + cred.On("List", ctx, serviceuser.Filter{ServiceUserID: suID}).Return([]serviceuser.Credential{}, nil) + mem.On("RemoveOrganizationMember", ctx, orgID, suID, schema.ServiceUserPrincipal).Return(nil) + rel.On("Delete", ctx, subjectFilter).Return(nil) + rel.On("Delete", ctx, objectFilter).Return(relation.ErrNotExist) + repo.On("Delete", ctx, suID).Return(nil) + }, + }, + { + name: "Object-side non-ErrNotExist failure blocks repo delete", + setup: func(repo *mocks.Repository, cred *mocks.CredentialRepository, rel *mocks.RelationService, mem *mocks.MembershipService) { + repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, OrgID: orgID}, nil) + cred.On("List", ctx, serviceuser.Filter{ServiceUserID: suID}).Return([]serviceuser.Credential{}, nil) + mem.On("RemoveOrganizationMember", ctx, orgID, suID, schema.ServiceUserPrincipal).Return(nil) + rel.On("Delete", ctx, subjectFilter).Return(nil) + rel.On("Delete", ctx, objectFilter).Return(errors.New("spicedb unavailable")) + // repo.Delete must NOT be called + }, + wantErr: true, + }, + { + name: "Subject-side failure short-circuits before Object sweep", + setup: func(repo *mocks.Repository, cred *mocks.CredentialRepository, rel *mocks.RelationService, mem *mocks.MembershipService) { + repo.On("GetByID", ctx, suID).Return(serviceuser.ServiceUser{ID: suID, OrgID: orgID}, nil) + cred.On("List", ctx, serviceuser.Filter{ServiceUserID: suID}).Return([]serviceuser.Credential{}, nil) + mem.On("RemoveOrganizationMember", ctx, orgID, suID, schema.ServiceUserPrincipal).Return(nil) + rel.On("Delete", ctx, subjectFilter).Return(errors.New("spicedb unavailable")) + // Object-side Delete and repo.Delete must NOT be called + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + svc, repo, cred, rel, mem := newTestService(t) + tt.setup(repo, cred, rel, mem) + + err := svc.Delete(ctx, suID) + if (err != nil) != tt.wantErr { + t.Errorf("Delete() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +}