diff --git a/cmd/serve.go b/cmd/serve.go index 563f6d977..7edc84c77 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -260,7 +260,7 @@ func buildAPIDependencies( ) organizationRepository := postgres.NewOrganizationRepository(dbc) - organizationService := organization.NewService(organizationRepository, relationService, userService, authnService) + organizationService := organization.NewService(organizationRepository, relationService, userService, authnService, cfg.App.DisableOrgsOnCreate) domainRepository := postgres.NewDomainRepository(logger, dbc) domainService := domain.NewService(logger, domainRepository, userService, organizationService) diff --git a/config/sample.config.yaml b/config/sample.config.yaml index 33cb1decc..666b05915 100644 --- a/config/sample.config.yaml +++ b/config/sample.config.yaml @@ -29,6 +29,9 @@ app: # secret string "val://user:password" # optional resources_config_path_secret: env://TEST_RESOURCE_CONFIG_SECRET + # disable_orgs_on_create if set to true will set the org status to disabled on creation. This can be used to + # prevent users from accessing the org until they contact the admin and get it enabled. Default is false + disable_orgs_on_create: false # disable_orgs_listing if set to true will disallow non-admin APIs to list all organizations disable_orgs_listing: false # disable_orgs_listing if set to true will disallow non-admin APIs to list all users diff --git a/core/organization/errors.go b/core/organization/errors.go index bea0f1581..ea19f39ec 100644 --- a/core/organization/errors.go +++ b/core/organization/errors.go @@ -8,4 +8,5 @@ var ( ErrInvalidID = errors.New("org id is invalid") ErrConflict = errors.New("org already exist") ErrInvalidDetail = errors.New("invalid org detail") + ErrDisabled = errors.New("org is disabled") ) diff --git a/core/organization/service.go b/core/organization/service.go index d1049400e..1a43bc67d 100644 --- a/core/organization/service.go +++ b/core/organization/service.go @@ -33,19 +33,49 @@ type Service struct { relationService RelationService userService UserService authnService AuthnService + defaultState State } func NewService(repository Repository, relationService RelationService, - userService UserService, authnService AuthnService) *Service { + userService UserService, authnService AuthnService, disableOrgsOnCreate bool) *Service { + defaultState := Enabled + if disableOrgsOnCreate { + defaultState = Disabled + } return &Service{ repository: repository, relationService: relationService, userService: userService, authnService: authnService, + defaultState: defaultState, } } +// Get returns an enabled organization by id or name. Will return `org is disabled` error if the organization is disabled func (s Service) Get(ctx context.Context, idOrName string) (Organization, error) { + if utils.IsValidUUID(idOrName) { + orgResp, err := s.repository.GetByID(ctx, idOrName) + if err != nil { + return Organization{}, err + } + if orgResp.State == Disabled { + return Organization{}, ErrDisabled + } + return orgResp, nil + } + + orgResp, err := s.repository.GetByName(ctx, idOrName) + if err != nil { + return Organization{}, err + } + if orgResp.State == Disabled { + return Organization{}, ErrDisabled + } + return orgResp, nil +} + +// GetRaw returns an organization(both enabled and disabled) by id or name +func (s Service) GetRaw(ctx context.Context, idOrName string) (Organization, error) { if utils.IsValidUUID(idOrName) { return s.repository.GetByID(ctx, idOrName) } @@ -63,6 +93,7 @@ func (s Service) Create(ctx context.Context, org Organization) (Organization, er Title: org.Title, Avatar: org.Avatar, Metadata: org.Metadata, + State: s.defaultState, }) if err != nil { return Organization{}, err diff --git a/docs/docs/configurations.md b/docs/docs/configurations.md index d311d5360..3a2e2b0cb 100644 --- a/docs/docs/configurations.md +++ b/docs/docs/configurations.md @@ -68,6 +68,9 @@ app: # secret string "val://user:password" # optional resources_config_path_secret: env://TEST_RESOURCE_CONFIG_SECRET + # disable_orgs_on_create if set to true will set the org status to disabled on creation. This can be used to + # prevent users from accessing the org until they contact the admin and get it enabled. Default is false + disable_orgs_on_create: false # disable_orgs_listing if set to true will disallow non-admin APIs to list all organizations disable_orgs_listing: false # disable_orgs_listing if set to true will disallow non-admin APIs to list all users diff --git a/docs/docs/reference/configurations.md b/docs/docs/reference/configurations.md index c63d536a9..c81c7d40d 100644 --- a/docs/docs/reference/configurations.md +++ b/docs/docs/reference/configurations.md @@ -35,6 +35,9 @@ app: # secret string "val://user:password" # optional resources_config_path_secret: env://TEST_RESOURCE_CONFIG_SECRET + # disable_orgs_on_create if set to true will set the org status to disabled on creation. This can be used to + # prevent users from accessing the org until they contact the admin and get it enabled. Default is false + disable_orgs_on_create: false # disable_orgs_listing if set to true will disallow non-admin APIs to list all organizations disable_orgs_listing: false # disable_orgs_listing if set to true will disallow non-admin APIs to list all users diff --git a/internal/api/v1beta1/audit.go b/internal/api/v1beta1/audit.go index fb8caba81..d40809515 100644 --- a/internal/api/v1beta1/audit.go +++ b/internal/api/v1beta1/audit.go @@ -5,6 +5,8 @@ import ( grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" "github.com/raystack/frontier/core/audit" + "github.com/raystack/frontier/core/organization" + "github.com/raystack/frontier/pkg/errors" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -17,14 +19,22 @@ type AuditService interface { func (h Handler) ListOrganizationAuditLogs(ctx context.Context, request *frontierv1beta1.ListOrganizationAuditLogsRequest) (*frontierv1beta1.ListOrganizationAuditLogsResponse, error) { logger := grpczap.Extract(ctx) - - if request.GetOrgId() == "" { - return nil, grpcBadBodyError + orgResp, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } } var logs []*frontierv1beta1.AuditLog logList, err := h.auditService.List(ctx, audit.Filter{ - OrgID: request.GetOrgId(), + OrgID: orgResp.ID, Source: request.GetSource(), Action: request.GetAction(), StartTime: request.GetStartTime().AsTime(), @@ -45,8 +55,17 @@ func (h Handler) ListOrganizationAuditLogs(ctx context.Context, request *frontie func (h Handler) CreateOrganizationAuditLogs(ctx context.Context, request *frontierv1beta1.CreateOrganizationAuditLogsRequest) (*frontierv1beta1.CreateOrganizationAuditLogsResponse, error) { logger := grpczap.Extract(ctx) - if request.GetOrgId() == "" || request.GetLogs() == nil { - return nil, grpcBadBodyError + orgResp, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } } for _, log := range request.GetLogs() { @@ -55,7 +74,7 @@ func (h Handler) CreateOrganizationAuditLogs(ctx context.Context, request *front } if err := h.auditService.Create(ctx, &audit.Log{ ID: log.GetId(), - OrgID: request.GetOrgId(), + OrgID: orgResp.ID, Source: log.Source, Action: log.Action, @@ -81,9 +100,17 @@ func (h Handler) CreateOrganizationAuditLogs(ctx context.Context, request *front func (h Handler) GetOrganizationAuditLog(ctx context.Context, request *frontierv1beta1.GetOrganizationAuditLogRequest) (*frontierv1beta1.GetOrganizationAuditLogResponse, error) { logger := grpczap.Extract(ctx) - - if request.OrgId == "" || request.GetId() == "" { - return nil, grpcBadBodyError + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } } log, err := h.auditService.GetByID(ctx, request.GetId()) diff --git a/internal/api/v1beta1/audit_test.go b/internal/api/v1beta1/audit_test.go index f2cee0757..a45a9a913 100644 --- a/internal/api/v1beta1/audit_test.go +++ b/internal/api/v1beta1/audit_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/raystack/frontier/core/audit" + "github.com/raystack/frontier/core/organization" "github.com/raystack/frontier/internal/api/v1beta1/mocks" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" "github.com/stretchr/testify/assert" @@ -17,16 +18,17 @@ import ( func TestHandler_ListOrganizationAuditLogs(t *testing.T) { tests := []struct { name string - setup func(as *mocks.AuditService) + setup func(as *mocks.AuditService, os *mocks.OrganizationService) request *frontierv1beta1.ListOrganizationAuditLogsRequest want *frontierv1beta1.ListOrganizationAuditLogsResponse wantErr error }{ { name: "should return list of audit logs", - setup: func(as *mocks.AuditService) { + setup: func(as *mocks.AuditService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "org-id").Return(testOrgMap[testOrgID], nil) as.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), audit.Filter{ - OrgID: "org-id", + OrgID: testOrgMap[testOrgID].ID, Source: "guardian-service", Action: "project.create", StartTime: time.Time{}, @@ -81,9 +83,10 @@ func TestHandler_ListOrganizationAuditLogs(t *testing.T) { }, { name: "should return error when audit service returns error", - setup: func(as *mocks.AuditService) { + setup: func(as *mocks.AuditService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "org-id").Return(testOrgMap[testOrgID], nil) as.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), audit.Filter{ - OrgID: "org-id", + OrgID: testOrgMap[testOrgID].ID, Source: "guardian-service", Action: "project.create", StartTime: time.Time{}, @@ -101,26 +104,30 @@ func TestHandler_ListOrganizationAuditLogs(t *testing.T) { wantErr: grpcInternalServerError, }, { - name: "should return error when org id is empty", + name: "should return error when org is disabled", + setup: func(as *mocks.AuditService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "org-id").Return(organization.Organization{}, organization.ErrDisabled) + }, request: &frontierv1beta1.ListOrganizationAuditLogsRequest{ - OrgId: "", + OrgId: "org-id", Source: "guardian-service", Action: "project.create", StartTime: timestamppb.New(time.Time{}), EndTime: timestamppb.New(time.Time{}), }, want: nil, - wantErr: grpcBadBodyError, + wantErr: grpcOrgDisabledErr, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockAuditSrv := new(mocks.AuditService) + mockOrgSrv := new(mocks.OrganizationService) if tt.setup != nil { - tt.setup(mockAuditSrv) + tt.setup(mockAuditSrv, mockOrgSrv) } - mockDep := Handler{auditService: mockAuditSrv} + mockDep := Handler{auditService: mockAuditSrv, orgService: mockOrgSrv} resp, err := mockDep.ListOrganizationAuditLogs(context.Background(), tt.request) assert.EqualValues(t, tt.want, resp) assert.EqualValues(t, tt.wantErr, err) @@ -131,17 +138,19 @@ func TestHandler_ListOrganizationAuditLogs(t *testing.T) { func TestHandler_CreateOrganizationAuditLogs(t *testing.T) { tests := []struct { name string - setup func(as *mocks.AuditService) + setup func(as *mocks.AuditService, os *mocks.OrganizationService) req *frontierv1beta1.CreateOrganizationAuditLogsRequest want *frontierv1beta1.CreateOrganizationAuditLogsResponse wantErr error }{ { name: "should create audit logs on success and return nil error", - setup: func(as *mocks.AuditService) { + setup: func(as *mocks.AuditService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "org-id").Return(testOrgMap[testOrgID], nil) as.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), &audit.Log{ - ID: "test-id", - OrgID: "org-id", + ID: "test-id", + OrgID: testOrgMap[testOrgID].ID, + Source: "guardian-service", Action: "project.create", CreatedAt: time.Time{}, @@ -181,17 +190,11 @@ func TestHandler_CreateOrganizationAuditLogs(t *testing.T) { want: &frontierv1beta1.CreateOrganizationAuditLogsResponse{}, wantErr: nil, }, - { - name: "should return error when missing org id or logs", - req: &frontierv1beta1.CreateOrganizationAuditLogsRequest{ - OrgId: "", - Logs: nil, - }, - want: nil, - wantErr: grpcBadBodyError, - }, { name: "should return error when log source and action is empty", + setup: func(as *mocks.AuditService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "org-id").Return(testOrgMap[testOrgID], nil) + }, req: &frontierv1beta1.CreateOrganizationAuditLogsRequest{ OrgId: "org-id", Logs: []*frontierv1beta1.AuditLog{ @@ -218,10 +221,11 @@ func TestHandler_CreateOrganizationAuditLogs(t *testing.T) { }, { name: "should return error when audit service returns error", - setup: func(as *mocks.AuditService) { + setup: func(as *mocks.AuditService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "org-id").Return(testOrgMap[testOrgID], nil) as.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), &audit.Log{ ID: "test-id", - OrgID: "org-id", + OrgID: testOrgMap[testOrgID].ID, Source: "guardian-service", Action: "project.create", CreatedAt: time.Time{}, @@ -251,11 +255,12 @@ func TestHandler_CreateOrganizationAuditLogs(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + mockOrgService := new(mocks.OrganizationService) mockAuditSrv := new(mocks.AuditService) if tt.setup != nil { - tt.setup(mockAuditSrv) + tt.setup(mockAuditSrv, mockOrgService) } - mockDep := Handler{auditService: mockAuditSrv} + mockDep := Handler{auditService: mockAuditSrv, orgService: mockOrgService} resp, err := mockDep.CreateOrganizationAuditLogs(context.Background(), tt.req) assert.EqualValues(t, tt.want, resp) assert.EqualValues(t, tt.wantErr, err) @@ -266,17 +271,18 @@ func TestHandler_CreateOrganizationAuditLogs(t *testing.T) { func TestHandler_GetOrganizationAuditLog(t *testing.T) { tests := []struct { name string - setup func(as *mocks.AuditService) + setup func(as *mocks.AuditService, os *mocks.OrganizationService) req *frontierv1beta1.GetOrganizationAuditLogRequest want *frontierv1beta1.GetOrganizationAuditLogResponse wantErr error }{ { name: "should return audit log on success", - setup: func(as *mocks.AuditService) { + setup: func(as *mocks.AuditService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "org-id").Return(testOrgMap[testOrgID], nil) as.EXPECT().GetByID(mock.AnythingOfType("*context.emptyCtx"), "test-id").Return(audit.Log{ ID: "test-id", - OrgID: "org-id", + OrgID: testOrgMap[testOrgID].ID, Source: "guardian-service", Action: "project.create", CreatedAt: time.Time{}, @@ -322,18 +328,10 @@ func TestHandler_GetOrganizationAuditLog(t *testing.T) { }, wantErr: nil, }, - { - name: "should return error when org id or log id is empty", - req: &frontierv1beta1.GetOrganizationAuditLogRequest{ - Id: "", - OrgId: "", - }, - want: nil, - wantErr: grpcBadBodyError, - }, { name: "should return error when audit service returns error", - setup: func(as *mocks.AuditService) { + setup: func(as *mocks.AuditService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "org-id").Return(testOrgMap[testOrgID], nil) as.EXPECT().GetByID(mock.AnythingOfType("*context.emptyCtx"), "test-id").Return(audit.Log{}, errors.New("test-error")) }, req: &frontierv1beta1.GetOrganizationAuditLogRequest{ @@ -347,11 +345,12 @@ func TestHandler_GetOrganizationAuditLog(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + mockOrgService := new(mocks.OrganizationService) mockAuditSrv := new(mocks.AuditService) if tt.setup != nil { - tt.setup(mockAuditSrv) + tt.setup(mockAuditSrv, mockOrgService) } - mockDep := Handler{auditService: mockAuditSrv} + mockDep := Handler{auditService: mockAuditSrv, orgService: mockOrgService} resp, err := mockDep.GetOrganizationAuditLog(context.Background(), tt.req) assert.EqualValues(t, tt.want, resp) assert.EqualValues(t, tt.wantErr, err) diff --git a/internal/api/v1beta1/domain.go b/internal/api/v1beta1/domain.go index 70c2b366d..a94558c84 100644 --- a/internal/api/v1beta1/domain.go +++ b/internal/api/v1beta1/domain.go @@ -6,6 +6,7 @@ import ( grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" "github.com/raystack/frontier/core/domain" "github.com/raystack/frontier/core/organization" + "github.com/raystack/frontier/pkg/errors" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -32,20 +33,26 @@ type DomainService interface { func (h Handler) CreateOrganizationDomain(ctx context.Context, request *frontierv1beta1.CreateOrganizationDomainRequest) (*frontierv1beta1.CreateOrganizationDomainResponse, error) { logger := grpczap.Extract(ctx) - - if request.GetOrgId() == "" || request.GetDomain() == "" { - return nil, grpcBadBodyError + orgResp, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } } dmn, err := h.domainService.Create(ctx, domain.Domain{ - OrgID: request.GetOrgId(), + OrgID: orgResp.ID, Name: request.GetDomain(), }) if err != nil { logger.Error(err.Error()) switch err { - case organization.ErrNotExist: - return nil, grpcOrgNotFoundErr case domain.ErrDuplicateKey: return nil, grpcDomainAlreadyExistsErr default: @@ -59,16 +66,22 @@ func (h Handler) CreateOrganizationDomain(ctx context.Context, request *frontier func (h Handler) DeleteOrganizationDomain(ctx context.Context, request *frontierv1beta1.DeleteOrganizationDomainRequest) (*frontierv1beta1.DeleteOrganizationDomainResponse, error) { logger := grpczap.Extract(ctx) - - if request.GetId() == "" || request.GetOrgId() == "" { - return nil, grpcBadBodyError + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } } if err := h.domainService.Delete(ctx, request.GetId()); err != nil { logger.Error(err.Error()) switch err { - case organization.ErrNotExist: - return nil, grpcOrgNotFoundErr case domain.ErrNotExist: return nil, grpcDomainNotFoundErr default: @@ -81,9 +94,17 @@ func (h Handler) DeleteOrganizationDomain(ctx context.Context, request *frontier func (h Handler) GetOrganizationDomain(ctx context.Context, request *frontierv1beta1.GetOrganizationDomainRequest) (*frontierv1beta1.GetOrganizationDomainResponse, error) { logger := grpczap.Extract(ctx) - - if request.GetId() == "" || request.GetOrgId() == "" { - return nil, grpcBadBodyError + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } } domainResp, err := h.domainService.Get(ctx, request.GetId()) @@ -103,9 +124,17 @@ func (h Handler) GetOrganizationDomain(ctx context.Context, request *frontierv1b func (h Handler) JoinOrganization(ctx context.Context, request *frontierv1beta1.JoinOrganizationRequest) (*frontierv1beta1.JoinOrganizationResponse, error) { logger := grpczap.Extract(ctx) - orgId := request.GetOrgId() - if orgId == "" { - return nil, grpcBadBodyError + orgResp, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } } // get current user @@ -115,11 +144,9 @@ func (h Handler) JoinOrganization(ctx context.Context, request *frontierv1beta1. return nil, grpcInternalServerError } - if err := h.domainService.Join(ctx, orgId, principal.ID); err != nil { + if err := h.domainService.Join(ctx, orgResp.ID, principal.ID); err != nil { logger.Error(err.Error()) switch err { - case organization.ErrNotExist: - return nil, grpcOrgNotFoundErr case domain.ErrDomainsMisMatch: return nil, grpcDomainMisMatchErr default: @@ -132,9 +159,17 @@ func (h Handler) JoinOrganization(ctx context.Context, request *frontierv1beta1. func (h Handler) VerifyOrganizationDomain(ctx context.Context, request *frontierv1beta1.VerifyOrganizationDomainRequest) (*frontierv1beta1.VerifyOrganizationDomainResponse, error) { logger := grpczap.Extract(ctx) - - if request.GetId() == "" || request.GetOrgId() == "" { - return nil, grpcBadBodyError + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } } domainResp, err := h.domainService.VerifyDomain(ctx, request.GetId()) @@ -157,12 +192,20 @@ func (h Handler) VerifyOrganizationDomain(ctx context.Context, request *frontier func (h Handler) ListOrganizationDomains(ctx context.Context, request *frontierv1beta1.ListOrganizationDomainsRequest) (*frontierv1beta1.ListOrganizationDomainsResponse, error) { logger := grpczap.Extract(ctx) - - if request.GetOrgId() == "" { - return nil, grpcBadBodyError + orgResp, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } } - domains, err := h.domainService.List(ctx, domain.Filter{OrgID: request.GetOrgId(), State: domain.Status(request.GetState())}) + domains, err := h.domainService.List(ctx, domain.Filter{OrgID: orgResp.ID, State: domain.Status(request.GetState())}) if err != nil { logger.Error(err.Error()) return nil, grpcInternalServerError diff --git a/internal/api/v1beta1/domain_test.go b/internal/api/v1beta1/domain_test.go index 11257c0fe..c3c00e3bf 100644 --- a/internal/api/v1beta1/domain_test.go +++ b/internal/api/v1beta1/domain_test.go @@ -2,13 +2,14 @@ package v1beta1 import ( "context" - "errors" "testing" "time" + "github.com/google/uuid" "github.com/raystack/frontier/core/authenticate" "github.com/raystack/frontier/core/domain" "github.com/raystack/frontier/core/organization" + "github.com/raystack/frontier/core/user" "github.com/raystack/frontier/internal/api/v1beta1/mocks" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" "github.com/stretchr/testify/assert" @@ -16,114 +17,122 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" ) +var ( + testDomainID1 = uuid.New().String() + testDomainID2 = uuid.New().String() + testDomainMap = map[string]domain.Domain{ + testDomainID1: { + ID: testDomainID1, + Name: "raystack.org", + OrgID: testOrgID, + Token: "_frontier-domain-verification=1234567890", + State: domain.Verified, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + testDomainID2: { + ID: testDomainID2, + Name: "raystack.com", + OrgID: testOrgID, + Token: "_frontier-domain-verification=1234567890", + State: domain.Pending, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + } + dm1PB = &frontierv1beta1.Domain{ + Id: testDomainID1, + Name: "raystack.org", + OrgId: testOrgID, + Token: "_frontier-domain-verification=1234567890", + State: domain.Verified.String(), + CreatedAt: timestamppb.New(time.Time{}), + UpdatedAt: timestamppb.New(time.Time{}), + } +) + func TestHandler_CreateOrganizationDomain(t *testing.T) { tests := []struct { name string - setup func(m *mocks.DomainService) - req *frontierv1beta1.CreateOrganizationDomainRequest + setup func(os *mocks.OrganizationService, ds *mocks.DomainService) + request *frontierv1beta1.CreateOrganizationDomainRequest want *frontierv1beta1.CreateOrganizationDomainResponse wantErr error }{ - {name: "should create Organization Domain on success", - setup: func(m *mocks.DomainService) { - m.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), domain.Domain{ - OrgID: "org_id", - Name: "example.com", - }).Return(domain.Domain{ - ID: "some_id", - Name: "example.com", - OrgID: "org_id", - Token: "some_token", - State: domain.Pending, - }, nil) - }, - req: &frontierv1beta1.CreateOrganizationDomainRequest{ - OrgId: "org_id", - Domain: "example.com", - }, - want: &frontierv1beta1.CreateOrganizationDomainResponse{Domain: &frontierv1beta1.Domain{ - Id: "some_id", - Name: "example.com", - OrgId: "org_id", - Token: "some_token", - State: domain.Pending.String(), - UpdatedAt: timestamppb.New(time.Time{}), - CreatedAt: timestamppb.New(time.Time{}), - }}, - wantErr: nil, - }, { - name: "should return error if Org Id or Name is empty", - setup: func(m *mocks.DomainService) { - m.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), domain.Domain{ - OrgID: "", - Name: "", - }).Return(domain.Domain{}, grpcBadBodyError) + name: "should return error when org doesn't exist", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) }, - req: &frontierv1beta1.CreateOrganizationDomainRequest{ - OrgId: "", - Domain: "example.com", + request: &frontierv1beta1.CreateOrganizationDomainRequest{ + OrgId: testOrgID, + Domain: "raystack.org", }, want: nil, - wantErr: grpcBadBodyError, + wantErr: grpcOrgNotFoundErr, }, { - name: "should return error if Organization does not exist", - setup: func(m *mocks.DomainService) { - m.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), domain.Domain{ - OrgID: "some-id", - Name: "example.com", - }).Return(domain.Domain{}, organization.ErrNotExist) + name: "should return error when org is disabled", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) }, - req: &frontierv1beta1.CreateOrganizationDomainRequest{ - OrgId: "some-id", - Domain: "example.com", + request: &frontierv1beta1.CreateOrganizationDomainRequest{ + OrgId: testOrgID, + Domain: "raystack.org", }, want: nil, - wantErr: grpcOrgNotFoundErr, + wantErr: grpcOrgDisabledErr, }, { - name: "should return error if Domain arleady exist", - setup: func(m *mocks.DomainService) { - m.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), domain.Domain{ - OrgID: "some-id", - Name: "example.com", + name: "should return error when domain already exists", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + ds.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), domain.Domain{ + OrgID: testOrgID, + Name: "raystack.org", }).Return(domain.Domain{}, domain.ErrDuplicateKey) }, - req: &frontierv1beta1.CreateOrganizationDomainRequest{ - OrgId: "some-id", - Domain: "example.com", + request: &frontierv1beta1.CreateOrganizationDomainRequest{ + OrgId: testOrgID, + Domain: "raystack.org", }, want: nil, wantErr: grpcDomainAlreadyExistsErr, }, { - name: "should return internal error if domain service return some error", - setup: func(m *mocks.DomainService) { - m.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), domain.Domain{ - OrgID: "some-id", - Name: "example.com", - }).Return(domain.Domain{}, errors.New("some error")) + name: "should create org domain with valid request", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + ds.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), domain.Domain{ + OrgID: testOrgID, + Name: "raystack.org", + }).Return(testDomainMap[testDomainID1], nil) }, - req: &frontierv1beta1.CreateOrganizationDomainRequest{ - OrgId: "some-id", - Domain: "example.com", + request: &frontierv1beta1.CreateOrganizationDomainRequest{ + OrgId: testOrgID, + Domain: "raystack.org", }, - want: nil, - wantErr: grpcInternalServerError, + want: &frontierv1beta1.CreateOrganizationDomainResponse{ + Domain: dm1PB, + }, + wantErr: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockDomain := new(mocks.DomainService) + os := &mocks.OrganizationService{} + ds := &mocks.DomainService{} if tt.setup != nil { - tt.setup(mockDomain) + tt.setup(os, ds) + } + h := Handler{ + orgService: os, + domainService: ds, } - mockDom := Handler{domainService: mockDomain} - resp, err := mockDom.CreateOrganizationDomain(context.Background(), tt.req) - assert.EqualValues(t, tt.want, resp) + got, err := h.CreateOrganizationDomain(context.Background(), tt.request) assert.EqualValues(t, tt.wantErr, err) + assert.EqualValues(t, tt.want, got) }) } } @@ -131,484 +140,401 @@ func TestHandler_CreateOrganizationDomain(t *testing.T) { func TestHandler_DeleteOrganizationDomain(t *testing.T) { tests := []struct { name string - setup func(m *mocks.DomainService) - req *frontierv1beta1.DeleteOrganizationDomainRequest + setup func(os *mocks.OrganizationService, ds *mocks.DomainService) + request *frontierv1beta1.DeleteOrganizationDomainRequest want *frontierv1beta1.DeleteOrganizationDomainResponse wantErr error }{ { - name: "should delete domain on success", - setup: func(m *mocks.DomainService) { - m.EXPECT().Delete(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(nil) + name: "should return error when org doesn't exist", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) }, - req: &frontierv1beta1.DeleteOrganizationDomainRequest{ - Id: "some_id", - OrgId: "org_id", - }, - want: &frontierv1beta1.DeleteOrganizationDomainResponse{}, - wantErr: nil, - }, - { - name: "should return error if Org Id or domain Id is empty", - setup: func(m *mocks.DomainService) { - m.EXPECT().Delete(mock.AnythingOfType("*context.emptyCtx"), "").Return(grpcBadBodyError) - }, - req: &frontierv1beta1.DeleteOrganizationDomainRequest{ - Id: "", - OrgId: "", + request: &frontierv1beta1.DeleteOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, }, want: nil, - wantErr: grpcBadBodyError, + wantErr: grpcOrgNotFoundErr, }, { - name: "should return error if organization does not exist", - setup: func(m *mocks.DomainService) { - m.EXPECT().Delete(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(organization.ErrNotExist) + name: "should return error when org is disabled", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) }, - req: &frontierv1beta1.DeleteOrganizationDomainRequest{ - Id: "some_id", - OrgId: "org_id", + request: &frontierv1beta1.DeleteOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, }, want: nil, - wantErr: grpcOrgNotFoundErr, + wantErr: grpcOrgDisabledErr, }, { - name: "should return error is domain does not exist", - setup: func(m *mocks.DomainService) { - m.EXPECT().Delete(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(domain.ErrNotExist) + name: "should return error when domain doesn't exist", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + ds.EXPECT().Delete(mock.AnythingOfType("*context.emptyCtx"), testDomainID1).Return(domain.ErrNotExist) }, - req: &frontierv1beta1.DeleteOrganizationDomainRequest{ - Id: "some_id", - OrgId: "org_id", + request: &frontierv1beta1.DeleteOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, }, want: nil, wantErr: grpcDomainNotFoundErr, }, { - name: "should return an internal server error if domain service fails to delete the domain", - setup: func(m *mocks.DomainService) { - m.EXPECT().Delete(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(errors.New("some_error")) + name: "should delete org domain with valid request", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + ds.EXPECT().Delete(mock.AnythingOfType("*context.emptyCtx"), testDomainID1).Return(nil) }, - req: &frontierv1beta1.DeleteOrganizationDomainRequest{ - Id: "some_id", - OrgId: "org_id", + request: &frontierv1beta1.DeleteOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, }, - want: nil, - wantErr: grpcInternalServerError, + want: &frontierv1beta1.DeleteOrganizationDomainResponse{}, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockDomain := new(mocks.DomainService) + os := &mocks.OrganizationService{} + ds := &mocks.DomainService{} if tt.setup != nil { - tt.setup(mockDomain) + tt.setup(os, ds) + } + h := Handler{ + orgService: os, + domainService: ds, } - mockDom := Handler{domainService: mockDomain} - resp, err := mockDom.DeleteOrganizationDomain(context.Background(), tt.req) - assert.EqualValues(t, tt.want, resp) + got, err := h.DeleteOrganizationDomain(context.Background(), tt.request) assert.EqualValues(t, tt.wantErr, err) + assert.EqualValues(t, tt.want, got) }) } } -func Test_GetOrganizationDomain(t *testing.T) { +func TestHandler_GetOrganizationDomain(t *testing.T) { tests := []struct { name string - setup func(m *mocks.DomainService) - req *frontierv1beta1.GetOrganizationDomainRequest + setup func(os *mocks.OrganizationService, ds *mocks.DomainService) + request *frontierv1beta1.GetOrganizationDomainRequest want *frontierv1beta1.GetOrganizationDomainResponse wantErr error }{ { - name: "should get the Org Domain on success", - setup: func(m *mocks.DomainService) { - m.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(domain.Domain{ - ID: "some_id", - Name: "example.com", - OrgID: "org_id", - Token: "some_token", - State: domain.Pending, - }, nil) - }, - req: &frontierv1beta1.GetOrganizationDomainRequest{ - Id: "some_id", - OrgId: "org_id", - }, - want: &frontierv1beta1.GetOrganizationDomainResponse{Domain: &frontierv1beta1.Domain{ - Id: "some_id", - Name: "example.com", - OrgId: "org_id", - Token: "some_token", - State: domain.Pending.String(), - UpdatedAt: timestamppb.New(time.Time{}), - CreatedAt: timestamppb.New(time.Time{}), - }}, - wantErr: nil, + name: "should return error when org doesn't exist", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) + }, + request: &frontierv1beta1.GetOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, + }, + want: nil, + wantErr: grpcOrgNotFoundErr, }, { - name: "should return error if Org Id or domain Id is empty", - setup: func(m *mocks.DomainService) { - m.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "").Return(domain.Domain{ - ID: "", - Name: "", - OrgID: "", - Token: "", - State: "", - }, grpcBadBodyError) - }, - req: &frontierv1beta1.GetOrganizationDomainRequest{ - Id: "", - OrgId: "", + name: "should return error when org is disabled", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) + }, + request: &frontierv1beta1.GetOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, }, want: nil, - wantErr: grpcBadBodyError, + wantErr: grpcOrgDisabledErr, }, { - name: "should return error is domain does not exist", - setup: func(m *mocks.DomainService) { - m.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(domain.Domain{ - ID: "some_id", - Name: "example.com", - OrgID: "org_id", - Token: "some_tokenpending", - State: "", - }, domain.ErrNotExist) - }, - req: &frontierv1beta1.GetOrganizationDomainRequest{ - Id: "some_id", - OrgId: "org_id", + name: "should return error when domain doesn't exist", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + ds.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testDomainID1).Return(domain.Domain{}, domain.ErrNotExist) + }, + request: &frontierv1beta1.GetOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, }, want: nil, wantErr: grpcDomainNotFoundErr, }, { - name: "should return an internal server error if domain service fails to get ", - setup: func(m *mocks.DomainService) { - m.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(domain.Domain{ - ID: "some_id", - Name: "example.com", - OrgID: "org_id", - Token: "some_tokenpending", - State: "", - }, errors.New("some_error")) - }, - req: &frontierv1beta1.GetOrganizationDomainRequest{ - Id: "some_id", - OrgId: "org_id", + name: "should get org domain with valid request", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + ds.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testDomainID1).Return(testDomainMap[testDomainID1], nil) + }, + request: &frontierv1beta1.GetOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, + }, + want: &frontierv1beta1.GetOrganizationDomainResponse{ + Domain: dm1PB, }, - want: nil, - wantErr: grpcInternalServerError, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockDomainServ := new(mocks.DomainService) + os := &mocks.OrganizationService{} + ds := &mocks.DomainService{} if tt.setup != nil { - tt.setup(mockDomainServ) + tt.setup(os, ds) + } + h := Handler{ + orgService: os, + domainService: ds, } - mockDom := Handler{domainService: mockDomainServ} - resp, err := mockDom.GetOrganizationDomain(context.Background(), tt.req) - assert.EqualValues(t, tt.want, resp) + got, err := h.GetOrganizationDomain(context.Background(), tt.request) assert.EqualValues(t, tt.wantErr, err) + assert.EqualValues(t, tt.want, got) }) } } -func Test_JoinOrganization(t *testing.T) { +func TestHandler_JoinOrganization(t *testing.T) { tests := []struct { name string - setup func(m *mocks.DomainService, a *mocks.AuthnService) - req *frontierv1beta1.JoinOrganizationRequest + setup func(os *mocks.OrganizationService, ds *mocks.DomainService, us *mocks.AuthnService) + request *frontierv1beta1.JoinOrganizationRequest want *frontierv1beta1.JoinOrganizationResponse wantErr error }{ { - name: "should join the org on success", - setup: func(m *mocks.DomainService, a *mocks.AuthnService) { - a.EXPECT().GetPrincipal(mock.AnythingOfType("*context.emptyCtx")).Return(authenticate.Principal{ - ID: "user_id", - }, nil) - m.EXPECT().Join(mock.AnythingOfType("*context.emptyCtx"), "org_id", "user_id").Return(nil) - }, - req: &frontierv1beta1.JoinOrganizationRequest{ - OrgId: "org_id", - }, - want: &frontierv1beta1.JoinOrganizationResponse{}, - wantErr: nil, - }, - { - name: "should return error if Org Id is empty", - setup: func(m *mocks.DomainService, a *mocks.AuthnService) { - a.EXPECT().GetPrincipal(mock.AnythingOfType("*context.emptyCtx")).Return(authenticate.Principal{ - ID: "user_id", - }, nil) - m.EXPECT().Join(mock.AnythingOfType("*context.emptyCtx"), "", "user_id").Return(grpcBadBodyError) + name: "should return error when org doesn't exist", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService, us *mocks.AuthnService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) }, - req: &frontierv1beta1.JoinOrganizationRequest{ - OrgId: "", + request: &frontierv1beta1.JoinOrganizationRequest{ + OrgId: testOrgID, }, want: nil, - wantErr: grpcBadBodyError, + wantErr: grpcOrgNotFoundErr, }, { - name: "should return an error if Authn service returns some error", - setup: func(m *mocks.DomainService, a *mocks.AuthnService) { - a.EXPECT().GetPrincipal(mock.AnythingOfType("*context.emptyCtx")).Return(authenticate.Principal{}, errors.New("some_err")) - m.EXPECT().Join(mock.AnythingOfType("*context.emptyCtx"), "org_id", "").Return(grpcInternalServerError) + name: "should return error when org is disabled", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService, us *mocks.AuthnService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) }, - req: &frontierv1beta1.JoinOrganizationRequest{ - OrgId: "org_id", + request: &frontierv1beta1.JoinOrganizationRequest{ + OrgId: testOrgID, }, want: nil, - wantErr: grpcInternalServerError, + wantErr: grpcOrgDisabledErr, }, { - name: "should return error if Org Id does not exist", - setup: func(m *mocks.DomainService, a *mocks.AuthnService) { - a.EXPECT().GetPrincipal(mock.AnythingOfType("*context.emptyCtx")).Return(authenticate.Principal{ - ID: "user_id", - }, nil) - m.EXPECT().Join(mock.AnythingOfType("*context.emptyCtx"), "org_id", "user_id").Return(organization.ErrNotExist) - }, - req: &frontierv1beta1.JoinOrganizationRequest{ - OrgId: "org_id", - }, - want: nil, - wantErr: grpcOrgNotFoundErr, - }, - { - name: "should return error is domain miss match with org id", - setup: func(m *mocks.DomainService, a *mocks.AuthnService) { - a.EXPECT().GetPrincipal(mock.AnythingOfType("*context.emptyCtx")).Return(authenticate.Principal{ - ID: "user_id", - }, nil) - m.EXPECT().Join(mock.AnythingOfType("*context.emptyCtx"), "org_id", "user_id").Return(domain.ErrDomainsMisMatch) - }, - req: &frontierv1beta1.JoinOrganizationRequest{ - OrgId: "org_id", + name: "should return error when unable domain mismatch", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService, us *mocks.AuthnService) { + usr := testUserMap[testUserID] + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + us.EXPECT().GetPrincipal(mock.AnythingOfType("*context.emptyCtx")).Return( + authenticate.Principal{ + ID: testUserID, + User: &usr, + }, nil) + ds.EXPECT().Join(mock.AnythingOfType("*context.emptyCtx"), testOrgID, testUserID).Return(domain.ErrDomainsMisMatch) + }, + request: &frontierv1beta1.JoinOrganizationRequest{ + OrgId: testOrgID, }, want: nil, wantErr: grpcDomainMisMatchErr, }, { - name: "should return an error if domain service return some error", - setup: func(m *mocks.DomainService, a *mocks.AuthnService) { - a.EXPECT().GetPrincipal(mock.AnythingOfType("*context.emptyCtx")).Return(authenticate.Principal{ - ID: "user_id", - }, nil) - m.EXPECT().Join(mock.AnythingOfType("*context.emptyCtx"), "org_id", "user_id").Return(errors.New("some_err")) - }, - req: &frontierv1beta1.JoinOrganizationRequest{ - OrgId: "org_id", - }, - want: nil, - wantErr: grpcInternalServerError, + name: "should join org with valid request", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService, us *mocks.AuthnService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + us.EXPECT().GetPrincipal(mock.AnythingOfType("*context.emptyCtx")).Return( + authenticate.Principal{ + ID: testUserID, + User: &user.User{ + ID: testUserID, + Email: "test@notraystack.org", + }, + }, nil) + ds.EXPECT().Join(mock.AnythingOfType("*context.emptyCtx"), testOrgID, testUserID).Return(nil) + }, + request: &frontierv1beta1.JoinOrganizationRequest{ + OrgId: testOrgID, + }, + want: &frontierv1beta1.JoinOrganizationResponse{}, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockDomainServ := new(mocks.DomainService) - mockAuthSerc := new(mocks.AuthnService) + os := &mocks.OrganizationService{} + ds := &mocks.DomainService{} + us := &mocks.AuthnService{} if tt.setup != nil { - tt.setup(mockDomainServ, mockAuthSerc) + tt.setup(os, ds, us) } - mockDom := Handler{ - domainService: mockDomainServ, - authnService: mockAuthSerc, + h := Handler{ + orgService: os, + domainService: ds, + authnService: us, } - resp, err := mockDom.JoinOrganization(context.Background(), tt.req) - assert.EqualValues(t, tt.want, resp) + got, err := h.JoinOrganization(context.Background(), tt.request) assert.EqualValues(t, tt.wantErr, err) + assert.EqualValues(t, tt.want, got) }) } } -func Test_VerifyOrganizationDomain(t *testing.T) { +func TestHandler_ListOrganizationDomains(t *testing.T) { tests := []struct { name string - setup func(m *mocks.DomainService) - req *frontierv1beta1.VerifyOrganizationDomainRequest - want *frontierv1beta1.VerifyOrganizationDomainResponse + setup func(os *mocks.OrganizationService, ds *mocks.DomainService) + request *frontierv1beta1.ListOrganizationDomainsRequest + want *frontierv1beta1.ListOrganizationDomainsResponse wantErr error }{ { - name: "should Verify the Org Domain on success", - setup: func(m *mocks.DomainService) { - m.EXPECT().VerifyDomain(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(domain.Domain{ - State: domain.Verified, - }, nil) - }, - req: &frontierv1beta1.VerifyOrganizationDomainRequest{ - OrgId: "org_id", - Id: "some_id", - }, - want: &frontierv1beta1.VerifyOrganizationDomainResponse{ - State: domain.Verified.String(), - }, - wantErr: nil, - }, - { - name: "should return error if Org Id or Id is empty", - setup: func(m *mocks.DomainService) { - m.EXPECT().VerifyDomain(mock.AnythingOfType("*context.emptyCtx"), "").Return(domain.Domain{}, grpcBadBodyError) - }, - req: &frontierv1beta1.VerifyOrganizationDomainRequest{ - OrgId: "", - Id: "", - }, - want: nil, - wantErr: grpcBadBodyError, - }, - { - name: "should return error if domain is invalid", - setup: func(m *mocks.DomainService) { - m.EXPECT().VerifyDomain(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(domain.Domain{}, domain.ErrInvalidDomain) + name: "should return error when org doesn't exist", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) }, - req: &frontierv1beta1.VerifyOrganizationDomainRequest{ - OrgId: "org_id", - Id: "some_id", + request: &frontierv1beta1.ListOrganizationDomainsRequest{ + OrgId: testOrgID, }, want: nil, - wantErr: grpcInvalidHostErr, + wantErr: grpcOrgNotFoundErr, }, { - name: "should return error if domain does not exist", - setup: func(m *mocks.DomainService) { - m.EXPECT().VerifyDomain(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(domain.Domain{}, domain.ErrNotExist) + name: "should return error when org is disabled", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) }, - req: &frontierv1beta1.VerifyOrganizationDomainRequest{ - OrgId: "org_id", - Id: "some_id", + request: &frontierv1beta1.ListOrganizationDomainsRequest{ + OrgId: testOrgID, }, want: nil, - wantErr: grpcDomainNotFoundErr, + wantErr: grpcOrgDisabledErr, }, { - name: "should return error if TXT record not found", - setup: func(m *mocks.DomainService) { - m.EXPECT().VerifyDomain(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(domain.Domain{}, domain.ErrTXTrecordNotFound) + name: "should list org domains with valid request", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + ds.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), domain.Filter{ + OrgID: testOrgID, + }).Return([]domain.Domain{testDomainMap[testDomainID1]}, nil) }, - req: &frontierv1beta1.VerifyOrganizationDomainRequest{ - OrgId: "org_id", - Id: "some_id", + request: &frontierv1beta1.ListOrganizationDomainsRequest{ + OrgId: testOrgID, }, - want: nil, - wantErr: grpcTXTRecordNotFound, - }, - { - name: "should return an error if domain service return some error", - setup: func(m *mocks.DomainService) { - m.EXPECT().VerifyDomain(mock.AnythingOfType("*context.emptyCtx"), "some_id").Return(domain.Domain{}, errors.New("some_error")) - }, - req: &frontierv1beta1.VerifyOrganizationDomainRequest{ - OrgId: "org_id", - Id: "some_id", + want: &frontierv1beta1.ListOrganizationDomainsResponse{ + Domains: []*frontierv1beta1.Domain{dm1PB}, }, - want: nil, - wantErr: grpcInternalServerError, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockDomSer := new(mocks.DomainService) + os := &mocks.OrganizationService{} + ds := &mocks.DomainService{} if tt.setup != nil { - tt.setup(mockDomSer) + tt.setup(os, ds) + } + h := Handler{ + orgService: os, + domainService: ds, } - mockDom := Handler{domainService: mockDomSer} - resp, err := mockDom.VerifyOrganizationDomain(context.Background(), tt.req) - assert.EqualValues(t, tt.want, resp) + got, err := h.ListOrganizationDomains(context.Background(), tt.request) assert.EqualValues(t, tt.wantErr, err) + assert.EqualValues(t, tt.want, got) }) } } -func Test_ListOrganizationDomains(t *testing.T) { +func TestHandler_VerifyOrganizationDomain(t *testing.T) { tests := []struct { name string - setup func(m *mocks.DomainService) - req *frontierv1beta1.ListOrganizationDomainsRequest - want *frontierv1beta1.ListOrganizationDomainsResponse + setup func(os *mocks.OrganizationService, ds *mocks.DomainService) + request *frontierv1beta1.VerifyOrganizationDomainRequest + want *frontierv1beta1.VerifyOrganizationDomainResponse wantErr error }{ { - name: "should list domains on success", - setup: func(m *mocks.DomainService) { - m.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), domain.Filter{ - OrgID: "org_id", - State: domain.Verified, - }).Return([]domain.Domain{ - { - ID: "some_id", - Name: "example.com", - OrgID: "org_id", - Token: "some_token", - State: domain.Pending, - }, - }, nil) + name: "should return error when org doesn't exist", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) }, - - req: &frontierv1beta1.ListOrganizationDomainsRequest{ - OrgId: "org_id", - State: domain.Verified.String(), + request: &frontierv1beta1.VerifyOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, }, - want: &frontierv1beta1.ListOrganizationDomainsResponse{ - Domains: []*frontierv1beta1.Domain{ - { - Id: "some_id", - Name: "example.com", - OrgId: "org_id", - Token: "some_token", - State: domain.Pending.String(), - UpdatedAt: timestamppb.New(time.Time{}), - CreatedAt: timestamppb.New(time.Time{}), - }, - }, + want: nil, + wantErr: grpcOrgNotFoundErr, + }, + { + name: "should return error when org is disabled", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) }, - wantErr: nil, + request: &frontierv1beta1.VerifyOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, + }, + want: nil, + wantErr: grpcOrgDisabledErr, }, { - name: "should return error if Org Id or State is empty", - setup: func(m *mocks.DomainService) { - m.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), domain.Filter{ - OrgID: "", - State: "", - }).Return([]domain.Domain{}, grpcBadBodyError) + name: "should return error when domain doesn't exist", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + ds.EXPECT().VerifyDomain(mock.AnythingOfType("*context.emptyCtx"), testDomainID1).Return(domain.Domain{}, domain.ErrNotExist) }, - - req: &frontierv1beta1.ListOrganizationDomainsRequest{ - OrgId: "", - State: "", + request: &frontierv1beta1.VerifyOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, }, want: nil, - wantErr: grpcBadBodyError, + wantErr: grpcDomainNotFoundErr, }, { - name: "should return an error if domain service return some error", - setup: func(m *mocks.DomainService) { - m.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), domain.Filter{ - OrgID: "org_id", - State: domain.Verified, - }).Return([]domain.Domain{}, errors.New("some_error")) + name: "should return error when TXT record not found", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + ds.EXPECT().VerifyDomain(mock.AnythingOfType("*context.emptyCtx"), testDomainID1).Return(testDomainMap[testDomainID1], domain.ErrTXTrecordNotFound) }, - - req: &frontierv1beta1.ListOrganizationDomainsRequest{ - OrgId: "org_id", - State: domain.Verified.String(), + request: &frontierv1beta1.VerifyOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, }, want: nil, - wantErr: grpcInternalServerError, + wantErr: grpcTXTRecordNotFound, + }, + { + name: "should verify org domain with valid request", + setup: func(os *mocks.OrganizationService, ds *mocks.DomainService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + ds.EXPECT().VerifyDomain(mock.AnythingOfType("*context.emptyCtx"), testDomainID1).Return(testDomainMap[testDomainID1], nil) + }, + request: &frontierv1beta1.VerifyOrganizationDomainRequest{ + OrgId: testOrgID, + Id: testDomainID1, + }, + want: &frontierv1beta1.VerifyOrganizationDomainResponse{ + State: domain.Verified.String(), + }, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockDomSer := new(mocks.DomainService) + os := &mocks.OrganizationService{} + ds := &mocks.DomainService{} if tt.setup != nil { - tt.setup(mockDomSer) + tt.setup(os, ds) + } + h := Handler{ + orgService: os, + domainService: ds, } - mockDom := Handler{domainService: mockDomSer} - resp, err := mockDom.ListOrganizationDomains(context.Background(), tt.req) - assert.EqualValues(t, tt.want, resp) + got, err := h.VerifyOrganizationDomain(context.Background(), tt.request) assert.EqualValues(t, tt.wantErr, err) + assert.EqualValues(t, tt.want, got) }) } } diff --git a/internal/api/v1beta1/group.go b/internal/api/v1beta1/group.go index 444e44802..e3a6e3a61 100644 --- a/internal/api/v1beta1/group.go +++ b/internal/api/v1beta1/group.go @@ -4,6 +4,7 @@ import ( "context" "github.com/raystack/frontier/core/audit" + "github.com/raystack/frontier/internal/bootstrap/schema" "github.com/raystack/frontier/pkg/str" @@ -38,6 +39,7 @@ type GroupService interface { var ( grpcGroupNotFoundErr = status.Errorf(codes.NotFound, "group doesn't exist") + grpcMinOwnerCounrErr = status.Errorf(codes.InvalidArgument, "group must have at least one owner, consider adding another owner before removing") ) func (h Handler) ListGroups(ctx context.Context, request *frontierv1beta1.ListGroupsRequest) (*frontierv1beta1.ListGroupsResponse, error) { @@ -68,10 +70,22 @@ func (h Handler) ListGroups(ctx context.Context, request *frontierv1beta1.ListGr func (h Handler) ListOrganizationGroups(ctx context.Context, request *frontierv1beta1.ListOrganizationGroupsRequest) (*frontierv1beta1.ListOrganizationGroupsResponse, error) { logger := grpczap.Extract(ctx) + orgResp, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } var groups []*frontierv1beta1.Group groupList, err := h.groupService.List(ctx, group.Filter{ - OrganizationID: request.GetOrgId(), + OrganizationID: orgResp.ID, State: group.State(request.GetState()), }) if err != nil { @@ -97,6 +111,18 @@ func (h Handler) CreateGroup(ctx context.Context, request *frontierv1beta1.Creat if request.GetBody() == nil { return nil, grpcBadBodyError } + orgResp, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } metaDataMap := metadata.Build(request.GetBody().GetMetadata().AsMap()) @@ -112,7 +138,7 @@ func (h Handler) CreateGroup(ctx context.Context, request *frontierv1beta1.Creat newGroup, err := h.groupService.Create(ctx, group.Group{ Name: request.GetBody().GetName(), Title: request.GetBody().GetTitle(), - OrganizationID: request.GetOrgId(), + OrganizationID: orgResp.ID, Metadata: metaDataMap, }) if err != nil { @@ -148,6 +174,18 @@ func (h Handler) CreateGroup(ctx context.Context, request *frontierv1beta1.Creat func (h Handler) GetGroup(ctx context.Context, request *frontierv1beta1.GetGroupRequest) (*frontierv1beta1.GetGroupResponse, error) { logger := grpczap.Extract(ctx) + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } fetchedGroup, err := h.groupService.Get(ctx, request.GetId()) if err != nil { @@ -175,6 +213,18 @@ func (h Handler) UpdateGroup(ctx context.Context, request *frontierv1beta1.Updat if request.GetBody() == nil { return nil, grpcBadBodyError } + orgResp, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } metaDataMap := metadata.Build(request.GetBody().GetMetadata().AsMap()) @@ -187,7 +237,7 @@ func (h Handler) UpdateGroup(ctx context.Context, request *frontierv1beta1.Updat ID: request.GetId(), Name: request.GetBody().GetName(), Title: request.GetBody().GetTitle(), - OrganizationID: request.GetOrgId(), + OrganizationID: orgResp.ID, Metadata: metaDataMap, }) if err != nil { @@ -213,12 +263,24 @@ func (h Handler) UpdateGroup(ctx context.Context, request *frontierv1beta1.Updat return nil, grpcInternalServerError } - audit.GetAuditor(ctx, request.GetOrgId()).Log(audit.GroupUpdatedEvent, audit.GroupTarget(updatedGroup.ID)) + audit.GetAuditor(ctx, orgResp.ID).Log(audit.GroupUpdatedEvent, audit.GroupTarget(updatedGroup.ID)) return &frontierv1beta1.UpdateGroupResponse{Group: &groupPB}, nil } func (h Handler) ListGroupUsers(ctx context.Context, request *frontierv1beta1.ListGroupUsersRequest) (*frontierv1beta1.ListGroupUsersResponse, error) { logger := grpczap.Extract(ctx) + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } users, err := h.userService.ListByGroup(ctx, request.Id, group.MemberPermission) if err != nil { @@ -243,6 +305,19 @@ func (h Handler) ListGroupUsers(ctx context.Context, request *frontierv1beta1.Li func (h Handler) AddGroupUsers(ctx context.Context, request *frontierv1beta1.AddGroupUsersRequest) (*frontierv1beta1.AddGroupUsersResponse, error) { logger := grpczap.Extract(ctx) + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + if err := h.groupService.AddUsers(ctx, request.GetId(), request.GetUserIds()); err != nil { logger.Error(err.Error()) return nil, grpcInternalServerError @@ -252,6 +327,28 @@ func (h Handler) AddGroupUsers(ctx context.Context, request *frontierv1beta1.Add func (h Handler) RemoveGroupUser(ctx context.Context, request *frontierv1beta1.RemoveGroupUserRequest) (*frontierv1beta1.RemoveGroupUserResponse, error) { logger := grpczap.Extract(ctx) + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + + owners, err := h.userService.ListByGroup(ctx, request.GetId(), schema.DeletePermission) + if err != nil { + logger.Error(err.Error()) + return nil, grpcInternalServerError + } + if len(owners) == 1 && owners[0].ID == request.GetUserId() { + return nil, grpcMinOwnerCounrErr + } + if err := h.groupService.RemoveUsers(ctx, request.GetId(), []string{request.GetUserId()}); err != nil { logger.Error(err.Error()) return nil, grpcInternalServerError @@ -261,6 +358,19 @@ func (h Handler) RemoveGroupUser(ctx context.Context, request *frontierv1beta1.R func (h Handler) EnableGroup(ctx context.Context, request *frontierv1beta1.EnableGroupRequest) (*frontierv1beta1.EnableGroupResponse, error) { logger := grpczap.Extract(ctx) + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + if err := h.groupService.Enable(ctx, request.GetId()); err != nil { logger.Error(err.Error()) switch { @@ -275,6 +385,19 @@ func (h Handler) EnableGroup(ctx context.Context, request *frontierv1beta1.Enabl func (h Handler) DisableGroup(ctx context.Context, request *frontierv1beta1.DisableGroupRequest) (*frontierv1beta1.DisableGroupResponse, error) { logger := grpczap.Extract(ctx) + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + if err := h.groupService.Disable(ctx, request.GetId()); err != nil { logger.Error(err.Error()) switch { @@ -289,6 +412,19 @@ func (h Handler) DisableGroup(ctx context.Context, request *frontierv1beta1.Disa func (h Handler) DeleteGroup(ctx context.Context, request *frontierv1beta1.DeleteGroupRequest) (*frontierv1beta1.DeleteGroupResponse, error) { logger := grpczap.Extract(ctx) + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + if err := h.groupService.Delete(ctx, request.GetId()); err != nil { logger.Error(err.Error()) switch { diff --git a/internal/api/v1beta1/group_test.go b/internal/api/v1beta1/group_test.go index 8aa1d41e8..41300242f 100644 --- a/internal/api/v1beta1/group_test.go +++ b/internal/api/v1beta1/group_test.go @@ -13,6 +13,7 @@ import ( "github.com/raystack/frontier/core/organization" "github.com/raystack/frontier/core/user" "github.com/raystack/frontier/internal/api/v1beta1/mocks" + "github.com/raystack/frontier/internal/bootstrap/schema" "github.com/raystack/frontier/pkg/errors" "github.com/raystack/frontier/pkg/metadata" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" @@ -202,18 +203,17 @@ func TestHandler_ListGroups(t *testing.T) { func TestHandler_CreateGroup(t *testing.T) { email := "user@raystack.org" - someOrgID := utils.NewString() someGroupID := utils.NewString() tests := []struct { name string - setup func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService) context.Context + setup func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) context.Context request *frontierv1beta1.CreateGroupRequest want *frontierv1beta1.CreateGroupResponse wantErr error }{ { name: "should return error if request body is nil", - setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService) context.Context { + setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) context.Context { return ctx }, request: &frontierv1beta1.CreateGroupRequest{ @@ -224,13 +224,13 @@ func TestHandler_CreateGroup(t *testing.T) { }, { name: "should return error if error in metadata validation", - setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService) context.Context { + setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) context.Context { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(errors.New("some-error")) - return ctx }, request: &frontierv1beta1.CreateGroupRequest{ - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Metadata: &structpb.Struct{}, }}, @@ -239,10 +239,11 @@ func TestHandler_CreateGroup(t *testing.T) { }, { name: "should return unauthenticated error if auth email in context is empty and group service return invalid user email", - setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService) context.Context { + setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) context.Context { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), group.Group{ - OrganizationID: someOrgID, + OrganizationID: testOrgID, Title: "Test Group", Name: "Test-Group", Metadata: metadata.Metadata{}, @@ -251,7 +252,7 @@ func TestHandler_CreateGroup(t *testing.T) { return ctx }, request: &frontierv1beta1.CreateGroupRequest{ - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Title: "Test Group", Metadata: &structpb.Struct{}, @@ -261,17 +262,18 @@ func TestHandler_CreateGroup(t *testing.T) { }, { name: "should return internal error if group service return some error", - setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService) context.Context { + setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) context.Context { + os.EXPECT().Get(mock.AnythingOfType("*context.valueCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Create(mock.AnythingOfType("*context.valueCtx"), group.Group{ Name: "some-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }).Return(group.Group{}, errors.New("some error")) return authenticate.SetContextWithEmail(ctx, email) }, request: &frontierv1beta1.CreateGroupRequest{ - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "some-group", Metadata: &structpb.Struct{}, @@ -281,18 +283,19 @@ func TestHandler_CreateGroup(t *testing.T) { }, { name: "should return already exist error if group service return error conflict", - setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService) context.Context { + setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) context.Context { + os.EXPECT().Get(mock.AnythingOfType("*context.valueCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Create(mock.AnythingOfType("*context.valueCtx"), group.Group{ Name: "some-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }).Return(group.Group{}, group.ErrConflict) return authenticate.SetContextWithEmail(ctx, email) }, request: &frontierv1beta1.CreateGroupRequest{ - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "some-group", Metadata: &structpb.Struct{}, @@ -302,37 +305,18 @@ func TestHandler_CreateGroup(t *testing.T) { }, { name: "should return bad request error if name empty", - setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService) context.Context { + setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) context.Context { + os.EXPECT().Get(mock.AnythingOfType("*context.valueCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Create(mock.AnythingOfType("*context.valueCtx"), group.Group{ Name: "some-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }).Return(group.Group{}, group.ErrInvalidDetail) return authenticate.SetContextWithEmail(ctx, email) }, request: &frontierv1beta1.CreateGroupRequest{ - OrgId: someOrgID, - Body: &frontierv1beta1.GroupRequestBody{ - Name: "some-group", - Metadata: &structpb.Struct{}, - }}, - want: nil, - wantErr: grpcBadBodyError, - }, - { - name: "should return bad request error if org id is not uuid", - setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService) context.Context { - ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) - gs.EXPECT().Create(mock.AnythingOfType("*context.valueCtx"), group.Group{ - Name: "some-group", - OrganizationID: "some-org-id", - Metadata: metadata.Metadata{}, - }).Return(group.Group{}, organization.ErrInvalidUUID) - return authenticate.SetContextWithEmail(ctx, email) - }, - request: &frontierv1beta1.CreateGroupRequest{ - OrgId: "some-org-id", + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "some-group", Metadata: &structpb.Struct{}, @@ -342,49 +326,45 @@ func TestHandler_CreateGroup(t *testing.T) { }, { name: "should return bad request error if org id not exist", - setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService) context.Context { + setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) context.Context { + os.EXPECT().Get(mock.AnythingOfType("*context.valueCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Create(mock.AnythingOfType("*context.valueCtx"), group.Group{ Name: "some-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }).Return(group.Group{}, organization.ErrNotExist) return authenticate.SetContextWithEmail(ctx, email) }, request: &frontierv1beta1.CreateGroupRequest{ - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "some-group", Metadata: &structpb.Struct{}, }}, want: nil, - wantErr: grpcBadBodyError, - }, - { - name: "should return bad request error if body is empty", - request: &frontierv1beta1.CreateGroupRequest{Body: nil}, - want: nil, - wantErr: grpcBadBodyError, + wantErr: grpcOrgNotFoundErr, }, { name: "should return success if group service return nil", - setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService) context.Context { + setup: func(ctx context.Context, gs *mocks.GroupService, us *mocks.UserService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) context.Context { + os.EXPECT().Get(mock.AnythingOfType("*context.valueCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Create(mock.AnythingOfType("*context.valueCtx"), group.Group{ Name: "some-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }).Return(group.Group{ ID: someGroupID, Name: "some-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }, nil) return authenticate.SetContextWithEmail(ctx, email) }, request: &frontierv1beta1.CreateGroupRequest{ - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "some-group", Metadata: &structpb.Struct{}, @@ -393,7 +373,7 @@ func TestHandler_CreateGroup(t *testing.T) { Group: &frontierv1beta1.Group{ Id: someGroupID, Name: "some-group", - OrgId: someOrgID, + OrgId: testOrgID, Metadata: &structpb.Struct{ Fields: make(map[string]*structpb.Value), }, @@ -408,15 +388,17 @@ func TestHandler_CreateGroup(t *testing.T) { t.Run(tt.name, func(t *testing.T) { mockGroupSvc := new(mocks.GroupService) mockUserSvc := new(mocks.UserService) + mockOrgSvc := new(mocks.OrganizationService) mockMetaSchemaSvc := new(mocks.MetaSchemaService) ctx := context.Background() if tt.setup != nil { - ctx = tt.setup(ctx, mockGroupSvc, mockUserSvc, mockMetaSchemaSvc) + ctx = tt.setup(ctx, mockGroupSvc, mockUserSvc, mockMetaSchemaSvc, mockOrgSvc) } h := Handler{ userService: mockUserSvc, groupService: mockGroupSvc, metaSchemaService: mockMetaSchemaSvc, + orgService: mockOrgSvc, } got, err := h.CreateGroup(ctx, tt.request) assert.EqualValues(t, got, tt.want) @@ -429,54 +411,78 @@ func TestHandler_GetGroup(t *testing.T) { someGroupID := utils.NewString() tests := []struct { name string - setup func(gs *mocks.GroupService) + setup func(gs *mocks.GroupService, os *mocks.OrganizationService) request *frontierv1beta1.GetGroupRequest want *frontierv1beta1.GetGroupResponse wantErr error }{ + { + name: "should return error if org does not exist", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) + }, + request: &frontierv1beta1.GetGroupRequest{ + OrgId: testOrgID, + Id: someGroupID, + }, + want: nil, + wantErr: grpcOrgNotFoundErr, + }, + { + name: "should return error if org is disabled", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) + }, + request: &frontierv1beta1.GetGroupRequest{ + OrgId: testOrgID, + Id: someGroupID, + }, + want: nil, + wantErr: grpcOrgDisabledErr, + }, { name: "should return internal error if group service return some error", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), someGroupID).Return(group.Group{}, errors.New("some error")) }, - request: &frontierv1beta1.GetGroupRequest{Id: someGroupID}, + request: &frontierv1beta1.GetGroupRequest{Id: someGroupID, OrgId: testOrgID}, want: nil, wantErr: grpcInternalServerError, }, { name: "should return not found error if id is invalid", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "").Return(group.Group{}, group.ErrInvalidID) }, - request: &frontierv1beta1.GetGroupRequest{}, + request: &frontierv1beta1.GetGroupRequest{ + Id: "", + OrgId: testOrgID, + }, want: nil, wantErr: grpcGroupNotFoundErr, }, { name: "should return not found error if group not exist", - setup: func(gs *mocks.GroupService) { - gs.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "").Return(group.Group{}, group.ErrNotExist) + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + gs.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), someGroupID).Return(group.Group{}, group.ErrNotExist) }, - request: &frontierv1beta1.GetGroupRequest{}, - want: nil, - wantErr: grpcGroupNotFoundErr, - }, - { - name: "should return not found error if uuid is invalid", - setup: func(gs *mocks.GroupService) { - gs.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "").Return(group.Group{}, group.ErrInvalidUUID) + request: &frontierv1beta1.GetGroupRequest{ + Id: someGroupID, + OrgId: testOrgID, }, - request: &frontierv1beta1.GetGroupRequest{}, want: nil, wantErr: grpcGroupNotFoundErr, }, - { name: "should return success if group service return nil", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testGroupID).Return(testGroupMap[testGroupID], nil) }, - request: &frontierv1beta1.GetGroupRequest{Id: testGroupID}, + request: &frontierv1beta1.GetGroupRequest{Id: testGroupID, OrgId: testOrgID}, want: &frontierv1beta1.GetGroupResponse{ Group: &frontierv1beta1.Group{ Id: testGroupID, @@ -495,7 +501,8 @@ func TestHandler_GetGroup(t *testing.T) { }, { name: "should return internal error if group service return key as integer typpe", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testGroupID).Return(group.Group{ Metadata: metadata.Metadata{ "key": map[int]any{}, @@ -503,19 +510,21 @@ func TestHandler_GetGroup(t *testing.T) { }, nil) }, - request: &frontierv1beta1.GetGroupRequest{Id: testGroupID}, + request: &frontierv1beta1.GetGroupRequest{Id: testGroupID, OrgId: testOrgID}, want: nil, wantErr: grpcInternalServerError, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + mockOrgSvc := new(mocks.OrganizationService) mockGroupSvc := new(mocks.GroupService) if tt.setup != nil { - tt.setup(mockGroupSvc) + tt.setup(mockGroupSvc, mockOrgSvc) } h := Handler{ groupService: mockGroupSvc, + orgService: mockOrgSvc, } got, err := h.GetGroup(context.Background(), tt.request) assert.EqualValues(t, got, tt.want) @@ -526,30 +535,29 @@ func TestHandler_GetGroup(t *testing.T) { func TestHandler_UpdateGroup(t *testing.T) { someGroupID := utils.NewString() - someOrgID := utils.NewString() tests := []struct { name string - setup func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) + setup func(gs *mocks.GroupService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) request *frontierv1beta1.UpdateGroupRequest want *frontierv1beta1.UpdateGroupResponse wantErr error }{ - { name: "should return internal error if group service return some error", - setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) { + setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Update(mock.AnythingOfType("*context.emptyCtx"), group.Group{ ID: someGroupID, Name: "new-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }).Return(group.Group{}, errors.New("some error")) }, request: &frontierv1beta1.UpdateGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "new-group", }, @@ -566,33 +574,21 @@ func TestHandler_UpdateGroup(t *testing.T) { want: nil, wantErr: grpcBadBodyError, }, - { - name: "should return bad body error if body is empty", - setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) { - ms.EXPECT().Validate(metadata.Metadata{}, groupMetaSchema).Return(errors.New("some_error")) - }, - request: &frontierv1beta1.UpdateGroupRequest{ - Id: someGroupID, - Body: nil, - }, - want: nil, - wantErr: grpcBadBodyError, - }, - { name: "should return not found error if group id is not uuid (slug) and does not exist", - setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) { + setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Update(mock.AnythingOfType("*context.emptyCtx"), group.Group{ ID: "some-id", Name: "some-id", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }).Return(group.Group{}, group.ErrNotExist) }, request: &frontierv1beta1.UpdateGroupRequest{ Id: "some-id", - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "some-id", }, @@ -602,38 +598,20 @@ func TestHandler_UpdateGroup(t *testing.T) { }, { name: "should return not found error if group id is uuid and does not exist", - setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) { + setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Update(mock.AnythingOfType("*context.emptyCtx"), group.Group{ ID: someGroupID, Name: "new-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }).Return(group.Group{}, group.ErrNotExist) }, request: &frontierv1beta1.UpdateGroupRequest{ Id: someGroupID, - OrgId: someOrgID, - Body: &frontierv1beta1.GroupRequestBody{ - Name: "new-group", - }, - }, - want: nil, - wantErr: grpcGroupNotFoundErr, - }, - { - name: "should return not found error if group id is empty", - setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) { - ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) - gs.EXPECT().Update(mock.AnythingOfType("*context.emptyCtx"), group.Group{ - Name: "new-group", - OrganizationID: someOrgID, - Metadata: metadata.Metadata{}, - }).Return(group.Group{}, group.ErrInvalidID) - }, - request: &frontierv1beta1.UpdateGroupRequest{ - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "new-group", }, @@ -643,19 +621,20 @@ func TestHandler_UpdateGroup(t *testing.T) { }, { name: "should return already exist error if group service return error conflict", - setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) { + setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Update(mock.AnythingOfType("*context.emptyCtx"), group.Group{ ID: someGroupID, Name: "new-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }).Return(group.Group{}, group.ErrConflict) }, request: &frontierv1beta1.UpdateGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "new-group", }, @@ -664,64 +643,51 @@ func TestHandler_UpdateGroup(t *testing.T) { wantErr: grpcConflictError, }, { - name: "should return bad request error if org id does not exist", - setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) { - ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) - gs.EXPECT().Update(mock.AnythingOfType("*context.emptyCtx"), group.Group{ - ID: someGroupID, - Name: "new-group", - OrganizationID: someOrgID, - - Metadata: metadata.Metadata{}, - }).Return(group.Group{}, organization.ErrNotExist) + name: "should return error if org does not exist", + setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) }, request: &frontierv1beta1.UpdateGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "new-group", }, }, want: nil, - wantErr: grpcBadBodyError, + wantErr: grpcOrgNotFoundErr, }, { - name: "should return bad request error if org id is not uuid", - setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) { - ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) - gs.EXPECT().Update(mock.AnythingOfType("*context.emptyCtx"), group.Group{ - ID: someGroupID, - Name: "new-group", - OrganizationID: someOrgID, - - Metadata: metadata.Metadata{}, - }).Return(group.Group{}, organization.ErrInvalidUUID) + name: "should return org is disabled", + setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) }, request: &frontierv1beta1.UpdateGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "new-group", }, }, want: nil, - wantErr: grpcBadBodyError, + wantErr: grpcOrgDisabledErr, }, { name: "should return bad request error if name is empty", - setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) { + setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Update(mock.AnythingOfType("*context.emptyCtx"), group.Group{ ID: someGroupID, Name: "new-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }).Return(group.Group{}, group.ErrInvalidDetail) }, request: &frontierv1beta1.UpdateGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "new-group", }, @@ -731,19 +697,21 @@ func TestHandler_UpdateGroup(t *testing.T) { }, { name: "should return bad request error if slug is empty", - setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) { + setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Update(mock.AnythingOfType("*context.emptyCtx"), group.Group{ - ID: someGroupID, - Name: someOrgID, - - Metadata: metadata.Metadata{}, + ID: someGroupID, + Name: testOrgID, + OrganizationID: testOrgID, + Metadata: metadata.Metadata{}, }).Return(group.Group{}, group.ErrInvalidDetail) }, request: &frontierv1beta1.UpdateGroupRequest{ - Id: someGroupID, + Id: someGroupID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ - Name: someOrgID, + Name: testOrgID, }, }, want: nil, @@ -751,25 +719,26 @@ func TestHandler_UpdateGroup(t *testing.T) { }, { name: "should return success if updated by id and group service return nil error", - setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService) { + setup: func(gs *mocks.GroupService, ms *mocks.MetaSchemaService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) ms.EXPECT().Validate(mock.AnythingOfType("metadata.Metadata"), groupMetaSchema).Return(nil) gs.EXPECT().Update(mock.AnythingOfType("*context.emptyCtx"), group.Group{ ID: someGroupID, Name: "new-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }).Return(group.Group{ ID: someGroupID, Name: "new-group", - OrganizationID: someOrgID, + OrganizationID: testOrgID, Metadata: metadata.Metadata{}, }, nil) }, request: &frontierv1beta1.UpdateGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, Body: &frontierv1beta1.GroupRequestBody{ Name: "new-group", }, @@ -778,7 +747,7 @@ func TestHandler_UpdateGroup(t *testing.T) { Group: &frontierv1beta1.Group{ Id: someGroupID, Name: "new-group", - OrgId: someOrgID, + OrgId: testOrgID, Metadata: &structpb.Struct{ Fields: make(map[string]*structpb.Value), }, @@ -792,13 +761,15 @@ func TestHandler_UpdateGroup(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockGroupSvc := new(mocks.GroupService) + mockOrgSvc := new(mocks.OrganizationService) mockMetaSchemaSvc := new(mocks.MetaSchemaService) if tt.setup != nil { - tt.setup(mockGroupSvc, mockMetaSchemaSvc) + tt.setup(mockGroupSvc, mockMetaSchemaSvc, mockOrgSvc) } h := Handler{ groupService: mockGroupSvc, metaSchemaService: mockMetaSchemaSvc, + orgService: mockOrgSvc, } got, err := h.UpdateGroup(context.Background(), tt.request) assert.EqualValues(t, got, tt.want) @@ -809,34 +780,59 @@ func TestHandler_UpdateGroup(t *testing.T) { func TestHandler_DeleteGroup(t *testing.T) { someGroupID := utils.NewString() - someOrgID := utils.NewString() tests := []struct { name string - setup func(gs *mocks.GroupService) + setup func(gs *mocks.GroupService, os *mocks.OrganizationService) request *frontierv1beta1.DeleteGroupRequest want *frontierv1beta1.DeleteGroupResponse wantErr error }{ + { + name: "should return error if org does not exist", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) + }, + request: &frontierv1beta1.DeleteGroupRequest{ + OrgId: testOrgID, + Id: someGroupID, + }, + want: nil, + wantErr: grpcOrgNotFoundErr, + }, + { + name: "should return error if org is disabled", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) + }, + request: &frontierv1beta1.DeleteGroupRequest{ + OrgId: testOrgID, + Id: someGroupID, + }, + want: nil, + wantErr: grpcOrgDisabledErr, + }, { name: "should return not found error if group service return not found error", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().Delete(mock.AnythingOfType("*context.emptyCtx"), someGroupID).Return(group.ErrNotExist) }, request: &frontierv1beta1.DeleteGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, }, want: nil, wantErr: grpcGroupNotFoundErr, }, { name: "should return success if deleted by id and group service return nil error", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().Delete(mock.AnythingOfType("*context.emptyCtx"), someGroupID).Return(nil) }, request: &frontierv1beta1.DeleteGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, }, want: &frontierv1beta1.DeleteGroupResponse{}, wantErr: nil, @@ -844,12 +840,14 @@ func TestHandler_DeleteGroup(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + mockOrgSvc := new(mocks.OrganizationService) mockGroupSvc := new(mocks.GroupService) if tt.setup != nil { - tt.setup(mockGroupSvc) + tt.setup(mockGroupSvc, mockOrgSvc) } h := Handler{ groupService: mockGroupSvc, + orgService: mockOrgSvc, } got, err := h.DeleteGroup(context.Background(), tt.request) assert.EqualValues(t, got, tt.want) @@ -860,34 +858,59 @@ func TestHandler_DeleteGroup(t *testing.T) { func TestHandler_DisableGroup(t *testing.T) { someGroupID := utils.NewString() - someOrgID := utils.NewString() tests := []struct { name string - setup func(gs *mocks.GroupService) + setup func(gs *mocks.GroupService, os *mocks.OrganizationService) request *frontierv1beta1.DisableGroupRequest want *frontierv1beta1.DisableGroupResponse wantErr error }{ + { + name: "should return error if org does not exist", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) + }, + request: &frontierv1beta1.DisableGroupRequest{ + OrgId: testOrgID, + Id: someGroupID, + }, + want: nil, + wantErr: grpcOrgNotFoundErr, + }, + { + name: "should return error if org is disabled", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) + }, + request: &frontierv1beta1.DisableGroupRequest{ + OrgId: testOrgID, + Id: someGroupID, + }, + want: nil, + wantErr: grpcOrgDisabledErr, + }, { name: "should return not found error if group service return not found error", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().Disable(mock.AnythingOfType("*context.emptyCtx"), someGroupID).Return(group.ErrNotExist) }, request: &frontierv1beta1.DisableGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, }, want: nil, wantErr: grpcGroupNotFoundErr, }, { name: "should return success if disabled by id and group service return nil error", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().Disable(mock.AnythingOfType("*context.emptyCtx"), someGroupID).Return(nil) }, request: &frontierv1beta1.DisableGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, }, want: &frontierv1beta1.DisableGroupResponse{}, wantErr: nil, @@ -895,12 +918,14 @@ func TestHandler_DisableGroup(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + mockOrgSvc := new(mocks.OrganizationService) mockGroupSvc := new(mocks.GroupService) if tt.setup != nil { - tt.setup(mockGroupSvc) + tt.setup(mockGroupSvc, mockOrgSvc) } h := Handler{ groupService: mockGroupSvc, + orgService: mockOrgSvc, } got, err := h.DisableGroup(context.Background(), tt.request) assert.EqualValues(t, got, tt.want) @@ -911,34 +936,59 @@ func TestHandler_DisableGroup(t *testing.T) { func TestHandler_EnableGroup(t *testing.T) { someGroupID := utils.NewString() - someOrgID := utils.NewString() tests := []struct { name string - setup func(gs *mocks.GroupService) + setup func(gs *mocks.GroupService, os *mocks.OrganizationService) request *frontierv1beta1.EnableGroupRequest want *frontierv1beta1.EnableGroupResponse wantErr error }{ + { + name: "should return error if org does not exist", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) + }, + request: &frontierv1beta1.EnableGroupRequest{ + OrgId: testOrgID, + Id: someGroupID, + }, + want: nil, + wantErr: grpcOrgNotFoundErr, + }, + { + name: "should return error if org is disabled", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) + }, + request: &frontierv1beta1.EnableGroupRequest{ + OrgId: testOrgID, + Id: someGroupID, + }, + want: nil, + wantErr: grpcOrgDisabledErr, + }, { name: "should return not found error if group service return not found error", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().Enable(mock.AnythingOfType("*context.emptyCtx"), someGroupID).Return(group.ErrNotExist) }, request: &frontierv1beta1.EnableGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, }, want: nil, wantErr: grpcGroupNotFoundErr, }, { name: "should return success if enabled by id and group service return nil error", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().Enable(mock.AnythingOfType("*context.emptyCtx"), someGroupID).Return(nil) }, request: &frontierv1beta1.EnableGroupRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, }, want: &frontierv1beta1.EnableGroupResponse{}, wantErr: nil, @@ -947,11 +997,13 @@ func TestHandler_EnableGroup(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockGroupSvc := new(mocks.GroupService) + mockOrgSvc := new(mocks.OrganizationService) if tt.setup != nil { - tt.setup(mockGroupSvc) + tt.setup(mockGroupSvc, mockOrgSvc) } h := Handler{ groupService: mockGroupSvc, + orgService: mockOrgSvc, } got, err := h.EnableGroup(context.Background(), tt.request) assert.EqualValues(t, got, tt.want) @@ -961,23 +1013,45 @@ func TestHandler_EnableGroup(t *testing.T) { } func TestHandler_ListOrganizationGroups(t *testing.T) { - someOrgID := utils.NewString() tests := []struct { name string - setup func(gs *mocks.GroupService) + setup func(gs *mocks.GroupService, os *mocks.OrganizationService) request *frontierv1beta1.ListOrganizationGroupsRequest want *frontierv1beta1.ListOrganizationGroupsResponse wantErr error }{ + { + name: "should return error if org does not exist", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) + }, + request: &frontierv1beta1.ListOrganizationGroupsRequest{ + OrgId: testOrgID, + }, + want: nil, + wantErr: grpcOrgNotFoundErr, + }, + { + name: "should return error if org is disabled", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) + }, + request: &frontierv1beta1.ListOrganizationGroupsRequest{ + OrgId: testOrgID, + }, + want: nil, + wantErr: grpcOrgDisabledErr, + }, { name: "should return empty groups list if organization with valid uuid is not found", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), group.Filter{ - OrganizationID: someOrgID, + OrganizationID: testOrgID, }).Return([]group.Group{}, nil) }, request: &frontierv1beta1.ListOrganizationGroupsRequest{ - OrgId: someOrgID, + OrgId: testOrgID, }, want: &frontierv1beta1.ListOrganizationGroupsResponse{ Groups: nil, @@ -986,17 +1060,18 @@ func TestHandler_ListOrganizationGroups(t *testing.T) { }, { name: "should return success if list organization groups and group service return nil error", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) var testGroupList []group.Group for _, u := range testGroupMap { testGroupList = append(testGroupList, u) } gs.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), group.Filter{ - OrganizationID: someOrgID, + OrganizationID: testOrgID, }).Return(testGroupList, nil) }, request: &frontierv1beta1.ListOrganizationGroupsRequest{ - OrgId: someOrgID, + OrgId: testOrgID, }, want: &frontierv1beta1.ListOrganizationGroupsResponse{ Groups: []*frontierv1beta1.Group{ @@ -1008,12 +1083,14 @@ func TestHandler_ListOrganizationGroups(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + mockOrgSvc := new(mocks.OrganizationService) mockGroupSvc := new(mocks.GroupService) if tt.setup != nil { - tt.setup(mockGroupSvc) + tt.setup(mockGroupSvc, mockOrgSvc) } h := Handler{ groupService: mockGroupSvc, + orgService: mockOrgSvc, } got, err := h.ListOrganizationGroups(context.Background(), tt.request) assert.EqualValues(t, got, tt.want) @@ -1023,24 +1100,48 @@ func TestHandler_ListOrganizationGroups(t *testing.T) { } func TestHandler_AddGroupUsers(t *testing.T) { - someOrgID := utils.NewString() someGroupID := utils.NewString() someUserID := utils.NewString() tests := []struct { name string - setup func(gs *mocks.GroupService) + setup func(gs *mocks.GroupService, os *mocks.OrganizationService) request *frontierv1beta1.AddGroupUsersRequest want *frontierv1beta1.AddGroupUsersResponse wantErr error }{ + { + name: "should return error if org does not exist", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) + }, + request: &frontierv1beta1.AddGroupUsersRequest{ + Id: someGroupID, + OrgId: testOrgID, + }, + want: nil, + wantErr: grpcOrgNotFoundErr, + }, + { + name: "should return error if org is disabled", + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) + }, + request: &frontierv1beta1.AddGroupUsersRequest{ + Id: someGroupID, + OrgId: testOrgID, + }, + want: nil, + wantErr: grpcOrgDisabledErr, + }, { name: "should return internal server error if error in adding group users", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().AddUsers(mock.AnythingOfType("*context.emptyCtx"), someGroupID, []string{someUserID}).Return(errors.New("some error")) }, request: &frontierv1beta1.AddGroupUsersRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, UserIds: []string{someUserID}, }, want: nil, @@ -1048,12 +1149,13 @@ func TestHandler_AddGroupUsers(t *testing.T) { }, { name: "should return success if add group users and group service return nil error", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) gs.EXPECT().AddUsers(mock.AnythingOfType("*context.emptyCtx"), someGroupID, []string{someUserID}).Return(nil) }, request: &frontierv1beta1.AddGroupUsersRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, UserIds: []string{someUserID}, }, want: &frontierv1beta1.AddGroupUsersResponse{}, @@ -1063,11 +1165,13 @@ func TestHandler_AddGroupUsers(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockGroupSvc := new(mocks.GroupService) + mockOrgSvc := new(mocks.OrganizationService) if tt.setup != nil { - tt.setup(mockGroupSvc) + tt.setup(mockGroupSvc, mockOrgSvc) } h := Handler{ groupService: mockGroupSvc, + orgService: mockOrgSvc, } got, err := h.AddGroupUsers(context.Background(), tt.request) assert.EqualValues(t, got, tt.want) @@ -1077,24 +1181,61 @@ func TestHandler_AddGroupUsers(t *testing.T) { } func TestHandler_RemoveGroupUsers(t *testing.T) { - someOrgID := utils.NewString() someGroupID := utils.NewString() someUserID := utils.NewString() tests := []struct { name string - setup func(gs *mocks.GroupService) + setup func(gs *mocks.GroupService, us *mocks.UserService, os *mocks.OrganizationService) request *frontierv1beta1.RemoveGroupUserRequest want *frontierv1beta1.RemoveGroupUserResponse wantErr error }{ + { + name: "should return error if org does not exist", + setup: func(gs *mocks.GroupService, us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) + }, + request: &frontierv1beta1.RemoveGroupUserRequest{ + Id: someGroupID, + OrgId: testOrgID, + }, + want: nil, + wantErr: grpcOrgNotFoundErr, + }, + { + name: "should return error if org is disabled", + setup: func(gs *mocks.GroupService, us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) + }, + request: &frontierv1beta1.RemoveGroupUserRequest{ + Id: someGroupID, + OrgId: testOrgID, + }, + want: nil, + wantErr: grpcOrgDisabledErr, + }, { name: "should return internal server error if error in removing group users", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + us.EXPECT().ListByGroup(mock.AnythingOfType("*context.emptyCtx"), someGroupID, schema.DeletePermission).Return( + []user.User{ + testUserMap[testUserID], + { + ID: "9f256f86-31a3-11ec-8d3d-0242ac130003", + Title: "User 2", + Name: "user2", + Email: "test@raystack.org", + Metadata: metadata.Metadata{}, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + }, nil) gs.EXPECT().RemoveUsers(mock.AnythingOfType("*context.emptyCtx"), someGroupID, []string{someUserID}).Return(errors.New("some error")) }, request: &frontierv1beta1.RemoveGroupUserRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, UserId: someUserID, }, want: nil, @@ -1102,12 +1243,26 @@ func TestHandler_RemoveGroupUsers(t *testing.T) { }, { name: "should return success if remove group users and group service return nil error", - setup: func(gs *mocks.GroupService) { + setup: func(gs *mocks.GroupService, us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + us.EXPECT().ListByGroup(mock.AnythingOfType("*context.emptyCtx"), someGroupID, schema.DeletePermission).Return( + []user.User{ + testUserMap[testUserID], + { + ID: "9f256f86-31a3-11ec-8d3d-0242ac130003", + Title: "User 2", + Name: "user2", + Email: "test@raystack.org", + Metadata: metadata.Metadata{}, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + }, nil) gs.EXPECT().RemoveUsers(mock.AnythingOfType("*context.emptyCtx"), someGroupID, []string{someUserID}).Return(nil) }, request: &frontierv1beta1.RemoveGroupUserRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, UserId: someUserID, }, want: &frontierv1beta1.RemoveGroupUserResponse{}, @@ -1117,11 +1272,15 @@ func TestHandler_RemoveGroupUsers(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockGroupSvc := new(mocks.GroupService) + mockUserSvc := new(mocks.UserService) + mockOrgSvc := new(mocks.OrganizationService) if tt.setup != nil { - tt.setup(mockGroupSvc) + tt.setup(mockGroupSvc, mockUserSvc, mockOrgSvc) } h := Handler{ groupService: mockGroupSvc, + userService: mockUserSvc, + orgService: mockOrgSvc, } got, err := h.RemoveGroupUser(context.Background(), tt.request) assert.EqualValues(t, got, tt.want) @@ -1131,30 +1290,55 @@ func TestHandler_RemoveGroupUsers(t *testing.T) { } func TestHandler_ListGroupUsers(t *testing.T) { - someOrgID := utils.NewString() someGroupID := utils.NewString() tests := []struct { name string - setup func(gs *mocks.GroupService, us *mocks.UserService) + setup func(gs *mocks.GroupService, us *mocks.UserService, os *mocks.OrganizationService) request *frontierv1beta1.ListGroupUsersRequest want *frontierv1beta1.ListGroupUsersResponse wantErr error }{ + { + name: "should return error if org does not exist", + setup: func(gs *mocks.GroupService, us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) + }, + request: &frontierv1beta1.ListGroupUsersRequest{ + Id: someGroupID, + OrgId: testOrgID, + }, + want: nil, + wantErr: grpcOrgNotFoundErr, + }, + { + name: "should error if org is disabled", + setup: func(gs *mocks.GroupService, us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrDisabled) + }, + request: &frontierv1beta1.ListGroupUsersRequest{ + Id: someGroupID, + OrgId: testOrgID, + }, + want: nil, + wantErr: grpcOrgDisabledErr, + }, { name: "should return internal server error if error in listing group users", - setup: func(gs *mocks.GroupService, us *mocks.UserService) { + setup: func(gs *mocks.GroupService, us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) us.EXPECT().ListByGroup(mock.AnythingOfType("*context.emptyCtx"), someGroupID, group.MemberPermission).Return(nil, errors.New("some error")) }, request: &frontierv1beta1.ListGroupUsersRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, }, want: nil, wantErr: grpcInternalServerError, }, { name: "should return error if metadata has int as key in list of group users", - setup: func(gs *mocks.GroupService, us *mocks.UserService) { + setup: func(gs *mocks.GroupService, us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) testUserList := []user.User{ { Metadata: metadata.Metadata{ @@ -1167,14 +1351,15 @@ func TestHandler_ListGroupUsers(t *testing.T) { }, request: &frontierv1beta1.ListGroupUsersRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, }, want: nil, wantErr: grpcInternalServerError, }, { name: "should return success if list group users and group service return nil error", - setup: func(gs *mocks.GroupService, us *mocks.UserService) { + setup: func(gs *mocks.GroupService, us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) var testUserList []user.User for _, u := range testUserMap { testUserList = append(testUserList, u) @@ -1183,7 +1368,7 @@ func TestHandler_ListGroupUsers(t *testing.T) { }, request: &frontierv1beta1.ListGroupUsersRequest{ Id: someGroupID, - OrgId: someOrgID, + OrgId: testOrgID, }, want: &frontierv1beta1.ListGroupUsersResponse{ Users: []*frontierv1beta1.User{ @@ -1211,12 +1396,14 @@ func TestHandler_ListGroupUsers(t *testing.T) { t.Run(tt.name, func(t *testing.T) { mockGroupSvc := new(mocks.GroupService) mockUserSvc := new(mocks.UserService) + mockOrgSvc := new(mocks.OrganizationService) if tt.setup != nil { - tt.setup(mockGroupSvc, mockUserSvc) + tt.setup(mockGroupSvc, mockUserSvc, mockOrgSvc) } h := Handler{ groupService: mockGroupSvc, userService: mockUserSvc, + orgService: mockOrgSvc, } got, err := h.ListGroupUsers(context.Background(), tt.request) assert.EqualValues(t, got, tt.want) diff --git a/internal/api/v1beta1/invitations.go b/internal/api/v1beta1/invitations.go index 6bbc80a29..703bdc2d6 100644 --- a/internal/api/v1beta1/invitations.go +++ b/internal/api/v1beta1/invitations.go @@ -8,6 +8,7 @@ import ( grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" "github.com/raystack/frontier/core/invitation" + "github.com/raystack/frontier/core/organization" "github.com/raystack/frontier/core/user" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" "google.golang.org/grpc/codes" @@ -28,8 +29,21 @@ type InvitationService interface { func (h Handler) ListOrganizationInvitations(ctx context.Context, request *frontierv1beta1.ListOrganizationInvitationsRequest) (*frontierv1beta1.ListOrganizationInvitationsResponse, error) { logger := grpczap.Extract(ctx) + orgResp, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + invite, err := h.invitationService.List(ctx, invitation.Filter{ - OrgID: request.GetOrgId(), + OrgID: orgResp.ID, UserID: request.GetUserId(), }) if err != nil { @@ -73,6 +87,19 @@ func (h Handler) ListUserInvitations(ctx context.Context, request *frontierv1bet func (h Handler) CreateOrganizationInvitation(ctx context.Context, request *frontierv1beta1.CreateOrganizationInvitationRequest) (*frontierv1beta1.CreateOrganizationInvitationResponse, error) { logger := grpczap.Extract(ctx) + orgResp, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + for _, userID := range request.GetUserIds() { if !isValidEmail(userID) { logger.Error("invalid email") @@ -85,7 +112,7 @@ func (h Handler) CreateOrganizationInvitation(ctx context.Context, request *fron inv, err := h.invitationService.Create(ctx, invitation.Invitation{ UserID: userID, RoleIDs: request.GetRoleIds(), - OrgID: request.GetOrgId(), + OrgID: orgResp.ID, GroupIDs: request.GetGroupIds(), }) if err != nil { @@ -111,6 +138,19 @@ func (h Handler) CreateOrganizationInvitation(ctx context.Context, request *fron func (h Handler) GetOrganizationInvitation(ctx context.Context, request *frontierv1beta1.GetOrganizationInvitationRequest) (*frontierv1beta1.GetOrganizationInvitationResponse, error) { logger := grpczap.Extract(ctx) + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + inviteID, err := uuid.Parse(request.GetId()) if err != nil { logger.Error(err.Error()) @@ -135,6 +175,19 @@ func (h Handler) GetOrganizationInvitation(ctx context.Context, request *frontie func (h Handler) AcceptOrganizationInvitation(ctx context.Context, request *frontierv1beta1.AcceptOrganizationInvitationRequest) (*frontierv1beta1.AcceptOrganizationInvitationResponse, error) { logger := grpczap.Extract(ctx) + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + inviteID, err := uuid.Parse(request.GetId()) if err != nil { logger.Error(err.Error()) @@ -157,6 +210,19 @@ func (h Handler) AcceptOrganizationInvitation(ctx context.Context, request *fron func (h Handler) DeleteOrganizationInvitation(ctx context.Context, request *frontierv1beta1.DeleteOrganizationInvitationRequest) (*frontierv1beta1.DeleteOrganizationInvitationResponse, error) { logger := grpczap.Extract(ctx) + _, err := h.orgService.Get(ctx, request.GetOrgId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + inviteID, err := uuid.Parse(request.GetId()) if err != nil { logger.Error(err.Error()) diff --git a/internal/api/v1beta1/invitations_test.go b/internal/api/v1beta1/invitations_test.go index b0fdc5557..9bd0ac1b0 100644 --- a/internal/api/v1beta1/invitations_test.go +++ b/internal/api/v1beta1/invitations_test.go @@ -58,14 +58,15 @@ var ( func TestHandler_ListOrganizationInvations(t *testing.T) { tests := []struct { name string - setup func(is *mocks.InvitationService) + setup func(is *mocks.InvitationService, os *mocks.OrganizationService) request *frontierv1beta1.ListOrganizationInvitationsRequest want *frontierv1beta1.ListOrganizationInvitationsResponse wantErr error }{ { name: "should return an error if listing invitation returns an error", - setup: func(is *mocks.InvitationService) { + setup: func(is *mocks.InvitationService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) is.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), invitation.Filter{ OrgID: testOrgID, }).Return(nil, errors.New("new-error")) @@ -78,7 +79,8 @@ func TestHandler_ListOrganizationInvations(t *testing.T) { }, { name: "should return the list of invitations belonging to an org on success", - setup: func(is *mocks.InvitationService) { + setup: func(is *mocks.InvitationService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) var testInvitationList []invitation.Invitation for _, u := range testInvitationMap { if u.OrgID == testOrgID { @@ -114,11 +116,13 @@ func TestHandler_ListOrganizationInvations(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockInvitationSvc := new(mocks.InvitationService) + mockOrgSvc := new(mocks.OrganizationService) if tt.setup != nil { - tt.setup(mockInvitationSvc) + tt.setup(mockInvitationSvc, mockOrgSvc) } h := Handler{ invitationService: mockInvitationSvc, + orgService: mockOrgSvc, } got, err := h.ListOrganizationInvitations(context.Background(), tt.request) assert.EqualValues(t, err, tt.wantErr) @@ -200,16 +204,17 @@ func TestHandler_CreateOrganizationInvitation(t *testing.T) { randomGroupID := utils.NewString() tests := []struct { name string - setup func(is *mocks.InvitationService) + setup func(is *mocks.InvitationService, os *mocks.OrganizationService) request *frontierv1beta1.CreateOrganizationInvitationRequest want *frontierv1beta1.CreateOrganizationInvitationResponse wantErr error }{ { name: "should create an invitation on success and return the invitation", - setup: func(is *mocks.InvitationService) { + setup: func(is *mocks.InvitationService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) is.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), invitation.Invitation{ - OrgID: randomOrgID, + OrgID: testOrgID, UserID: testUserEmail, GroupIDs: []string{randomGroupID}, CreatedAt: time.Time{}, @@ -217,7 +222,7 @@ func TestHandler_CreateOrganizationInvitation(t *testing.T) { }).Return(testInvitationMap[testInvitation1ID.String()], nil) }, request: &frontierv1beta1.CreateOrganizationInvitationRequest{ - OrgId: randomOrgID, + OrgId: testOrgID, UserIds: []string{testUserEmail}, GroupIds: []string{randomGroupID}, }, @@ -242,7 +247,8 @@ func TestHandler_CreateOrganizationInvitation(t *testing.T) { }, { name: "should return an error if user email is not provided", - setup: func(is *mocks.InvitationService) { + setup: func(is *mocks.InvitationService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), randomOrgID).Return(testOrgMap[randomOrgID], nil) }, request: &frontierv1beta1.CreateOrganizationInvitationRequest{ OrgId: randomOrgID, @@ -254,28 +260,10 @@ func TestHandler_CreateOrganizationInvitation(t *testing.T) { }, { name: "should return an error if the invitation service fails", - setup: func(is *mocks.InvitationService) { - is.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), invitation.Invitation{ - OrgID: randomOrgID, - UserID: testUserEmail, - GroupIDs: []string{randomGroupID}, - CreatedAt: time.Time{}, - ExpiresAt: time.Time{}, - }).Return(invitation.Invitation{}, errors.New("test error")) - }, - request: &frontierv1beta1.CreateOrganizationInvitationRequest{ - OrgId: randomOrgID, - UserIds: []string{testUserEmail}, - GroupIds: []string{randomGroupID}, - }, - want: nil, - wantErr: status.Error(codes.Internal, "test error"), - }, - { - name: "should return an error if the invitation service fails", - setup: func(is *mocks.InvitationService) { + setup: func(is *mocks.InvitationService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) is.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), invitation.Invitation{ - OrgID: randomOrgID, + OrgID: testOrgID, UserID: testUserEmail, GroupIDs: []string{randomGroupID}, CreatedAt: time.Time{}, @@ -283,7 +271,7 @@ func TestHandler_CreateOrganizationInvitation(t *testing.T) { }).Return(invitation.Invitation{}, errors.New("test error")) }, request: &frontierv1beta1.CreateOrganizationInvitationRequest{ - OrgId: randomOrgID, + OrgId: testOrgID, UserIds: []string{testUserEmail}, GroupIds: []string{randomGroupID}, }, @@ -292,9 +280,10 @@ func TestHandler_CreateOrganizationInvitation(t *testing.T) { }, { name: "should create a new invitation with the default expiration date", - setup: func(is *mocks.InvitationService) { + setup: func(is *mocks.InvitationService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) is.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), invitation.Invitation{ - OrgID: randomOrgID, + OrgID: testOrgID, UserID: testUserEmail, GroupIDs: []string{randomGroupID}, CreatedAt: time.Time{}, @@ -302,7 +291,7 @@ func TestHandler_CreateOrganizationInvitation(t *testing.T) { }).Return(testInvitationMap[testInvitation1ID.String()], nil) }, request: &frontierv1beta1.CreateOrganizationInvitationRequest{ - OrgId: randomOrgID, + OrgId: testOrgID, UserIds: []string{testUserEmail}, GroupIds: []string{randomGroupID}, }, @@ -330,11 +319,13 @@ func TestHandler_CreateOrganizationInvitation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { is := &mocks.InvitationService{} + os := &mocks.OrganizationService{} if tt.setup != nil { - tt.setup(is) + tt.setup(is, os) } h := &Handler{ invitationService: is, + orgService: os, } got, err := h.CreateOrganizationInvitation(context.Background(), tt.request) if tt.wantErr != nil { @@ -350,18 +341,20 @@ func TestHandler_CreateOrganizationInvitation(t *testing.T) { func TestHandler_GetOrganizationInvitation(t *testing.T) { tests := []struct { name string - setup func(is *mocks.InvitationService) + setup func(is *mocks.InvitationService, os *mocks.OrganizationService) request *frontierv1beta1.GetOrganizationInvitationRequest want *frontierv1beta1.GetOrganizationInvitationResponse wantErr error }{ { name: "should return an invitation", - setup: func(is *mocks.InvitationService) { + setup: func(is *mocks.InvitationService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) is.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testInvitation1ID).Return(testInvitationMap[testInvitation1ID.String()], nil) }, request: &frontierv1beta1.GetOrganizationInvitationRequest{ - Id: testInvitation1ID.String(), + Id: testInvitation1ID.String(), + OrgId: testOrgID, }, want: &frontierv1beta1.GetOrganizationInvitationResponse{ Invitation: &frontierv1beta1.Invitation{ @@ -382,22 +375,26 @@ func TestHandler_GetOrganizationInvitation(t *testing.T) { }, { name: "should return an error if the invitation service fails", - setup: func(is *mocks.InvitationService) { + setup: func(is *mocks.InvitationService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) is.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testInvitation1ID).Return(invitation.Invitation{}, errors.New("test error")) }, request: &frontierv1beta1.GetOrganizationInvitationRequest{ - Id: testInvitation1ID.String(), + Id: testInvitation1ID.String(), + OrgId: testOrgID, }, want: nil, wantErr: status.Error(codes.Internal, "test error"), }, { name: "should return an error if the invitation is not found", - setup: func(is *mocks.InvitationService) { + setup: func(is *mocks.InvitationService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) is.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testInvitation1ID).Return(invitation.Invitation{}, invitation.ErrNotFound) }, request: &frontierv1beta1.GetOrganizationInvitationRequest{ - Id: testInvitation1ID.String(), + Id: testInvitation1ID.String(), + OrgId: testOrgID, }, want: nil, wantErr: status.Error(codes.Internal, "invitation not found"), @@ -407,11 +404,13 @@ func TestHandler_GetOrganizationInvitation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { is := &mocks.InvitationService{} + os := &mocks.OrganizationService{} if tt.setup != nil { - tt.setup(is) + tt.setup(is, os) } h := &Handler{ invitationService: is, + orgService: os, } got, err := h.GetOrganizationInvitation(context.Background(), tt.request) if tt.wantErr != nil { @@ -425,59 +424,61 @@ func TestHandler_GetOrganizationInvitation(t *testing.T) { } func TestHandler_AcceptOrganizationInvitation(t *testing.T) { - randomOrgID := uuid.New().String() - tests := []struct { name string - setup func(is *mocks.InvitationService, us *mocks.UserService, gs *mocks.GroupService) + setup func(is *mocks.InvitationService, us *mocks.UserService, gs *mocks.GroupService, os *mocks.OrganizationService) request *frontierv1beta1.AcceptOrganizationInvitationRequest want *frontierv1beta1.AcceptOrganizationInvitationResponse wantErr error }{ { name: "should return an error if invite not found", - setup: func(is *mocks.InvitationService, us *mocks.UserService, gs *mocks.GroupService) { + setup: func(is *mocks.InvitationService, us *mocks.UserService, gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) is.EXPECT().Accept(mock.AnythingOfType("*context.emptyCtx"), testInvitation1ID).Return(invitation.ErrNotFound) }, request: &frontierv1beta1.AcceptOrganizationInvitationRequest{ Id: testInvitation1ID.String(), - OrgId: randomOrgID, + OrgId: testOrgID, }, want: nil, wantErr: grpcInvitationNotFoundError, }, { name: "should return an error if unable to get user by id", - setup: func(is *mocks.InvitationService, us *mocks.UserService, gs *mocks.GroupService) { + setup: func(is *mocks.InvitationService, us *mocks.UserService, gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) is.EXPECT().Accept(mock.AnythingOfType("*context.emptyCtx"), testInvitation1ID).Return(user.ErrNotExist) }, request: &frontierv1beta1.AcceptOrganizationInvitationRequest{ Id: testInvitation1ID.String(), - OrgId: randomOrgID, + OrgId: testOrgID, }, want: nil, wantErr: grpcUserNotFoundError, }, { name: "should return an internal error if unable to accept invitation", - setup: func(is *mocks.InvitationService, us *mocks.UserService, gs *mocks.GroupService) { + setup: func(is *mocks.InvitationService, us *mocks.UserService, gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) is.EXPECT().Accept(mock.AnythingOfType("*context.emptyCtx"), testInvitation1ID).Return(errors.New("test error")) }, request: &frontierv1beta1.AcceptOrganizationInvitationRequest{ Id: testInvitation1ID.String(), - OrgId: randomOrgID, + OrgId: testOrgID, }, want: nil, wantErr: grpcInternalServerError, }, { name: "should accept an invitation on success", - setup: func(is *mocks.InvitationService, us *mocks.UserService, gs *mocks.GroupService) { + setup: func(is *mocks.InvitationService, us *mocks.UserService, gs *mocks.GroupService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) is.EXPECT().Accept(mock.AnythingOfType("*context.emptyCtx"), testInvitation1ID).Return(nil) }, request: &frontierv1beta1.AcceptOrganizationInvitationRequest{ Id: testInvitation1ID.String(), - OrgId: randomOrgID, + OrgId: testOrgID, }, want: &frontierv1beta1.AcceptOrganizationInvitationResponse{}, wantErr: nil, @@ -489,13 +490,16 @@ func TestHandler_AcceptOrganizationInvitation(t *testing.T) { is := &mocks.InvitationService{} us := &mocks.UserService{} gs := &mocks.GroupService{} + os := &mocks.OrganizationService{} + if tt.setup != nil { - tt.setup(is, us, gs) + tt.setup(is, us, gs, os) } h := &Handler{ invitationService: is, userService: us, groupService: gs, + orgService: os, } got, err := h.AcceptOrganizationInvitation(context.Background(), tt.request) if tt.wantErr != nil { @@ -512,14 +516,15 @@ func TestHandler_DeleteOrganizationInvitation(t *testing.T) { randomOrgID := uuid.New().String() tests := []struct { name string - setup func(is *mocks.InvitationService) + setup func(is *mocks.InvitationService, os *mocks.OrganizationService) request *frontierv1beta1.DeleteOrganizationInvitationRequest want *frontierv1beta1.DeleteOrganizationInvitationResponse wantErr error }{ { name: "should return an internal server error if invitation service fails to delete the invite", - setup: func(is *mocks.InvitationService) { + setup: func(is *mocks.InvitationService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), randomOrgID).Return(testOrgMap[randomOrgID], nil) is.EXPECT().Delete(mock.AnythingOfType("*context.emptyCtx"), testInvitation1ID).Return(errors.New("test error")) }, request: &frontierv1beta1.DeleteOrganizationInvitationRequest{ @@ -531,7 +536,8 @@ func TestHandler_DeleteOrganizationInvitation(t *testing.T) { }, { name: "should delete an invitation on success", - setup: func(is *mocks.InvitationService) { + setup: func(is *mocks.InvitationService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), randomOrgID).Return(testOrgMap[randomOrgID], nil) is.EXPECT().Delete(mock.AnythingOfType("*context.emptyCtx"), testInvitation1ID).Return(nil) }, request: &frontierv1beta1.DeleteOrganizationInvitationRequest{ @@ -546,11 +552,13 @@ func TestHandler_DeleteOrganizationInvitation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { is := &mocks.InvitationService{} + os := &mocks.OrganizationService{} if tt.setup != nil { - tt.setup(is) + tt.setup(is, os) } h := &Handler{ invitationService: is, + orgService: os, } got, err := h.DeleteOrganizationInvitation(context.Background(), tt.request) if tt.wantErr != nil { diff --git a/internal/api/v1beta1/mocks/organization_service.go b/internal/api/v1beta1/mocks/organization_service.go index fe49acdbb..49b1220de 100644 --- a/internal/api/v1beta1/mocks/organization_service.go +++ b/internal/api/v1beta1/mocks/organization_service.go @@ -258,6 +258,59 @@ func (_c *OrganizationService_Get_Call) RunAndReturn(run func(context.Context, s return _c } +// GetRaw provides a mock function with given fields: ctx, idOrSlug +func (_m *OrganizationService) GetRaw(ctx context.Context, idOrSlug string) (organization.Organization, error) { + ret := _m.Called(ctx, idOrSlug) + + var r0 organization.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (organization.Organization, error)); ok { + return rf(ctx, idOrSlug) + } + if rf, ok := ret.Get(0).(func(context.Context, string) organization.Organization); ok { + r0 = rf(ctx, idOrSlug) + } else { + r0 = ret.Get(0).(organization.Organization) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, idOrSlug) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrganizationService_GetRaw_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRaw' +type OrganizationService_GetRaw_Call struct { + *mock.Call +} + +// GetRaw is a helper method to define mock.On call +// - ctx context.Context +// - idOrSlug string +func (_e *OrganizationService_Expecter) GetRaw(ctx interface{}, idOrSlug interface{}) *OrganizationService_GetRaw_Call { + return &OrganizationService_GetRaw_Call{Call: _e.mock.On("GetRaw", ctx, idOrSlug)} +} + +func (_c *OrganizationService_GetRaw_Call) Run(run func(ctx context.Context, idOrSlug string)) *OrganizationService_GetRaw_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *OrganizationService_GetRaw_Call) Return(_a0 organization.Organization, _a1 error) *OrganizationService_GetRaw_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrganizationService_GetRaw_Call) RunAndReturn(run func(context.Context, string) (organization.Organization, error)) *OrganizationService_GetRaw_Call { + _c.Call.Return(run) + return _c +} + // List provides a mock function with given fields: ctx, f func (_m *OrganizationService) List(ctx context.Context, f organization.Filter) ([]organization.Organization, error) { ret := _m.Called(ctx, f) diff --git a/internal/api/v1beta1/org.go b/internal/api/v1beta1/org.go index 4401d500e..84ab69008 100644 --- a/internal/api/v1beta1/org.go +++ b/internal/api/v1beta1/org.go @@ -25,10 +25,15 @@ import ( frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" ) -var grpcOrgNotFoundErr = status.Errorf(codes.NotFound, "org doesn't exist") +var ( + grpcOrgNotFoundErr = status.Errorf(codes.NotFound, "org doesn't exist") + grpcOrgDisabledErr = status.Errorf(codes.NotFound, "org is disabled. Please contact your administrator to enable it") + grpcMinAdminCountErr = status.Errorf(codes.PermissionDenied, "org must have at least one admin, consider adding another admin before removing") +) type OrganizationService interface { Get(ctx context.Context, idOrSlug string) (organization.Organization, error) + GetRaw(ctx context.Context, idOrSlug string) (organization.Organization, error) Create(ctx context.Context, org organization.Organization) (organization.Organization, error) List(ctx context.Context, f organization.Filter) ([]organization.Organization, error) Update(ctx context.Context, toUpdate organization.Organization) (organization.Organization, error) @@ -136,7 +141,7 @@ func (h Handler) CreateOrganization(ctx context.Context, request *frontierv1beta func (h Handler) GetOrganization(ctx context.Context, request *frontierv1beta1.GetOrganizationRequest) (*frontierv1beta1.GetOrganizationResponse, error) { logger := grpczap.Extract(ctx) - fetchedOrg, err := h.orgService.Get(ctx, request.GetId()) + fetchedOrg, err := h.orgService.GetRaw(ctx, request.GetId()) if err != nil { logger.Error(err.Error()) switch { @@ -216,11 +221,12 @@ func (h Handler) UpdateOrganization(ctx context.Context, request *frontierv1beta func (h Handler) ListOrganizationAdmins(ctx context.Context, request *frontierv1beta1.ListOrganizationAdminsRequest) (*frontierv1beta1.ListOrganizationAdminsResponse, error) { logger := grpczap.Extract(ctx) - - admins, err := h.userService.ListByOrg(ctx, request.GetId(), organization.AdminPermission) + orgResp, err := h.orgService.Get(ctx, request.GetId()) if err != nil { logger.Error(err.Error()) switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr case errors.Is(err, organization.ErrNotExist): return nil, grpcOrgNotFoundErr default: @@ -228,6 +234,12 @@ func (h Handler) ListOrganizationAdmins(ctx context.Context, request *frontierv1 } } + admins, err := h.userService.ListByOrg(ctx, orgResp.ID, organization.AdminPermission) + if err != nil { + logger.Error(err.Error()) + return nil, grpcInternalServerError + } + var adminsPB []*frontierv1beta1.User for _, user := range admins { u, err := transformUserToPB(user) @@ -244,16 +256,12 @@ func (h Handler) ListOrganizationAdmins(ctx context.Context, request *frontierv1 func (h Handler) ListOrganizationUsers(ctx context.Context, request *frontierv1beta1.ListOrganizationUsersRequest) (*frontierv1beta1.ListOrganizationUsersResponse, error) { logger := grpczap.Extract(ctx) - - permissionFilter := schema.MembershipPermission - if len(request.GetPermissionFilter()) > 0 { - permissionFilter = request.GetPermissionFilter() - } - - users, err := h.userService.ListByOrg(ctx, request.GetId(), permissionFilter) + orgResp, err := h.orgService.Get(ctx, request.GetId()) if err != nil { logger.Error(err.Error()) switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr case errors.Is(err, organization.ErrNotExist): return nil, grpcOrgNotFoundErr default: @@ -261,6 +269,17 @@ func (h Handler) ListOrganizationUsers(ctx context.Context, request *frontierv1b } } + permissionFilter := schema.MembershipPermission + if len(request.GetPermissionFilter()) > 0 { + permissionFilter = request.GetPermissionFilter() + } + + users, err := h.userService.ListByOrg(ctx, orgResp.ID, permissionFilter) + if err != nil { + logger.Error(err.Error()) + return nil, grpcInternalServerError + } + var usersPB []*frontierv1beta1.User for _, rel := range users { u, err := transformUserToPB(rel) @@ -277,11 +296,12 @@ func (h Handler) ListOrganizationUsers(ctx context.Context, request *frontierv1b func (h Handler) ListOrganizationServiceUsers(ctx context.Context, request *frontierv1beta1.ListOrganizationServiceUsersRequest) (*frontierv1beta1.ListOrganizationServiceUsersResponse, error) { logger := grpczap.Extract(ctx) - - users, err := h.serviceUserService.ListByOrg(ctx, request.GetId()) + orgResp, err := h.orgService.Get(ctx, request.GetId()) if err != nil { logger.Error(err.Error()) switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr case errors.Is(err, organization.ErrNotExist): return nil, grpcOrgNotFoundErr default: @@ -289,6 +309,12 @@ func (h Handler) ListOrganizationServiceUsers(ctx context.Context, request *fron } } + users, err := h.serviceUserService.ListByOrg(ctx, orgResp.ID) + if err != nil { + logger.Error(err.Error()) + return nil, grpcInternalServerError + } + var usersPB []*frontierv1beta1.ServiceUser for _, rel := range users { u, err := transformServiceUserToPB(rel) @@ -304,13 +330,12 @@ func (h Handler) ListOrganizationServiceUsers(ctx context.Context, request *fron func (h Handler) ListOrganizationProjects(ctx context.Context, request *frontierv1beta1.ListOrganizationProjectsRequest) (*frontierv1beta1.ListOrganizationProjectsResponse, error) { logger := grpczap.Extract(ctx) - - projects, err := h.projectService.List(ctx, project.Filter{ - OrgID: request.GetId(), - }) + orgResp, err := h.orgService.Get(ctx, request.GetId()) if err != nil { logger.Error(err.Error()) switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr case errors.Is(err, organization.ErrNotExist): return nil, grpcOrgNotFoundErr default: @@ -318,6 +343,14 @@ func (h Handler) ListOrganizationProjects(ctx context.Context, request *frontier } } + projects, err := h.projectService.List(ctx, project.Filter{ + OrgID: orgResp.ID, + }) + if err != nil { + logger.Error(err.Error()) + return nil, grpcInternalServerError + } + var projectPB []*frontierv1beta1.Project for _, rel := range projects { u, err := transformProjectToPB(rel) @@ -334,11 +367,24 @@ func (h Handler) ListOrganizationProjects(ctx context.Context, request *frontier func (h Handler) AddOrganizationUsers(ctx context.Context, request *frontierv1beta1.AddOrganizationUsersRequest) (*frontierv1beta1.AddOrganizationUsersResponse, error) { logger := grpczap.Extract(ctx) + orgResp, err := h.orgService.Get(ctx, request.GetId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + for _, userID := range request.GetUserIds() { - audit.GetAuditor(ctx, request.GetId()).Log(audit.OrgMemberCreatedEvent, audit.UserTarget(userID)) + audit.GetAuditor(ctx, orgResp.ID).Log(audit.OrgMemberCreatedEvent, audit.UserTarget(userID)) } - if err := h.orgService.AddUsers(ctx, request.GetId(), request.GetUserIds()); err != nil { + if err := h.orgService.AddUsers(ctx, orgResp.ID, request.GetUserIds()); err != nil { logger.Error(err.Error()) return nil, grpcInternalServerError } @@ -347,11 +393,33 @@ func (h Handler) AddOrganizationUsers(ctx context.Context, request *frontierv1be func (h Handler) RemoveOrganizationUser(ctx context.Context, request *frontierv1beta1.RemoveOrganizationUserRequest) (*frontierv1beta1.RemoveOrganizationUserResponse, error) { logger := grpczap.Extract(ctx) - if err := h.orgService.RemoveUsers(ctx, request.GetId(), []string{request.GetUserId()}); err != nil { + orgResp, err := h.orgService.Get(ctx, request.GetId()) + if err != nil { + logger.Error(err.Error()) + switch { + case errors.Is(err, organization.ErrDisabled): + return nil, grpcOrgDisabledErr + case errors.Is(err, organization.ErrNotExist): + return nil, grpcOrgNotFoundErr + default: + return nil, grpcInternalServerError + } + } + + admins, err := h.userService.ListByOrg(ctx, orgResp.ID, organization.AdminPermission) + if err != nil { + logger.Error(err.Error()) + return nil, grpcInternalServerError + } + if len(admins) == 1 && admins[0].ID == request.GetUserId() { + return nil, grpcMinAdminCountErr + } + + if err := h.orgService.RemoveUsers(ctx, orgResp.ID, []string{request.GetUserId()}); err != nil { logger.Error(err.Error()) return nil, grpcInternalServerError } - audit.GetAuditor(ctx, request.GetId()).Log(audit.OrgMemberDeletedEvent, audit.UserTarget(request.GetUserId())) + audit.GetAuditor(ctx, orgResp.ID).Log(audit.OrgMemberDeletedEvent, audit.UserTarget(request.GetUserId())) return &frontierv1beta1.RemoveOrganizationUserResponse{}, nil } diff --git a/internal/api/v1beta1/org_test.go b/internal/api/v1beta1/org_test.go index ddff3fb7f..7e186c972 100644 --- a/internal/api/v1beta1/org_test.go +++ b/internal/api/v1beta1/org_test.go @@ -32,8 +32,9 @@ var ( testOrgID = "9f256f86-31a3-11ec-8d3d-0242ac130003" testOrgMap = map[string]organization.Organization{ "9f256f86-31a3-11ec-8d3d-0242ac130003": { - ID: "9f256f86-31a3-11ec-8d3d-0242ac130003", - Name: "org-1", + ID: "9f256f86-31a3-11ec-8d3d-0242ac130003", + Name: "org-1", + State: organization.Enabled, Metadata: metadata.Metadata{ "email": "org1@org1.com", "age": 21, @@ -80,6 +81,7 @@ func TestHandler_ListOrganization(t *testing.T) { "intern": structpb.NewBoolValue(true), }, }, + State: "enabled", CreatedAt: timestamppb.New(time.Time{}), UpdatedAt: timestamppb.New(time.Time{}), }, @@ -206,6 +208,7 @@ func TestHandler_CreateOrganization(t *testing.T) { Metadata: metadata.Metadata{ "email": "a", }, + State: organization.Enabled, }, nil) return authenticate.SetContextWithEmail(ctx, email) }, @@ -226,6 +229,8 @@ func TestHandler_CreateOrganization(t *testing.T) { }}, CreatedAt: timestamppb.New(time.Time{}), UpdatedAt: timestamppb.New(time.Time{}), + State: "enabled", + Avatar: "", }}, err: nil, }, @@ -260,7 +265,7 @@ func TestHandler_GetOrganization(t *testing.T) { { name: "should return internal error if org service return some error", setup: func(os *mocks.OrganizationService) { - os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), someOrgID).Return(organization.Organization{}, errors.New("some error")) + os.EXPECT().GetRaw(mock.AnythingOfType("*context.emptyCtx"), someOrgID).Return(organization.Organization{}, errors.New("some error")) }, request: &frontierv1beta1.GetOrganizationRequest{ Id: someOrgID, @@ -271,7 +276,7 @@ func TestHandler_GetOrganization(t *testing.T) { { name: "should return not found error if org id is not uuid (slug) and org not exist", setup: func(os *mocks.OrganizationService) { - os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), someOrgID).Return(organization.Organization{}, organization.ErrNotExist) + os.EXPECT().GetRaw(mock.AnythingOfType("*context.emptyCtx"), someOrgID).Return(organization.Organization{}, organization.ErrNotExist) }, request: &frontierv1beta1.GetOrganizationRequest{ Id: someOrgID, @@ -282,7 +287,7 @@ func TestHandler_GetOrganization(t *testing.T) { { name: "should return not found error if org id is invalid", setup: func(os *mocks.OrganizationService) { - os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "").Return(organization.Organization{}, organization.ErrInvalidID) + os.EXPECT().GetRaw(mock.AnythingOfType("*context.emptyCtx"), "").Return(organization.Organization{}, organization.ErrInvalidID) }, request: &frontierv1beta1.GetOrganizationRequest{}, want: nil, @@ -291,7 +296,7 @@ func TestHandler_GetOrganization(t *testing.T) { { name: "should return success if org service return nil error", setup: func(os *mocks.OrganizationService) { - os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "9f256f86-31a3-11ec-8d3d-0242ac130003").Return(testOrgMap["9f256f86-31a3-11ec-8d3d-0242ac130003"], nil) + os.EXPECT().GetRaw(mock.AnythingOfType("*context.emptyCtx"), "9f256f86-31a3-11ec-8d3d-0242ac130003").Return(testOrgMap["9f256f86-31a3-11ec-8d3d-0242ac130003"], nil) }, request: &frontierv1beta1.GetOrganizationRequest{ Id: "9f256f86-31a3-11ec-8d3d-0242ac130003", @@ -307,6 +312,7 @@ func TestHandler_GetOrganization(t *testing.T) { "intern": structpb.NewBoolValue(true), }, }, + State: "enabled", CreatedAt: timestamppb.New(time.Time{}), UpdatedAt: timestamppb.New(time.Time{}), }, @@ -475,7 +481,8 @@ func TestHandler_UpdateOrganization(t *testing.T) { "age": float64(21), "valid": true, }, - Name: "new-org", + Name: "new-org", + State: organization.Enabled, }, nil) }, request: &frontierv1beta1.UpdateOrganizationRequest{ @@ -502,6 +509,8 @@ func TestHandler_UpdateOrganization(t *testing.T) { "valid": structpb.NewBoolValue(true), }, }, + State: "enabled", + Avatar: "", CreatedAt: timestamppb.New(time.Time{}), UpdatedAt: timestamppb.New(time.Time{}), }, @@ -527,6 +536,7 @@ func TestHandler_UpdateOrganization(t *testing.T) { "age": float64(21), "valid": true, }, + State: organization.Enabled, }, nil) }, request: &frontierv1beta1.UpdateOrganizationRequest{ @@ -552,6 +562,7 @@ func TestHandler_UpdateOrganization(t *testing.T) { "valid": structpb.NewBoolValue(true), }, }, + State: "enabled", CreatedAt: timestamppb.New(time.Time{}), UpdatedAt: timestamppb.New(time.Time{}), }, @@ -576,47 +587,48 @@ func TestHandler_UpdateOrganization(t *testing.T) { } func TestHandler_ListOrganizationAdmins(t *testing.T) { - someOrgID := utils.NewString() tests := []struct { name string - setup func(us *mocks.UserService) + setup func(us *mocks.UserService, os *mocks.OrganizationService) request *frontierv1beta1.ListOrganizationAdminsRequest want *frontierv1beta1.ListOrganizationAdminsResponse wantErr error }{ { name: "should return internal error if org service return some error", - setup: func(us *mocks.UserService) { - us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), someOrgID, schema.UpdatePermission).Return([]user.User{}, errors.New("some error")) + setup: func(us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), testOrgID, schema.UpdatePermission).Return([]user.User{}, errors.New("some error")) }, request: &frontierv1beta1.ListOrganizationAdminsRequest{ - Id: someOrgID, + Id: testOrgID, }, want: nil, wantErr: grpcInternalServerError, }, { - name: "should return error if org id does not exist", - setup: func(us *mocks.UserService) { - us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), someOrgID, schema.UpdatePermission).Return([]user.User{}, organization.ErrNotExist) + name: "should return error if org id is not exist", + setup: func(us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) }, request: &frontierv1beta1.ListOrganizationAdminsRequest{ - Id: someOrgID, + Id: testOrgID, }, want: nil, wantErr: grpcOrgNotFoundErr, }, { name: "should return success if org service return nil error", - setup: func(us *mocks.UserService) { + setup: func(us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) var testUserList []user.User for _, u := range testUserMap { testUserList = append(testUserList, u) } - us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), someOrgID, schema.UpdatePermission).Return(testUserList, nil) + us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), testOrgID, schema.UpdatePermission).Return(testUserList, nil) }, request: &frontierv1beta1.ListOrganizationAdminsRequest{ - Id: someOrgID, + Id: testOrgID, }, want: &frontierv1beta1.ListOrganizationAdminsResponse{ Users: []*frontierv1beta1.User{ @@ -643,11 +655,12 @@ func TestHandler_ListOrganizationAdmins(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockUserService := new(mocks.UserService) + mockOrgService := new(mocks.OrganizationService) ctx := context.Background() if tt.setup != nil { - tt.setup(mockUserService) + tt.setup(mockUserService, mockOrgService) } - mockDep := Handler{userService: mockUserService} + mockDep := Handler{userService: mockUserService, orgService: mockOrgService} got, err := mockDep.ListOrganizationAdmins(ctx, tt.request) assert.EqualValues(t, tt.want, got) assert.EqualValues(t, tt.wantErr, err) @@ -658,26 +671,15 @@ func TestHandler_ListOrganizationAdmins(t *testing.T) { func TestHandler_ListOrganizationUsers(t *testing.T) { tests := []struct { name string - setup func(us *mocks.UserService) + setup func(us *mocks.UserService, os *mocks.OrganizationService) request *frontierv1beta1.ListOrganizationUsersRequest want *frontierv1beta1.ListOrganizationUsersResponse wantErr error }{ - { - name: "should return Org Not Found Error if Org does not exist ", - setup: func(us *mocks.UserService) { - us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), "some-org-id", "membership").Return([]user.User{}, organization.ErrNotExist) - }, - request: &frontierv1beta1.ListOrganizationUsersRequest{ - Id: "some-org-id", - }, - want: nil, - wantErr: grpcOrgNotFoundErr, - }, { name: "should return internal error if org service return some error", - setup: func(us *mocks.UserService) { - us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), "some-org-id", "membership").Return([]user.User{}, errors.New("some error")) + setup: func(us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "some-org-id").Return(organization.Organization{}, errors.New("some error")) }, request: &frontierv1beta1.ListOrganizationUsersRequest{ Id: "some-org-id", @@ -686,27 +688,28 @@ func TestHandler_ListOrganizationUsers(t *testing.T) { wantErr: grpcInternalServerError, }, { - name: "should return empty list of users if org id is not exist", - setup: func(us *mocks.UserService) { - us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), "some-org-id", "membership").Return([]user.User{}, nil) + name: "should return org not found error if org id is not exist", + setup: func(us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "some-org-id").Return(organization.Organization{}, organization.ErrNotExist) }, request: &frontierv1beta1.ListOrganizationUsersRequest{ Id: "some-org-id", }, - want: &frontierv1beta1.ListOrganizationUsersResponse{}, - wantErr: nil, + want: nil, + wantErr: grpcOrgNotFoundErr, }, { name: "should return success if org service return nil error", - setup: func(us *mocks.UserService) { + setup: func(us *mocks.UserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) var testUserList []user.User for _, u := range testUserMap { testUserList = append(testUserList, u) } - us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), "some-org-id", "membership").Return(testUserList, nil) + us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), testOrgID, "membership").Return(testUserList, nil) }, request: &frontierv1beta1.ListOrganizationUsersRequest{ - Id: "some-org-id", + Id: testOrgID, }, want: &frontierv1beta1.ListOrganizationUsersResponse{ Users: []*frontierv1beta1.User{ @@ -734,11 +737,12 @@ func TestHandler_ListOrganizationUsers(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockUserService := new(mocks.UserService) + mockOrgService := new(mocks.OrganizationService) ctx := context.Background() if tt.setup != nil { - tt.setup(mockUserService) + tt.setup(mockUserService, mockOrgService) } - mockDep := Handler{userService: mockUserService} + mockDep := Handler{userService: mockUserService, orgService: mockOrgService} got, err := mockDep.ListOrganizationUsers(ctx, tt.request) assert.EqualValues(t, tt.want, got) assert.EqualValues(t, tt.wantErr, err) @@ -749,53 +753,43 @@ func TestHandler_ListOrganizationUsers(t *testing.T) { func TestHandler_ListOrganizationServiceUsers(t *testing.T) { tests := []struct { name string - setup func(us *mocks.ServiceUserService) + setup func(us *mocks.ServiceUserService, os *mocks.OrganizationService) req *frontierv1beta1.ListOrganizationServiceUsersRequest want *frontierv1beta1.ListOrganizationServiceUsersResponse wantErr error }{ - { - name: "should return Org Not Exist Error if oragnization does not exist", - setup: func(us *mocks.ServiceUserService) { - us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), "some-org-id").Return([]serviceuser.ServiceUser{}, organization.ErrNotExist) - }, - req: &frontierv1beta1.ListOrganizationServiceUsersRequest{ - Id: "some-org-id", - }, - want: nil, - wantErr: grpcOrgNotFoundErr, - }, - { name: "should return internal error if org service return some error", - setup: func(us *mocks.ServiceUserService) { - us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), "some-org-id").Return([]serviceuser.ServiceUser{}, errors.New("some error")) + setup: func(us *mocks.ServiceUserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, errors.New("some error")) + us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return([]serviceuser.ServiceUser{}, errors.New("some error")) }, req: &frontierv1beta1.ListOrganizationServiceUsersRequest{ - Id: "some-org-id", + Id: testOrgID, }, want: nil, wantErr: grpcInternalServerError, }, { - name: "should return empty list of users if org id is not exist", - setup: func(us *mocks.ServiceUserService) { - us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), "some-org-id").Return([]serviceuser.ServiceUser{}, nil) + name: "should return org not found error if org doesnt exist", + setup: func(us *mocks.ServiceUserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, organization.ErrNotExist) }, req: &frontierv1beta1.ListOrganizationServiceUsersRequest{ - Id: "some-org-id", + Id: testOrgID, }, - want: &frontierv1beta1.ListOrganizationServiceUsersResponse{}, - wantErr: nil, + want: nil, + wantErr: grpcOrgNotFoundErr, }, { name: "should return success if org service return nil error", - setup: func(us *mocks.ServiceUserService) { + setup: func(us *mocks.ServiceUserService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) var testUserList []user.User for _, u := range testUserMap { testUserList = append(testUserList, u) } - us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), "some-org-id").Return([]serviceuser.ServiceUser{ + us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return([]serviceuser.ServiceUser{ { ID: "9f256f86-31a3-11ec-8d3d-0242ac130003", Title: "Sample Service User", @@ -808,7 +802,7 @@ func TestHandler_ListOrganizationServiceUsers(t *testing.T) { }, nil) }, req: &frontierv1beta1.ListOrganizationServiceUsersRequest{ - Id: "some-org-id", + Id: testOrgID, }, want: &frontierv1beta1.ListOrganizationServiceUsersResponse{ Serviceusers: []*frontierv1beta1.ServiceUser{ @@ -832,11 +826,12 @@ func TestHandler_ListOrganizationServiceUsers(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockSvcUserService := new(mocks.ServiceUserService) + mockOrgService := new(mocks.OrganizationService) ctx := context.Background() if tt.setup != nil { - tt.setup(mockSvcUserService) + tt.setup(mockSvcUserService, mockOrgService) } - mockDep := Handler{serviceUserService: mockSvcUserService} + mockDep := Handler{serviceUserService: mockSvcUserService, orgService: mockOrgService} got, err := mockDep.ListOrganizationServiceUsers(ctx, tt.req) assert.EqualValues(t, tt.want, got) assert.EqualValues(t, tt.wantErr, err) @@ -894,6 +889,7 @@ func TestHandler_ListAllOrganizations(t *testing.T) { "email": structpb.NewStringValue("org1@org1.com"), }, }, + State: "enabled", CreatedAt: timestamppb.New(time.Time{}), UpdatedAt: timestamppb.New(time.Time{}), }, @@ -929,10 +925,10 @@ func TestHandler_EnableOrganization(t *testing.T) { { name: "should return internal error if org service return some error", setup: func(os *mocks.OrganizationService) { - os.EXPECT().Enable(mock.AnythingOfType("*context.emptyCtx"), "some-org-id").Return(errors.New("some error")) + os.EXPECT().Enable(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(errors.New("some error")) }, req: &frontierv1beta1.EnableOrganizationRequest{ - Id: "some-org-id", + Id: testOrgID, }, want: nil, wantErr: grpcInternalServerError, @@ -940,10 +936,10 @@ func TestHandler_EnableOrganization(t *testing.T) { { name: "should enable org successfully", setup: func(os *mocks.OrganizationService) { - os.EXPECT().Enable(mock.AnythingOfType("*context.emptyCtx"), "some-org-id").Return(nil) + os.EXPECT().Enable(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(nil) }, req: &frontierv1beta1.EnableOrganizationRequest{ - Id: "some-org-id", + Id: testOrgID, }, want: &frontierv1beta1.EnableOrganizationResponse{}, wantErr: nil, @@ -1023,10 +1019,10 @@ func TestHandler_AddOrganizationUser(t *testing.T) { { name: "should return internal error if org service return some error", setup: func(os *mocks.OrganizationService) { - os.EXPECT().AddUsers(mock.AnythingOfType("*context.emptyCtx"), "some-org-id", []string{"some-user-id"}).Return(errors.New("some error")) + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, errors.New("some error")) }, req: &frontierv1beta1.AddOrganizationUsersRequest{ - Id: "some-org-id", + Id: testOrgID, UserIds: []string{"some-user-id"}, }, want: nil, @@ -1035,10 +1031,11 @@ func TestHandler_AddOrganizationUser(t *testing.T) { { name: "should add user to org successfully", setup: func(os *mocks.OrganizationService) { - os.EXPECT().AddUsers(mock.AnythingOfType("*context.emptyCtx"), "some-org-id", []string{"some-user-id"}).Return(nil) + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + os.EXPECT().AddUsers(mock.AnythingOfType("*context.emptyCtx"), testOrgID, []string{"some-user-id"}).Return(nil) }, req: &frontierv1beta1.AddOrganizationUsersRequest{ - Id: "some-org-id", + Id: testOrgID, UserIds: []string{"some-user-id"}, }, want: &frontierv1beta1.AddOrganizationUsersResponse{}, @@ -1064,30 +1061,59 @@ func TestHandler_AddOrganizationUser(t *testing.T) { func TestHandler_RemoveOrganizationUser(t *testing.T) { tests := []struct { name string - setup func(os *mocks.OrganizationService) + setup func(os *mocks.OrganizationService, us *mocks.UserService) req *frontierv1beta1.RemoveOrganizationUserRequest want *frontierv1beta1.RemoveOrganizationUserResponse wantErr error }{ { name: "should return internal error if org service return some error", - setup: func(os *mocks.OrganizationService) { - os.EXPECT().RemoveUsers(mock.AnythingOfType("*context.emptyCtx"), "some-org-id", []string{"some-user-id"}).Return(errors.New("some error")) + setup: func(os *mocks.OrganizationService, us *mocks.UserService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, errors.New("some error")) }, req: &frontierv1beta1.RemoveOrganizationUserRequest{ - Id: "some-org-id", + Id: testOrgID, UserId: "some-user-id", }, want: nil, wantErr: grpcInternalServerError, }, + { + name: "should return the error and not remove user if it is the last admin user", + setup: func(os *mocks.OrganizationService, us *mocks.UserService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), testOrgID, "update").Return([]user.User{ + testUserMap[testUserID], + }, nil) + os.EXPECT().RemoveUsers(mock.AnythingOfType("*context.emptyCtx"), testOrgID, []string{testUserID}).Return(nil) + }, + req: &frontierv1beta1.RemoveOrganizationUserRequest{ + Id: testOrgID, + UserId: testUserID, + }, + want: nil, + wantErr: grpcMinAdminCountErr, + }, { name: "should remove user from org successfully", - setup: func(os *mocks.OrganizationService) { - os.EXPECT().RemoveUsers(mock.AnythingOfType("*context.emptyCtx"), "some-org-id", []string{"some-user-id"}).Return(nil) + setup: func(os *mocks.OrganizationService, us *mocks.UserService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(testOrgMap[testOrgID], nil) + us.EXPECT().ListByOrg(mock.AnythingOfType("*context.emptyCtx"), testOrgID, "update").Return([]user.User{ + testUserMap[testUserID], + { + ID: "some-user-id", + Title: "User 1", + Name: "user1", + Email: "test@raystack.org", + Metadata: map[string]interface{}{}, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + }, nil) + os.EXPECT().RemoveUsers(mock.AnythingOfType("*context.emptyCtx"), testOrgID, []string{"some-user-id"}).Return(nil) }, req: &frontierv1beta1.RemoveOrganizationUserRequest{ - Id: "some-org-id", + Id: testOrgID, UserId: "some-user-id", }, want: &frontierv1beta1.RemoveOrganizationUserResponse{}, @@ -1098,14 +1124,15 @@ func TestHandler_RemoveOrganizationUser(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockOrgService := new(mocks.OrganizationService) + mockUserService := new(mocks.UserService) ctx := context.Background() if tt.setup != nil { - tt.setup(mockOrgService) + tt.setup(mockOrgService, mockUserService) } - mockDep := Handler{orgService: mockOrgService} + mockDep := Handler{orgService: mockOrgService, userService: mockUserService} got, err := mockDep.RemoveOrganizationUser(ctx, tt.req) - assert.EqualValues(t, tt.want, got) assert.EqualValues(t, tt.wantErr, err) + assert.EqualValues(t, tt.want, got) }) } } @@ -1113,14 +1140,15 @@ func TestHandler_RemoveOrganizationUser(t *testing.T) { func TestHandler_ListOrganizationProjects(t *testing.T) { tests := []struct { name string - setup func(ps *mocks.ProjectService) + setup func(ps *mocks.ProjectService, os *mocks.OrganizationService) req *frontierv1beta1.ListOrganizationProjectsRequest want *frontierv1beta1.ListOrganizationProjectsResponse wantErr error }{ { name: "should return error if organization does not exist ", - setup: func(ps *mocks.ProjectService) { + setup: func(ps *mocks.ProjectService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), "some-org-id").Return(organization.Organization{}, organization.ErrNotExist) ps.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), project.Filter{OrgID: "some-org-id"}).Return([]project.Project{}, organization.ErrNotExist) }, req: &frontierv1beta1.ListOrganizationProjectsRequest{ @@ -1131,19 +1159,20 @@ func TestHandler_ListOrganizationProjects(t *testing.T) { }, { name: "should return internal error if org service return some error", - setup: func(ps *mocks.ProjectService) { - ps.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), project.Filter{OrgID: "some-org-id"}).Return(nil, errors.New("some error")) + setup: func(ps *mocks.ProjectService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgID).Return(organization.Organization{}, errors.New("some error")) }, req: &frontierv1beta1.ListOrganizationProjectsRequest{ - Id: "some-org-id", + Id: testOrgID, }, want: nil, wantErr: grpcInternalServerError, }, { name: "should return list of projects successfully", - setup: func(ps *mocks.ProjectService) { - ps.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), project.Filter{OrgID: "some-org-id"}).Return([]project.Project{ + setup: func(ps *mocks.ProjectService, os *mocks.OrganizationService) { + os.EXPECT().Get(mock.AnythingOfType("*context.emptyCtx"), testOrgMap[testOrgID].Name).Return(testOrgMap[testOrgID], nil) + ps.EXPECT().List(mock.AnythingOfType("*context.emptyCtx"), project.Filter{OrgID: testOrgID}).Return([]project.Project{ { ID: "some-project-id", Name: "some-project-name", @@ -1151,7 +1180,7 @@ func TestHandler_ListOrganizationProjects(t *testing.T) { "foo": "bar", }, Organization: organization.Organization{ - ID: "some-org-id", + ID: testOrgID, }, CreatedAt: time.Time{}, UpdatedAt: time.Time{}, @@ -1159,14 +1188,14 @@ func TestHandler_ListOrganizationProjects(t *testing.T) { }, nil) }, req: &frontierv1beta1.ListOrganizationProjectsRequest{ - Id: "some-org-id", + Id: testOrgMap[testOrgID].Name, }, want: &frontierv1beta1.ListOrganizationProjectsResponse{ Projects: []*frontierv1beta1.Project{ { Id: "some-project-id", Name: "some-project-name", - OrgId: "some-org-id", + OrgId: testOrgID, Metadata: &structpb.Struct{ Fields: map[string]*structpb.Value{ "foo": { @@ -1188,11 +1217,12 @@ func TestHandler_ListOrganizationProjects(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockProjectService := new(mocks.ProjectService) + mockOrgService := new(mocks.OrganizationService) ctx := context.Background() if tt.setup != nil { - tt.setup(mockProjectService) + tt.setup(mockProjectService, mockOrgService) } - mockDep := Handler{projectService: mockProjectService} + mockDep := Handler{projectService: mockProjectService, orgService: mockOrgService} got, err := mockDep.ListOrganizationProjects(ctx, tt.req) assert.EqualValues(t, tt.want, got) assert.EqualValues(t, tt.wantErr, err) diff --git a/internal/store/postgres/organization_repository.go b/internal/store/postgres/organization_repository.go index 18c48d5d7..68ac5435a 100644 --- a/internal/store/postgres/organization_repository.go +++ b/internal/store/postgres/organization_repository.go @@ -39,7 +39,7 @@ func (r OrganizationRepository) GetByID(ctx context.Context, id string) (organiz query, params, err := dialect.From(TABLE_ORGANIZATIONS).Where(goqu.Ex{ "id": id, - }).Where(notDisabledOrgExp).ToSQL() + }).ToSQL() if err != nil { return organization.Organization{}, fmt.Errorf("%w: %s", queryErr, err) } @@ -114,7 +114,7 @@ func (r OrganizationRepository) GetByName(ctx context.Context, name string) (org query, params, err := dialect.From(TABLE_ORGANIZATIONS).Where(goqu.Ex{ "name": name, - }).Where(notDisabledOrgExp).ToSQL() + }).ToSQL() if err != nil { return organization.Organization{}, fmt.Errorf("%w: %s", queryErr, err) } diff --git a/pkg/server/config.go b/pkg/server/config.go index b0baeda3d..8b2e47b09 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -57,7 +57,8 @@ type Config struct { TelemetryConfig telemetry.Config `yaml:"telemetry_config" mapstructure:"telemetry_config"` Authentication authenticate.Config `yaml:"authentication" mapstructure:"authentication"` - + // DisableOrgsOnCreate if set to true will turn the default state of new orgs as disabled. Default is false + DisableOrgsOnCreate bool `yaml:"disable_orgs_on_create" mapstructure:"disable_orgs_on_create" default:"false"` // DisableOrgsListing if set to true will disallow non-admin APIs to list all organizations DisableOrgsListing bool `yaml:"disable_orgs_listing" mapstructure:"disable_orgs_listing" default:"false"` // DisableUsersListing if set to true will disallow non-admin APIs to list all users diff --git a/test/e2e/regression/api_test.go b/test/e2e/regression/api_test.go index 84c406ccd..d3a482ce3 100644 --- a/test/e2e/regression/api_test.go +++ b/test/e2e/regression/api_test.go @@ -412,14 +412,14 @@ func (s *APIRegressionTestSuite) TestGroupAPI() { s.Assert().Equal(codes.InvalidArgument, status.Convert(err).Code()) }) - s.Run("3. org admin create a new team with wrong org id should return invalid argument", func() { + s.Run("3. org admin create a new team with wrong org id should return not found", func() { _, err := s.testBench.Client.CreateGroup(ctxOrgAdminAuth, &frontierv1beta1.CreateGroupRequest{ OrgId: "not-uuid", Body: &frontierv1beta1.GroupRequestBody{ Name: "new-group", }, }) - s.Assert().Equal(codes.InvalidArgument, status.Convert(err).Code()) + s.Assert().Equal(codes.NotFound, status.Convert(err).Code()) }) s.Run("4. org admin create a new team with same name and org-id should conflict", func() { @@ -1159,7 +1159,8 @@ func (s *APIRegressionTestSuite) TestInvitationAPI() { // accept invite should add user to org and delete it _, err = s.testBench.Client.AcceptOrganizationInvitation(ctxOrgAdminAuth, &frontierv1beta1.AcceptOrganizationInvitationRequest{ - Id: createdInvite.GetId(), + Id: createdInvite.GetId(), + OrgId: existingOrg.GetOrganization().GetId(), }) s.Assert().NoError(err)