From b101b8a8e43ac1e2f1b775546ec20776133a918f Mon Sep 17 00:00:00 2001 From: Anuj Khandelwal Date: Mon, 26 Aug 2024 21:44:46 +0530 Subject: [PATCH] Unit tests for group service (#742) * add mocks for group service dependencies * added test for create method * add tests for getByID and getByIDs * add tests for other group service methods --- .mockery.yaml | 4 + core/group/mocks/authn_service.go | 109 ++++++ core/group/mocks/policy_service.go | 260 ++++++++++++++ core/group/mocks/relation_service.go | 319 ++++++++++++++++++ core/group/mocks/repository.go | 485 +++++++++++++++++++++++++++ core/group/service_test.go | 264 +++++++++++++++ 6 files changed, 1441 insertions(+) create mode 100644 core/group/mocks/authn_service.go create mode 100644 core/group/mocks/policy_service.go create mode 100644 core/group/mocks/relation_service.go create mode 100644 core/group/mocks/repository.go create mode 100644 core/group/service_test.go diff --git a/.mockery.yaml b/.mockery.yaml index 5b78e46f3..93454d8a9 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -31,6 +31,10 @@ packages: config: dir: "core/policy/mocks" all: true + github.com/raystack/frontier/core/group: + config: + dir: "core/group/mocks" + all: true github.com/raystack/frontier/core/authenticate/session: config: dir: "core/authenticate/session/mocks" diff --git a/core/group/mocks/authn_service.go b/core/group/mocks/authn_service.go new file mode 100644 index 000000000..6d30464a6 --- /dev/null +++ b/core/group/mocks/authn_service.go @@ -0,0 +1,109 @@ +// Code generated by mockery v2.40.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + authenticate "github.com/raystack/frontier/core/authenticate" + + mock "github.com/stretchr/testify/mock" +) + +// AuthnService is an autogenerated mock type for the AuthnService type +type AuthnService struct { + mock.Mock +} + +type AuthnService_Expecter struct { + mock *mock.Mock +} + +func (_m *AuthnService) EXPECT() *AuthnService_Expecter { + return &AuthnService_Expecter{mock: &_m.Mock} +} + +// GetPrincipal provides a mock function with given fields: ctx, via +func (_m *AuthnService) GetPrincipal(ctx context.Context, via ...authenticate.ClientAssertion) (authenticate.Principal, error) { + _va := make([]interface{}, len(via)) + for _i := range via { + _va[_i] = via[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for GetPrincipal") + } + + var r0 authenticate.Principal + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ...authenticate.ClientAssertion) (authenticate.Principal, error)); ok { + return rf(ctx, via...) + } + if rf, ok := ret.Get(0).(func(context.Context, ...authenticate.ClientAssertion) authenticate.Principal); ok { + r0 = rf(ctx, via...) + } else { + r0 = ret.Get(0).(authenticate.Principal) + } + + if rf, ok := ret.Get(1).(func(context.Context, ...authenticate.ClientAssertion) error); ok { + r1 = rf(ctx, via...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AuthnService_GetPrincipal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPrincipal' +type AuthnService_GetPrincipal_Call struct { + *mock.Call +} + +// GetPrincipal is a helper method to define mock.On call +// - ctx context.Context +// - via ...authenticate.ClientAssertion +func (_e *AuthnService_Expecter) GetPrincipal(ctx interface{}, via ...interface{}) *AuthnService_GetPrincipal_Call { + return &AuthnService_GetPrincipal_Call{Call: _e.mock.On("GetPrincipal", + append([]interface{}{ctx}, via...)...)} +} + +func (_c *AuthnService_GetPrincipal_Call) Run(run func(ctx context.Context, via ...authenticate.ClientAssertion)) *AuthnService_GetPrincipal_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]authenticate.ClientAssertion, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(authenticate.ClientAssertion) + } + } + run(args[0].(context.Context), variadicArgs...) + }) + return _c +} + +func (_c *AuthnService_GetPrincipal_Call) Return(_a0 authenticate.Principal, _a1 error) *AuthnService_GetPrincipal_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AuthnService_GetPrincipal_Call) RunAndReturn(run func(context.Context, ...authenticate.ClientAssertion) (authenticate.Principal, error)) *AuthnService_GetPrincipal_Call { + _c.Call.Return(run) + return _c +} + +// NewAuthnService creates a new instance of AuthnService. 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 NewAuthnService(t interface { + mock.TestingT + Cleanup(func()) +}) *AuthnService { + mock := &AuthnService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/group/mocks/policy_service.go b/core/group/mocks/policy_service.go new file mode 100644 index 000000000..776b4242c --- /dev/null +++ b/core/group/mocks/policy_service.go @@ -0,0 +1,260 @@ +// Code generated by mockery v2.40.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + policy "github.com/raystack/frontier/core/policy" +) + +// PolicyService is an autogenerated mock type for the PolicyService type +type PolicyService struct { + mock.Mock +} + +type PolicyService_Expecter struct { + mock *mock.Mock +} + +func (_m *PolicyService) EXPECT() *PolicyService_Expecter { + return &PolicyService_Expecter{mock: &_m.Mock} +} + +// Create provides a mock function with given fields: ctx, _a1 +func (_m *PolicyService) Create(ctx context.Context, _a1 policy.Policy) (policy.Policy, error) { + ret := _m.Called(ctx, _a1) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 policy.Policy + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, policy.Policy) (policy.Policy, error)); ok { + return rf(ctx, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, policy.Policy) policy.Policy); ok { + r0 = rf(ctx, _a1) + } else { + r0 = ret.Get(0).(policy.Policy) + } + + if rf, ok := ret.Get(1).(func(context.Context, policy.Policy) error); ok { + r1 = rf(ctx, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PolicyService_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type PolicyService_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - ctx context.Context +// - _a1 policy.Policy +func (_e *PolicyService_Expecter) Create(ctx interface{}, _a1 interface{}) *PolicyService_Create_Call { + return &PolicyService_Create_Call{Call: _e.mock.On("Create", ctx, _a1)} +} + +func (_c *PolicyService_Create_Call) Run(run func(ctx context.Context, _a1 policy.Policy)) *PolicyService_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(policy.Policy)) + }) + return _c +} + +func (_c *PolicyService_Create_Call) Return(_a0 policy.Policy, _a1 error) *PolicyService_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *PolicyService_Create_Call) RunAndReturn(run func(context.Context, policy.Policy) (policy.Policy, error)) *PolicyService_Create_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function with given fields: ctx, id +func (_m *PolicyService) 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 +} + +// PolicyService_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type PolicyService_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *PolicyService_Expecter) Delete(ctx interface{}, id interface{}) *PolicyService_Delete_Call { + return &PolicyService_Delete_Call{Call: _e.mock.On("Delete", ctx, id)} +} + +func (_c *PolicyService_Delete_Call) Run(run func(ctx context.Context, id string)) *PolicyService_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *PolicyService_Delete_Call) Return(_a0 error) *PolicyService_Delete_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *PolicyService_Delete_Call) RunAndReturn(run func(context.Context, string) error) *PolicyService_Delete_Call { + _c.Call.Return(run) + return _c +} + +// GroupMemberCount provides a mock function with given fields: ctx, ids +func (_m *PolicyService) GroupMemberCount(ctx context.Context, ids []string) ([]policy.MemberCount, error) { + ret := _m.Called(ctx, ids) + + if len(ret) == 0 { + panic("no return value specified for GroupMemberCount") + } + + var r0 []policy.MemberCount + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]policy.MemberCount, error)); ok { + return rf(ctx, ids) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []policy.MemberCount); ok { + r0 = rf(ctx, ids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]policy.MemberCount) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { + r1 = rf(ctx, ids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PolicyService_GroupMemberCount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GroupMemberCount' +type PolicyService_GroupMemberCount_Call struct { + *mock.Call +} + +// GroupMemberCount is a helper method to define mock.On call +// - ctx context.Context +// - ids []string +func (_e *PolicyService_Expecter) GroupMemberCount(ctx interface{}, ids interface{}) *PolicyService_GroupMemberCount_Call { + return &PolicyService_GroupMemberCount_Call{Call: _e.mock.On("GroupMemberCount", ctx, ids)} +} + +func (_c *PolicyService_GroupMemberCount_Call) Run(run func(ctx context.Context, ids []string)) *PolicyService_GroupMemberCount_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]string)) + }) + return _c +} + +func (_c *PolicyService_GroupMemberCount_Call) Return(_a0 []policy.MemberCount, _a1 error) *PolicyService_GroupMemberCount_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *PolicyService_GroupMemberCount_Call) RunAndReturn(run func(context.Context, []string) ([]policy.MemberCount, error)) *PolicyService_GroupMemberCount_Call { + _c.Call.Return(run) + return _c +} + +// List provides a mock function with given fields: ctx, flt +func (_m *PolicyService) List(ctx context.Context, flt policy.Filter) ([]policy.Policy, error) { + ret := _m.Called(ctx, flt) + + if len(ret) == 0 { + panic("no return value specified for List") + } + + var r0 []policy.Policy + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, policy.Filter) ([]policy.Policy, error)); ok { + return rf(ctx, flt) + } + if rf, ok := ret.Get(0).(func(context.Context, policy.Filter) []policy.Policy); ok { + r0 = rf(ctx, flt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]policy.Policy) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, policy.Filter) error); ok { + r1 = rf(ctx, flt) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PolicyService_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' +type PolicyService_List_Call struct { + *mock.Call +} + +// List is a helper method to define mock.On call +// - ctx context.Context +// - flt policy.Filter +func (_e *PolicyService_Expecter) List(ctx interface{}, flt interface{}) *PolicyService_List_Call { + return &PolicyService_List_Call{Call: _e.mock.On("List", ctx, flt)} +} + +func (_c *PolicyService_List_Call) Run(run func(ctx context.Context, flt policy.Filter)) *PolicyService_List_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(policy.Filter)) + }) + return _c +} + +func (_c *PolicyService_List_Call) Return(_a0 []policy.Policy, _a1 error) *PolicyService_List_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *PolicyService_List_Call) RunAndReturn(run func(context.Context, policy.Filter) ([]policy.Policy, error)) *PolicyService_List_Call { + _c.Call.Return(run) + return _c +} + +// NewPolicyService creates a new instance of PolicyService. 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 NewPolicyService(t interface { + mock.TestingT + Cleanup(func()) +}) *PolicyService { + mock := &PolicyService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/group/mocks/relation_service.go b/core/group/mocks/relation_service.go new file mode 100644 index 000000000..6fbfee0cb --- /dev/null +++ b/core/group/mocks/relation_service.go @@ -0,0 +1,319 @@ +// Code generated by mockery v2.40.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + relation "github.com/raystack/frontier/core/relation" +) + +// 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} +} + +// 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 +} + +// ListRelations provides a mock function with given fields: ctx, rel +func (_m *RelationService) ListRelations(ctx context.Context, rel relation.Relation) ([]relation.Relation, error) { + ret := _m.Called(ctx, rel) + + if len(ret) == 0 { + panic("no return value specified for ListRelations") + } + + 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 { + if ret.Get(0) != nil { + 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_ListRelations_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRelations' +type RelationService_ListRelations_Call struct { + *mock.Call +} + +// ListRelations is a helper method to define mock.On call +// - ctx context.Context +// - rel relation.Relation +func (_e *RelationService_Expecter) ListRelations(ctx interface{}, rel interface{}) *RelationService_ListRelations_Call { + return &RelationService_ListRelations_Call{Call: _e.mock.On("ListRelations", ctx, rel)} +} + +func (_c *RelationService_ListRelations_Call) Run(run func(ctx context.Context, rel relation.Relation)) *RelationService_ListRelations_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(relation.Relation)) + }) + return _c +} + +func (_c *RelationService_ListRelations_Call) Return(_a0 []relation.Relation, _a1 error) *RelationService_ListRelations_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RelationService_ListRelations_Call) RunAndReturn(run func(context.Context, relation.Relation) ([]relation.Relation, error)) *RelationService_ListRelations_Call { + _c.Call.Return(run) + return _c +} + +// LookupResources provides a mock function with given fields: ctx, rel +func (_m *RelationService) LookupResources(ctx context.Context, rel relation.Relation) ([]string, error) { + ret := _m.Called(ctx, rel) + + if len(ret) == 0 { + panic("no return value specified for LookupResources") + } + + 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_LookupResources_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LookupResources' +type RelationService_LookupResources_Call struct { + *mock.Call +} + +// LookupResources is a helper method to define mock.On call +// - ctx context.Context +// - rel relation.Relation +func (_e *RelationService_Expecter) LookupResources(ctx interface{}, rel interface{}) *RelationService_LookupResources_Call { + return &RelationService_LookupResources_Call{Call: _e.mock.On("LookupResources", ctx, rel)} +} + +func (_c *RelationService_LookupResources_Call) Run(run func(ctx context.Context, rel relation.Relation)) *RelationService_LookupResources_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(relation.Relation)) + }) + return _c +} + +func (_c *RelationService_LookupResources_Call) Return(_a0 []string, _a1 error) *RelationService_LookupResources_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RelationService_LookupResources_Call) RunAndReturn(run func(context.Context, relation.Relation) ([]string, error)) *RelationService_LookupResources_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/group/mocks/repository.go b/core/group/mocks/repository.go new file mode 100644 index 000000000..07cc5ce4c --- /dev/null +++ b/core/group/mocks/repository.go @@ -0,0 +1,485 @@ +// Code generated by mockery v2.40.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + group "github.com/raystack/frontier/core/group" + mock "github.com/stretchr/testify/mock" + + relation "github.com/raystack/frontier/core/relation" +) + +// 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, grp +func (_m *Repository) Create(ctx context.Context, grp group.Group) (group.Group, error) { + ret := _m.Called(ctx, grp) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 group.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, group.Group) (group.Group, error)); ok { + return rf(ctx, grp) + } + if rf, ok := ret.Get(0).(func(context.Context, group.Group) group.Group); ok { + r0 = rf(ctx, grp) + } else { + r0 = ret.Get(0).(group.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, group.Group) error); ok { + r1 = rf(ctx, grp) + } 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 +// - grp group.Group +func (_e *Repository_Expecter) Create(ctx interface{}, grp interface{}) *Repository_Create_Call { + return &Repository_Create_Call{Call: _e.mock.On("Create", ctx, grp)} +} + +func (_c *Repository_Create_Call) Run(run func(ctx context.Context, grp group.Group)) *Repository_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(group.Group)) + }) + return _c +} + +func (_c *Repository_Create_Call) Return(_a0 group.Group, _a1 error) *Repository_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Repository_Create_Call) RunAndReturn(run func(context.Context, group.Group) (group.Group, 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) (group.Group, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetByID") + } + + var r0 group.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (group.Group, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) group.Group); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(group.Group) + } + + 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 group.Group, _a1 error) *Repository_GetByID_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Repository_GetByID_Call) RunAndReturn(run func(context.Context, string) (group.Group, error)) *Repository_GetByID_Call { + _c.Call.Return(run) + return _c +} + +// GetByIDs provides a mock function with given fields: ctx, groupIDs, flt +func (_m *Repository) GetByIDs(ctx context.Context, groupIDs []string, flt group.Filter) ([]group.Group, error) { + ret := _m.Called(ctx, groupIDs, flt) + + if len(ret) == 0 { + panic("no return value specified for GetByIDs") + } + + var r0 []group.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string, group.Filter) ([]group.Group, error)); ok { + return rf(ctx, groupIDs, flt) + } + if rf, ok := ret.Get(0).(func(context.Context, []string, group.Filter) []group.Group); ok { + r0 = rf(ctx, groupIDs, flt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]group.Group) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string, group.Filter) error); ok { + r1 = rf(ctx, groupIDs, flt) + } 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 +// - groupIDs []string +// - flt group.Filter +func (_e *Repository_Expecter) GetByIDs(ctx interface{}, groupIDs interface{}, flt interface{}) *Repository_GetByIDs_Call { + return &Repository_GetByIDs_Call{Call: _e.mock.On("GetByIDs", ctx, groupIDs, flt)} +} + +func (_c *Repository_GetByIDs_Call) Run(run func(ctx context.Context, groupIDs []string, flt group.Filter)) *Repository_GetByIDs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]string), args[2].(group.Filter)) + }) + return _c +} + +func (_c *Repository_GetByIDs_Call) Return(_a0 []group.Group, _a1 error) *Repository_GetByIDs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Repository_GetByIDs_Call) RunAndReturn(run func(context.Context, []string, group.Filter) ([]group.Group, 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 group.Filter) ([]group.Group, error) { + ret := _m.Called(ctx, flt) + + if len(ret) == 0 { + panic("no return value specified for List") + } + + var r0 []group.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, group.Filter) ([]group.Group, error)); ok { + return rf(ctx, flt) + } + if rf, ok := ret.Get(0).(func(context.Context, group.Filter) []group.Group); ok { + r0 = rf(ctx, flt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]group.Group) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, group.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 group.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 group.Filter)) *Repository_List_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(group.Filter)) + }) + return _c +} + +func (_c *Repository_List_Call) Return(_a0 []group.Group, _a1 error) *Repository_List_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Repository_List_Call) RunAndReturn(run func(context.Context, group.Filter) ([]group.Group, error)) *Repository_List_Call { + _c.Call.Return(run) + return _c +} + +// ListGroupRelations provides a mock function with given fields: ctx, objectId, subjectType, role +func (_m *Repository) ListGroupRelations(ctx context.Context, objectId string, subjectType string, role string) ([]relation.Relation, error) { + ret := _m.Called(ctx, objectId, subjectType, role) + + if len(ret) == 0 { + panic("no return value specified for ListGroupRelations") + } + + var r0 []relation.Relation + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) ([]relation.Relation, error)); ok { + return rf(ctx, objectId, subjectType, role) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) []relation.Relation); ok { + r0 = rf(ctx, objectId, subjectType, role) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]relation.Relation) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok { + r1 = rf(ctx, objectId, subjectType, role) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Repository_ListGroupRelations_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListGroupRelations' +type Repository_ListGroupRelations_Call struct { + *mock.Call +} + +// ListGroupRelations is a helper method to define mock.On call +// - ctx context.Context +// - objectId string +// - subjectType string +// - role string +func (_e *Repository_Expecter) ListGroupRelations(ctx interface{}, objectId interface{}, subjectType interface{}, role interface{}) *Repository_ListGroupRelations_Call { + return &Repository_ListGroupRelations_Call{Call: _e.mock.On("ListGroupRelations", ctx, objectId, subjectType, role)} +} + +func (_c *Repository_ListGroupRelations_Call) Run(run func(ctx context.Context, objectId string, subjectType string, role string)) *Repository_ListGroupRelations_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 *Repository_ListGroupRelations_Call) Return(_a0 []relation.Relation, _a1 error) *Repository_ListGroupRelations_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Repository_ListGroupRelations_Call) RunAndReturn(run func(context.Context, string, string, string) ([]relation.Relation, error)) *Repository_ListGroupRelations_Call { + _c.Call.Return(run) + return _c +} + +// SetState provides a mock function with given fields: ctx, id, state +func (_m *Repository) SetState(ctx context.Context, id string, state group.State) error { + ret := _m.Called(ctx, id, state) + + if len(ret) == 0 { + panic("no return value specified for SetState") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, group.State) error); ok { + r0 = rf(ctx, id, state) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Repository_SetState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetState' +type Repository_SetState_Call struct { + *mock.Call +} + +// SetState is a helper method to define mock.On call +// - ctx context.Context +// - id string +// - state group.State +func (_e *Repository_Expecter) SetState(ctx interface{}, id interface{}, state interface{}) *Repository_SetState_Call { + return &Repository_SetState_Call{Call: _e.mock.On("SetState", ctx, id, state)} +} + +func (_c *Repository_SetState_Call) Run(run func(ctx context.Context, id string, state group.State)) *Repository_SetState_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(group.State)) + }) + return _c +} + +func (_c *Repository_SetState_Call) Return(_a0 error) *Repository_SetState_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Repository_SetState_Call) RunAndReturn(run func(context.Context, string, group.State) error) *Repository_SetState_Call { + _c.Call.Return(run) + return _c +} + +// UpdateByID provides a mock function with given fields: ctx, toUpdate +func (_m *Repository) UpdateByID(ctx context.Context, toUpdate group.Group) (group.Group, error) { + ret := _m.Called(ctx, toUpdate) + + if len(ret) == 0 { + panic("no return value specified for UpdateByID") + } + + var r0 group.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, group.Group) (group.Group, error)); ok { + return rf(ctx, toUpdate) + } + if rf, ok := ret.Get(0).(func(context.Context, group.Group) group.Group); ok { + r0 = rf(ctx, toUpdate) + } else { + r0 = ret.Get(0).(group.Group) + } + + if rf, ok := ret.Get(1).(func(context.Context, group.Group) error); ok { + r1 = rf(ctx, toUpdate) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Repository_UpdateByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateByID' +type Repository_UpdateByID_Call struct { + *mock.Call +} + +// UpdateByID is a helper method to define mock.On call +// - ctx context.Context +// - toUpdate group.Group +func (_e *Repository_Expecter) UpdateByID(ctx interface{}, toUpdate interface{}) *Repository_UpdateByID_Call { + return &Repository_UpdateByID_Call{Call: _e.mock.On("UpdateByID", ctx, toUpdate)} +} + +func (_c *Repository_UpdateByID_Call) Run(run func(ctx context.Context, toUpdate group.Group)) *Repository_UpdateByID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(group.Group)) + }) + return _c +} + +func (_c *Repository_UpdateByID_Call) Return(_a0 group.Group, _a1 error) *Repository_UpdateByID_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Repository_UpdateByID_Call) RunAndReturn(run func(context.Context, group.Group) (group.Group, error)) *Repository_UpdateByID_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/group/service_test.go b/core/group/service_test.go new file mode 100644 index 000000000..dfe1a64d7 --- /dev/null +++ b/core/group/service_test.go @@ -0,0 +1,264 @@ +package group_test + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + "github.com/google/uuid" + "github.com/raystack/frontier/core/authenticate" + "github.com/raystack/frontier/core/group" + "github.com/raystack/frontier/core/group/mocks" + "github.com/raystack/frontier/core/policy" + "github.com/raystack/frontier/core/relation" + "github.com/raystack/frontier/core/user" + "github.com/raystack/frontier/internal/bootstrap/schema" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestService_Create(t *testing.T) { + t.Run("should create group successfully by adding member to org, adding relation between group and org, and making current user owner", func(t *testing.T) { + mockRepo := mocks.NewRepository(t) + mockAuthnSvc := mocks.NewAuthnService(t) + mockRelationSvc := mocks.NewRelationService(t) + mockPolicySvc := mocks.NewPolicyService(t) + + svc := group.NewService(mockRepo, mockRelationSvc, mockAuthnSvc, mockPolicySvc) + + mockUserID := uuid.New() + mockAuthnSvc.On("GetPrincipal", mock.Anything).Return(authenticate.Principal{ + ID: mockUserID.String(), + Type: "user", + User: &user.User{ + ID: mockUserID.String(), + }, + }, nil) + + groupParam := group.Group{ + Name: "test-group", + Title: "Test Group", + OrganizationID: uuid.New().String(), + } + + groupInRepo := groupParam + groupInRepo.ID = uuid.New().String() + mockRepo.On("Create", mock.Anything, groupParam).Return(groupInRepo, nil) + + // when adding group as org member + mockRelationSvc.On("Create", mock.Anything, mock.AnythingOfType("relation.Relation")).Run(func(args mock.Arguments) { + arg := args.Get(1) + r := arg.(relation.Relation) + assert.Equal(t, r.Object.ID, groupInRepo.OrganizationID) + assert.Equal(t, r.Subject.ID, groupInRepo.ID) + assert.Equal(t, r.RelationName, schema.MemberRelationName) + }).Return(relation.Relation{}, nil).Once() + + // when adding group to org + mockRelationSvc.On("Create", mock.Anything, mock.AnythingOfType("relation.Relation")).Run(func(args mock.Arguments) { + arg := args.Get(1) + r := arg.(relation.Relation) + assert.Equal(t, r.Object.ID, groupInRepo.ID) + assert.Equal(t, r.Subject.ID, groupInRepo.OrganizationID) + assert.Equal(t, r.RelationName, schema.OrganizationRelationName) + }).Return(relation.Relation{}, nil).Once() + + // when adding current user as group owner + mockPolicySvc.On("Create", mock.Anything, mock.AnythingOfType("policy.Policy")).Run(func(args mock.Arguments) { + arg := args.Get(1) + r := arg.(policy.Policy) + assert.Equal(t, r.RoleID, schema.GroupOwnerRole) + assert.Equal(t, r.ResourceID, groupInRepo.ID) + assert.Equal(t, r.ResourceType, schema.GroupNamespace) + assert.Equal(t, r.PrincipalID, mockUserID.String()) + assert.Equal(t, r.PrincipalType, "user") + }).Return(policy.Policy{}, nil).Once() + + // adding relation between group and user + mockRelationSvc.On("Create", mock.Anything, mock.AnythingOfType("relation.Relation")).Run(func(args mock.Arguments) { + arg := args.Get(1) + r := arg.(relation.Relation) + assert.Equal(t, r.Object.ID, groupInRepo.ID) + assert.Equal(t, r.Object.Namespace, schema.GroupNamespace) + assert.Equal(t, r.Subject.ID, mockUserID.String()) + assert.Equal(t, r.Subject.Namespace, "user") + assert.Equal(t, r.RelationName, schema.OwnerRelationName) + }).Return(relation.Relation{}, nil).Once() + + grp, err := svc.Create(context.Background(), groupParam) + + assert.Nil(t, err) + assert.Equal(t, grp.Name, groupParam.Name) + }) + + t.Run("should return an error if principal is not found", func(t *testing.T) { + mockRepo := mocks.NewRepository(t) + mockAuthnSvc := mocks.NewAuthnService(t) + mockRelationSvc := mocks.NewRelationService(t) + mockPolicySvc := mocks.NewPolicyService(t) + + svc := group.NewService(mockRepo, mockRelationSvc, mockAuthnSvc, mockPolicySvc) + + mockAuthnSvc.On("GetPrincipal", mock.Anything).Return(authenticate.Principal{}, errors.New("internal-error")) + + _, err := svc.Create(context.Background(), group.Group{}) + assert.NotNil(t, err) + assert.Equal(t, strings.Contains(err.Error(), authenticate.ErrInvalidID.Error()), true) + }) +} + +func TestService_Get(t *testing.T) { + mockRepo := mocks.NewRepository(t) + mockAuthnSvc := mocks.NewAuthnService(t) + mockRelationSvc := mocks.NewRelationService(t) + mockPolicySvc := mocks.NewPolicyService(t) + + t.Run("should return group if present", func(t *testing.T) { + svc := group.NewService(mockRepo, mockRelationSvc, mockAuthnSvc, mockPolicySvc) + paramID := uuid.New().String() + + expectedGroup := group.Group{ + ID: paramID, + Name: "test-group", + Title: "Test Group", + } + mockRepo.On("GetByID", mock.Anything, paramID).Return(expectedGroup, nil).Once() + actual, err := svc.Get(context.Background(), paramID) + + assert.Nil(t, err) + assert.Equal(t, expectedGroup, actual) + }) + + t.Run("should return error if group is not present", func(t *testing.T) { + svc := group.NewService(mockRepo, mockRelationSvc, mockAuthnSvc, mockPolicySvc) + paramID := uuid.New().String() + + mockRepo.On("GetByID", mock.Anything, paramID).Return(group.Group{}, group.ErrNotExist).Once() + _, err := svc.Get(context.Background(), paramID) + assert.NotNil(t, err) + assert.Equal(t, err, group.ErrNotExist) + }) +} + +func TestService_GetByIDs(t *testing.T) { + mockRepo := mocks.NewRepository(t) + mockAuthnSvc := mocks.NewAuthnService(t) + mockRelationSvc := mocks.NewRelationService(t) + mockPolicySvc := mocks.NewPolicyService(t) + + t.Run("should return group if present", func(t *testing.T) { + svc := group.NewService(mockRepo, mockRelationSvc, mockAuthnSvc, mockPolicySvc) + ID1 := uuid.New().String() + ID2 := uuid.New().String() + + expectedGroup1 := group.Group{ + ID: ID1, + Name: "test-group-2", + Title: "Test Group One", + } + expectedGroup2 := group.Group{ + ID: ID2, + Name: "test-group-2", + Title: "Test Group Two", + } + + expectedGroups := []group.Group{expectedGroup1, expectedGroup2} + + mockRepo.On("GetByIDs", mock.Anything, []string{ID1, ID2}, group.Filter{}).Return(expectedGroups, nil).Once() + actualGroups, err := svc.GetByIDs(context.Background(), []string{ID1, ID2}) + + assert.Nil(t, err) + assert.Equal(t, len(actualGroups), 2) + assert.ElementsMatch(t, expectedGroups, actualGroups) + }) + + t.Run("should return error if no groups are found", func(t *testing.T) { + svc := group.NewService(mockRepo, mockRelationSvc, mockAuthnSvc, mockPolicySvc) + ID1 := uuid.New().String() + ID2 := uuid.New().String() + + mockRepo.On("GetByIDs", mock.Anything, []string{ID1, ID2}, group.Filter{}).Return([]group.Group{}, group.ErrNotExist).Once() + actualGroups, err := svc.GetByIDs(context.Background(), []string{ID1, ID2}) + + assert.Equal(t, len(actualGroups), 0) + assert.NotNil(t, err) + assert.Equal(t, err, group.ErrNotExist) + }) +} + +func TestService_List(t *testing.T) { + mockRepo := mocks.NewRepository(t) + mockAuthnSvc := mocks.NewAuthnService(t) + mockRelationSvc := mocks.NewRelationService(t) + mockPolicySvc := mocks.NewPolicyService(t) + + t.Run("should return list of users based on filters passed", func(t *testing.T) { + svc := group.NewService(mockRepo, mockRelationSvc, mockAuthnSvc, mockPolicySvc) + + flt := group.Filter{ + OrganizationID: "123123123", + WithMemberCount: true, + } + g1 := group.Group{ + ID: "123", + Name: "group-1", + } + g2 := group.Group{ + ID: "456", + Name: "group-2", + } + mockRepo.On("List", mock.Anything, flt).Return([]group.Group{g1, g2}, nil) + mockPolicySvc.On("GroupMemberCount", mock.Anything, []string{"123", "456"}).Return([]policy.MemberCount{{ID: "123", Count: 4}, {ID: "456", Count: 10}}, nil) + + receivedGroups, err := svc.List(context.Background(), flt) + fmt.Println(receivedGroups) + + expectedGroup1 := g1 + expectedGroup1.MemberCount = 4 + + expectedGroup2 := g2 + expectedGroup2.MemberCount = 10 + assert.Nil(t, err) + assert.ElementsMatch(t, receivedGroups, []group.Group{expectedGroup1, expectedGroup2}) + }) + + t.Run("should return an error if no org id or groupID filter is passed", func(t *testing.T) { + svc := group.NewService(mockRepo, mockRelationSvc, mockAuthnSvc, mockPolicySvc) + flt := group.Filter{} + + _, err := svc.List(context.Background(), flt) + assert.NotNil(t, err) + assert.Equal(t, err, group.ErrInvalidID) + }) +} + +func TestService_Update(t *testing.T) { + mockRepo := mocks.NewRepository(t) + mockAuthnSvc := mocks.NewAuthnService(t) + mockRelationSvc := mocks.NewRelationService(t) + mockPolicySvc := mocks.NewPolicyService(t) + + t.Run("should update the group parameters as requested", func(t *testing.T) { + svc := group.NewService(mockRepo, mockRelationSvc, mockAuthnSvc, mockPolicySvc) + + groupToBeUpdated := group.Group{ + ID: "123123", + Name: "test-group", + Title: "Test Group", + } + mockRepo.On("UpdateByID", mock.Anything, groupToBeUpdated).Return(groupToBeUpdated, nil) + grp, err := svc.Update(context.Background(), groupToBeUpdated) + + assert.Nil(t, err) + assert.Equal(t, grp, groupToBeUpdated) + }) + + t.Run("should return an error if group id is empty", func(t *testing.T) { + svc := group.NewService(mockRepo, mockRelationSvc, mockAuthnSvc, mockPolicySvc) + _, err := svc.Update(context.Background(), group.Group{ID: ""}) + assert.NotNil(t, err) + assert.Equal(t, err, group.ErrInvalidID) + }) +}