From 56151a82ebb5627259dbb74f76f4bca5b170d7fa Mon Sep 17 00:00:00 2001 From: Keepers Date: Sat, 1 Jul 2023 07:52:57 -0600 Subject: [PATCH] remove discovery pkg (#3677) Currently unused, and functionality is largely duplicated by the services/m365 package. --- #### Does this PR need a docs update or release note? - [x] :no_entry: No #### Type of change - [ ] :broom: Tech Debt/Cleanup #### Test Plan - [x] :zap: Unit test - [x] :green_heart: E2E --- CHANGELOG.md | 1 + src/internal/m365/backup.go | 8 +- src/internal/m365/discovery/discovery.go | 150 --------- src/internal/m365/discovery/discovery_test.go | 296 ------------------ src/internal/m365/graph/errors.go | 31 +- src/pkg/services/m365/m365.go | 22 +- src/pkg/services/m365/m365_test.go | 282 ++++++++++++++--- 7 files changed, 278 insertions(+), 512 deletions(-) delete mode 100644 src/internal/m365/discovery/discovery.go delete mode 100644 src/internal/m365/discovery/discovery_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a592bc02..f8d4ffa72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Handle OLE conversion errors when trying to fetch attachments - Fix uploading large attachments for emails and calendar - Fixed high memory use in OneDrive backup related to logging +- Return a ServiceNotEnabled error when a tenant has no active SharePoint license. ### Changed - Switched to Go 1.20 diff --git a/src/internal/m365/backup.go b/src/internal/m365/backup.go index 3d451dd43..50514e4ad 100644 --- a/src/internal/m365/backup.go +++ b/src/internal/m365/backup.go @@ -10,7 +10,6 @@ import ( "github.com/alcionai/corso/src/internal/common/prefixmatcher" "github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/diagnostics" - "github.com/alcionai/corso/src/internal/m365/discovery" "github.com/alcionai/corso/src/internal/m365/exchange" "github.com/alcionai/corso/src/internal/m365/graph" "github.com/alcionai/corso/src/internal/m365/onedrive" @@ -21,6 +20,7 @@ import ( "github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/selectors" + "github.com/alcionai/corso/src/pkg/services/m365/api" ) // --------------------------------------------------------------------------- @@ -203,9 +203,13 @@ func verifyBackupInputs(sels selectors.Selector, siteIDs []string) error { return nil } +type getInfoer interface { + GetInfo(context.Context, string) (*api.UserInfo, error) +} + func checkServiceEnabled( ctx context.Context, - gi discovery.GetInfoer, + gi getInfoer, service path.ServiceType, resource string, ) (bool, bool, error) { diff --git a/src/internal/m365/discovery/discovery.go b/src/internal/m365/discovery/discovery.go deleted file mode 100644 index 1aae9e7e7..000000000 --- a/src/internal/m365/discovery/discovery.go +++ /dev/null @@ -1,150 +0,0 @@ -package discovery - -import ( - "context" - - "github.com/alcionai/clues" - "github.com/microsoftgraph/msgraph-sdk-go/models" - - "github.com/alcionai/corso/src/internal/m365/graph" - "github.com/alcionai/corso/src/pkg/account" - "github.com/alcionai/corso/src/pkg/fault" - "github.com/alcionai/corso/src/pkg/services/m365/api" -) - -// --------------------------------------------------------------------------- -// interfaces -// --------------------------------------------------------------------------- - -type getter interface { - GetByID(context.Context, string) (models.Userable, error) -} - -type GetInfoer interface { - GetInfo(context.Context, string) (*api.UserInfo, error) -} - -type getWithInfoer interface { - getter - GetInfoer -} - -type GetDefaultDriver interface { - GetDefaultDrive(ctx context.Context, userID string) (models.Driveable, error) -} - -type getAller interface { - GetAll(ctx context.Context, errs *fault.Bus) ([]models.Userable, error) -} - -// --------------------------------------------------------------------------- -// helpers -// --------------------------------------------------------------------------- - -func apiClient(ctx context.Context, acct account.Account) (api.Client, error) { - m365, err := acct.M365Config() - if err != nil { - return api.Client{}, clues.Wrap(err, "retrieving m365 account configuration").WithClues(ctx) - } - - client, err := api.NewClient(m365) - if err != nil { - return api.Client{}, clues.Wrap(err, "creating api client").WithClues(ctx) - } - - return client, nil -} - -// --------------------------------------------------------------------------- -// users -// --------------------------------------------------------------------------- - -// Users fetches all users in the tenant. -func Users( - ctx context.Context, - ga getAller, - errs *fault.Bus, -) ([]models.Userable, error) { - users, err := ga.GetAll(ctx, errs) - if err != nil { - return nil, clues.Wrap(err, "getting all users") - } - - return users, nil -} - -// UserDetails fetches detailed info like - userPurpose for all users in the tenant. -func GetUserInfo( - ctx context.Context, - acct account.Account, - userID string, - errs *fault.Bus, -) (*api.UserInfo, error) { - client, err := apiClient(ctx, acct) - if err != nil { - return nil, err - } - - aui, err := client.Users().GetInfo(ctx, userID) - if err != nil { - return nil, clues.Stack(err) - } - - return aui, nil -} - -// User fetches a single user's data. -func User( - ctx context.Context, - gwi getWithInfoer, - userID string, -) (models.Userable, *api.UserInfo, error) { - u, err := gwi.GetByID(ctx, userID) - if err != nil { - if graph.IsErrUserNotFound(err) { - return nil, nil, clues.Stack(graph.ErrResourceOwnerNotFound, err).With("user_id", userID) - } - - return nil, nil, clues.Wrap(err, "getting user") - } - - ui, err := gwi.GetInfo(ctx, userID) - if err != nil { - return nil, nil, clues.Wrap(err, "getting user info") - } - - return u, ui, nil -} - -// UserInfo produces extensible user info: metadata that is relevant -// or identified in Corso, but not in m365. -func UserInfo( - ctx context.Context, - gi GetInfoer, - userID string, -) (*api.UserInfo, error) { - ui, err := gi.GetInfo(ctx, userID) - if err != nil { - return nil, clues.Wrap(err, "getting user info") - } - - return ui, nil -} - -// --------------------------------------------------------------------------- -// sites -// --------------------------------------------------------------------------- - -// Sites fetches all sharepoint sites in the tenant -func Sites( - ctx context.Context, - acct account.Account, - errs *fault.Bus, -) ([]models.Siteable, error) { - client, err := apiClient(ctx, acct) - if err != nil { - return nil, err - } - - return client.Sites().GetAll(ctx, errs) -} diff --git a/src/internal/m365/discovery/discovery_test.go b/src/internal/m365/discovery/discovery_test.go deleted file mode 100644 index 0be5aebe2..000000000 --- a/src/internal/m365/discovery/discovery_test.go +++ /dev/null @@ -1,296 +0,0 @@ -package discovery_test - -import ( - "testing" - - "github.com/alcionai/clues" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - - "github.com/alcionai/corso/src/internal/m365/discovery" - "github.com/alcionai/corso/src/internal/m365/graph" - "github.com/alcionai/corso/src/internal/tester" - "github.com/alcionai/corso/src/pkg/account" - "github.com/alcionai/corso/src/pkg/credentials" - "github.com/alcionai/corso/src/pkg/fault" - "github.com/alcionai/corso/src/pkg/path" - "github.com/alcionai/corso/src/pkg/services/m365/api" -) - -type DiscoveryIntgSuite struct { - tester.Suite -} - -func TestDiscoveryIntgSuite(t *testing.T) { - suite.Run(t, &DiscoveryIntgSuite{ - Suite: tester.NewIntegrationSuite( - t, - [][]string{tester.M365AcctCredEnvs}), - }) -} - -func (suite *DiscoveryIntgSuite) SetupSuite() { - ctx, flush := tester.NewContext(suite.T()) - defer flush() - - graph.InitializeConcurrencyLimiter(ctx, true, 4) -} - -func (suite *DiscoveryIntgSuite) TestUsers() { - t := suite.T() - - ctx, flush := tester.NewContext(t) - defer flush() - - var ( - acct = tester.NewM365Account(t) - errs = fault.New(true) - ) - - creds, err := acct.M365Config() - require.NoError(t, err) - - cli, err := api.NewClient(creds) - require.NoError(t, err) - - users, err := discovery.Users(ctx, cli.Users(), errs) - assert.NoError(t, err, clues.ToCore(err)) - - ferrs := errs.Errors() - assert.Nil(t, ferrs.Failure) - assert.Empty(t, ferrs.Recovered) - assert.NotEmpty(t, users) -} - -func (suite *DiscoveryIntgSuite) TestUsers_InvalidCredentials() { - table := []struct { - name string - acct func(t *testing.T) account.Account - }{ - { - name: "Invalid Credentials", - acct: func(t *testing.T) account.Account { - a, err := account.NewAccount( - account.ProviderM365, - account.M365Config{ - M365: credentials.M365{ - AzureClientID: "Test", - AzureClientSecret: "without", - }, - AzureTenantID: "data", - }, - ) - require.NoError(t, err, clues.ToCore(err)) - - return a - }, - }, - } - - for _, test := range table { - suite.Run(test.name, func() { - t := suite.T() - - ctx, flush := tester.NewContext(t) - defer flush() - - acct := test.acct(t) - - creds, err := acct.M365Config() - require.NoError(t, err) - - cli, err := api.NewClient(creds) - require.NoError(t, err) - - users, err := discovery.Users(ctx, cli.Users(), fault.New(true)) - assert.Empty(t, users, "returned some users") - assert.NotNil(t, err) - }) - } -} - -func (suite *DiscoveryIntgSuite) TestSites() { - t := suite.T() - - ctx, flush := tester.NewContext(t) - defer flush() - - var ( - acct = tester.NewM365Account(t) - errs = fault.New(true) - ) - - sites, err := discovery.Sites(ctx, acct, errs) - assert.NoError(t, err, clues.ToCore(err)) - - ferrs := errs.Errors() - assert.Nil(t, ferrs.Failure) - assert.Empty(t, ferrs.Recovered) - assert.NotEmpty(t, sites) -} - -func (suite *DiscoveryIntgSuite) TestSites_InvalidCredentials() { - table := []struct { - name string - acct func(t *testing.T) account.Account - }{ - { - name: "Invalid Credentials", - acct: func(t *testing.T) account.Account { - a, err := account.NewAccount( - account.ProviderM365, - account.M365Config{ - M365: credentials.M365{ - AzureClientID: "Test", - AzureClientSecret: "without", - }, - AzureTenantID: "data", - }, - ) - require.NoError(t, err, clues.ToCore(err)) - - return a - }, - }, - { - name: "Empty Credentials", - acct: func(t *testing.T) account.Account { - // intentionally swallowing the error here - a, _ := account.NewAccount(account.ProviderM365) - return a - }, - }, - } - - for _, test := range table { - suite.Run(test.name, func() { - var ( - t = suite.T() - a = test.acct(t) - errs = fault.New(true) - ctx, flush = tester.NewContext(t) - ) - - defer flush() - - sites, err := discovery.Sites(ctx, a, errs) - assert.Empty(t, sites, "returned some sites") - assert.NotNil(t, err) - }) - } -} - -func (suite *DiscoveryIntgSuite) TestUserInfo() { - t := suite.T() - acct := tester.NewM365Account(t) - - creds, err := acct.M365Config() - require.NoError(t, err) - - cli, err := api.NewClient(creds) - require.NoError(t, err) - - uapi := cli.Users() - - table := []struct { - name string - user string - expect *api.UserInfo - expectErr require.ErrorAssertionFunc - }{ - { - name: "standard test user", - user: tester.M365UserID(t), - expect: &api.UserInfo{ - ServicesEnabled: map[path.ServiceType]struct{}{ - path.ExchangeService: {}, - path.OneDriveService: {}, - }, - Mailbox: api.MailboxInfo{ - Purpose: "user", - ErrGetMailBoxSetting: nil, - }, - }, - expectErr: require.NoError, - }, - { - name: "user does not exist", - user: uuid.NewString(), - expect: &api.UserInfo{ - ServicesEnabled: map[path.ServiceType]struct{}{}, - Mailbox: api.MailboxInfo{}, - }, - expectErr: require.Error, - }, - } - for _, test := range table { - suite.Run(test.name, func() { - t := suite.T() - - ctx, flush := tester.NewContext(t) - defer flush() - - result, err := discovery.UserInfo(ctx, uapi, test.user) - test.expectErr(t, err, clues.ToCore(err)) - - if err != nil { - return - } - - assert.Equal(t, test.expect.ServicesEnabled, result.ServicesEnabled) - }) - } -} - -func (suite *DiscoveryIntgSuite) TestUserWithoutDrive() { - t := suite.T() - acct := tester.NewM365Account(t) - userID := tester.M365UserID(t) - - table := []struct { - name string - user string - expect *api.UserInfo - }{ - { - name: "user without drive and exchange", - user: "a53c26f7-5100-4acb-a910-4d20960b2c19", // User: testevents@10rqc2.onmicrosoft.com - expect: &api.UserInfo{ - ServicesEnabled: map[path.ServiceType]struct{}{}, - Mailbox: api.MailboxInfo{ - ErrGetMailBoxSetting: []error{api.ErrMailBoxSettingsNotFound}, - }, - }, - }, - { - name: "user with drive and exchange", - user: userID, - expect: &api.UserInfo{ - ServicesEnabled: map[path.ServiceType]struct{}{ - path.ExchangeService: {}, - path.OneDriveService: {}, - }, - Mailbox: api.MailboxInfo{ - Purpose: "user", - ErrGetMailBoxSetting: []error{}, - }, - }, - }, - } - for _, test := range table { - suite.Run(test.name, func() { - t := suite.T() - - ctx, flush := tester.NewContext(t) - defer flush() - - result, err := discovery.GetUserInfo(ctx, acct, test.user, fault.New(true)) - require.NoError(t, err, clues.ToCore(err)) - assert.Equal(t, test.expect.ServicesEnabled, result.ServicesEnabled) - assert.Equal(t, test.expect.Mailbox.ErrGetMailBoxSetting, result.Mailbox.ErrGetMailBoxSetting) - assert.Equal(t, test.expect.Mailbox.Purpose, result.Mailbox.Purpose) - }) - } -} diff --git a/src/internal/m365/graph/errors.go b/src/internal/m365/graph/errors.go index ca70df38d..a408c61a2 100644 --- a/src/internal/m365/graph/errors.go +++ b/src/internal/m365/graph/errors.go @@ -26,10 +26,20 @@ import ( type errorCode string const ( - activityLimitReached errorCode = "activityLimitReached" - emailFolderNotFound errorCode = "ErrorSyncFolderNotFound" - errorAccessDenied errorCode = "ErrorAccessDenied" - errorItemNotFound errorCode = "ErrorItemNotFound" + // cannotOpenFileAttachment happen when an attachment is + // inaccessible. The error message is usually "OLE conversion + // failed for an attachment." + cannotOpenFileAttachment errorCode = "ErrorCannotOpenFileAttachment" + emailFolderNotFound errorCode = "ErrorSyncFolderNotFound" + errorAccessDenied errorCode = "ErrorAccessDenied" + errorItemNotFound errorCode = "ErrorItemNotFound" + // This error occurs when an attempt is made to create a folder that has + // the same name as another folder in the same parent. Such duplicate folder + // names are not allowed by graph. + folderExists errorCode = "ErrorFolderExists" + // Some datacenters are returning this when we try to get the inbox of a user + // that doesn't exist. + invalidUser errorCode = "ErrorInvalidUser" itemNotFound errorCode = "itemNotFound" MailboxNotEnabledForRESTAPI errorCode = "MailboxNotEnabledForRESTAPI" malwareDetected errorCode = "malwareDetected" @@ -39,22 +49,11 @@ const ( quotaExceeded errorCode = "ErrorQuotaExceeded" RequestResourceNotFound errorCode = "Request_ResourceNotFound" // Returned when we try to get the inbox of a user that doesn't exist. - ResourceNotFound errorCode = "ResourceNotFound" - // Some datacenters are returning this when we try to get the inbox of a user - // that doesn't exist. - invalidUser errorCode = "ErrorInvalidUser" + ResourceNotFound errorCode = "ResourceNotFound" resyncRequired errorCode = "ResyncRequired" syncFolderNotFound errorCode = "ErrorSyncFolderNotFound" syncStateInvalid errorCode = "SyncStateInvalid" syncStateNotFound errorCode = "SyncStateNotFound" - // This error occurs when an attempt is made to create a folder that has - // the same name as another folder in the same parent. Such duplicate folder - // names are not allowed by graph. - folderExists errorCode = "ErrorFolderExists" - // cannotOpenFileAttachment happen when an attachment is - // inaccessible. The error message is usually "OLE conversion - // failed for an attachment." - cannotOpenFileAttachment errorCode = "ErrorCannotOpenFileAttachment" ) type errorMessage string diff --git a/src/pkg/services/m365/m365.go b/src/pkg/services/m365/m365.go index 64846b81e..1a7f8f5da 100644 --- a/src/pkg/services/m365/m365.go +++ b/src/pkg/services/m365/m365.go @@ -8,18 +8,18 @@ import ( "github.com/alcionai/corso/src/internal/common/idname" "github.com/alcionai/corso/src/internal/common/ptr" - "github.com/alcionai/corso/src/internal/m365/discovery" "github.com/alcionai/corso/src/internal/m365/graph" "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/services/m365/api" ) -// ServiceAccess is true if a resource owner is capable of -// accessing or utilizing the specified service. -type ServiceAccess struct { - Exchange bool - // TODO: onedrive, sharepoint +// --------------------------------------------------------------------------- +// interfaces & structs +// --------------------------------------------------------------------------- + +type getDefaultDriver interface { + GetDefaultDrive(ctx context.Context, userID string) (models.Driveable, error) } // --------------------------------------------------------------------------- @@ -107,7 +107,7 @@ func UserHasDrives(ctx context.Context, acct account.Account, userID string) (bo return checkUserHasDrives(ctx, ac.Users(), userID) } -func checkUserHasDrives(ctx context.Context, dgdd discovery.GetDefaultDriver, userID string) (bool, error) { +func checkUserHasDrives(ctx context.Context, dgdd getDefaultDriver, userID string) (bool, error) { _, err := dgdd.GetDefaultDrive(ctx, userID) if err != nil { // we consider this a non-error case, since it @@ -135,7 +135,7 @@ func usersNoInfo(ctx context.Context, acct account.Account, errs *fault.Bus) ([] return nil, clues.Stack(err).WithClues(ctx) } - us, err := discovery.Users(ctx, ac.Users(), errs) + us, err := ac.Users().GetAll(ctx, errs) if err != nil { return nil, err } @@ -167,7 +167,7 @@ func Users(ctx context.Context, acct account.Account, errs *fault.Bus) ([]*User, return nil, clues.Stack(err).WithClues(ctx) } - us, err := discovery.Users(ctx, ac.Users(), errs) + us, err := ac.Users().GetAll(ctx, errs) if err != nil { return nil, err } @@ -180,7 +180,7 @@ func Users(ctx context.Context, acct account.Account, errs *fault.Bus) ([]*User, return nil, clues.Wrap(err, "formatting user data") } - userInfo, err := discovery.GetUserInfo(ctx, acct, pu.ID, errs) + userInfo, err := ac.Users().GetInfo(ctx, pu.ID) if err != nil { return nil, clues.Wrap(err, "getting user details") } @@ -220,7 +220,7 @@ func GetUserInfo( return nil, clues.Stack(err).WithClues(ctx) } - ui, err := discovery.UserInfo(ctx, ac.Users(), userID) + ui, err := ac.Users().GetInfo(ctx, userID) if err != nil { return nil, err } diff --git a/src/pkg/services/m365/m365_test.go b/src/pkg/services/m365/m365_test.go index 838d908be..1ccf2be3a 100644 --- a/src/pkg/services/m365/m365_test.go +++ b/src/pkg/services/m365/m365_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/alcionai/clues" + "github.com/google/uuid" "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" "github.com/stretchr/testify/assert" @@ -12,11 +13,13 @@ import ( "github.com/stretchr/testify/suite" "github.com/alcionai/corso/src/internal/common/ptr" - "github.com/alcionai/corso/src/internal/m365/discovery" "github.com/alcionai/corso/src/internal/m365/graph" "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/pkg/account" + "github.com/alcionai/corso/src/pkg/credentials" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" + "github.com/alcionai/corso/src/pkg/services/m365/api" ) type M365IntegrationSuite struct { @@ -31,6 +34,13 @@ func TestM365IntegrationSuite(t *testing.T) { }) } +func (suite *M365IntegrationSuite) SetupSuite() { + ctx, flush := tester.NewContext(suite.T()) + defer flush() + + graph.InitializeConcurrencyLimiter(ctx, true, 4) +} + func (suite *M365IntegrationSuite) TestUsers() { t := suite.T() @@ -80,35 +90,6 @@ func (suite *M365IntegrationSuite) TestUsersCompat_HasNoInfo() { } } -func (suite *M365IntegrationSuite) TestGetUserInfo() { - t := suite.T() - - ctx, flush := tester.NewContext(t) - defer flush() - - graph.InitializeConcurrencyLimiter(ctx, true, 4) - - var ( - acct = tester.NewM365Account(t) - uid = tester.M365UserID(t) - ) - - info, err := GetUserInfo(ctx, acct, uid) - require.NoError(t, err, clues.ToCore(err)) - require.NotNil(t, info) - require.NotEmpty(t, info) - - expectEnabled := map[path.ServiceType]struct{}{ - path.ExchangeService: {}, - path.OneDriveService: {}, - } - - assert.NotEmpty(t, info.ServicesEnabled) - assert.NotEmpty(t, info.Mailbox) - assert.Equal(t, expectEnabled, info.ServicesEnabled) - assert.Equal(t, "user", info.Mailbox.Purpose) -} - func (suite *M365IntegrationSuite) TestUserHasMailbox() { t := suite.T() @@ -183,13 +164,13 @@ func (m mockDGDD) GetDefaultDrive(context.Context, string) (models.Driveable, er func (suite *m365UnitSuite) TestCheckUserHasDrives() { table := []struct { name string - mock func(context.Context) discovery.GetDefaultDriver + mock func(context.Context) getDefaultDriver expect assert.BoolAssertionFunc expectErr func(*testing.T, error) }{ { name: "ok", - mock: func(ctx context.Context) discovery.GetDefaultDriver { + mock: func(ctx context.Context) getDefaultDriver { return mockDGDD{models.NewDrive(), nil} }, expect: assert.True, @@ -199,7 +180,7 @@ func (suite *m365UnitSuite) TestCheckUserHasDrives() { }, { name: "mysite not found", - mock: func(ctx context.Context) discovery.GetDefaultDriver { + mock: func(ctx context.Context) getDefaultDriver { odErr := odataerrors.NewODataError() merr := odataerrors.NewMainError() merr.SetCode(ptr.To("code")) @@ -215,7 +196,7 @@ func (suite *m365UnitSuite) TestCheckUserHasDrives() { }, { name: "mysite URL not found", - mock: func(ctx context.Context) discovery.GetDefaultDriver { + mock: func(ctx context.Context) getDefaultDriver { odErr := odataerrors.NewODataError() merr := odataerrors.NewMainError() merr.SetCode(ptr.To("code")) @@ -231,7 +212,7 @@ func (suite *m365UnitSuite) TestCheckUserHasDrives() { }, { name: "no sharepoint license", - mock: func(ctx context.Context) discovery.GetDefaultDriver { + mock: func(ctx context.Context) getDefaultDriver { odErr := odataerrors.NewODataError() merr := odataerrors.NewMainError() merr.SetCode(ptr.To("code")) @@ -247,7 +228,7 @@ func (suite *m365UnitSuite) TestCheckUserHasDrives() { }, { name: "user not found", - mock: func(ctx context.Context) discovery.GetDefaultDriver { + mock: func(ctx context.Context) getDefaultDriver { odErr := odataerrors.NewODataError() merr := odataerrors.NewMainError() merr.SetCode(ptr.To(string(graph.RequestResourceNotFound))) @@ -263,7 +244,7 @@ func (suite *m365UnitSuite) TestCheckUserHasDrives() { }, { name: "arbitrary error", - mock: func(ctx context.Context) discovery.GetDefaultDriver { + mock: func(ctx context.Context) getDefaultDriver { odErr := odataerrors.NewODataError() merr := odataerrors.NewMainError() merr.SetCode(ptr.To("code")) @@ -363,3 +344,230 @@ func (suite *m365UnitSuite) TestGetAllSites() { }) } } + +type DiscoveryIntgSuite struct { + tester.Suite + acct account.Account +} + +func TestDiscoveryIntgSuite(t *testing.T) { + suite.Run(t, &DiscoveryIntgSuite{ + Suite: tester.NewIntegrationSuite( + t, + [][]string{tester.M365AcctCredEnvs}), + }) +} + +func (suite *DiscoveryIntgSuite) SetupSuite() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + defer flush() + + graph.InitializeConcurrencyLimiter(ctx, true, 4) + + suite.acct = tester.NewM365Account(t) +} + +func (suite *DiscoveryIntgSuite) TestUsers() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + defer flush() + + errs := fault.New(true) + + users, err := Users(ctx, suite.acct, errs) + assert.NoError(t, err, clues.ToCore(err)) + + ferrs := errs.Errors() + assert.Nil(t, ferrs.Failure) + assert.Empty(t, ferrs.Recovered) + assert.NotEmpty(t, users) +} + +func (suite *DiscoveryIntgSuite) TestUsers_InvalidCredentials() { + table := []struct { + name string + acct func(t *testing.T) account.Account + }{ + { + name: "Invalid Credentials", + acct: func(t *testing.T) account.Account { + a, err := account.NewAccount( + account.ProviderM365, + account.M365Config{ + M365: credentials.M365{ + AzureClientID: "Test", + AzureClientSecret: "without", + }, + AzureTenantID: "data", + }, + ) + require.NoError(t, err, clues.ToCore(err)) + + return a + }, + }, + } + + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + defer flush() + + users, err := Users(ctx, test.acct(t), fault.New(true)) + assert.Empty(t, users, "returned some users") + assert.NotNil(t, err) + }) + } +} + +func (suite *DiscoveryIntgSuite) TestSites_InvalidCredentials() { + table := []struct { + name string + acct func(t *testing.T) account.Account + }{ + { + name: "Invalid Credentials", + acct: func(t *testing.T) account.Account { + a, err := account.NewAccount( + account.ProviderM365, + account.M365Config{ + M365: credentials.M365{ + AzureClientID: "Test", + AzureClientSecret: "without", + }, + AzureTenantID: "data", + }, + ) + require.NoError(t, err, clues.ToCore(err)) + + return a + }, + }, + { + name: "Empty Credentials", + acct: func(t *testing.T) account.Account { + // intentionally swallowing the error here + a, _ := account.NewAccount(account.ProviderM365) + return a + }, + }, + } + + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + defer flush() + + sites, err := Sites(ctx, test.acct(t), fault.New(true)) + assert.Empty(t, sites, "returned some sites") + assert.NotNil(t, err) + }) + } +} + +func (suite *DiscoveryIntgSuite) TestGetUserInfo() { + table := []struct { + name string + user string + expect *api.UserInfo + expectErr require.ErrorAssertionFunc + }{ + { + name: "standard test user", + user: tester.M365UserID(suite.T()), + expect: &api.UserInfo{ + ServicesEnabled: map[path.ServiceType]struct{}{ + path.ExchangeService: {}, + path.OneDriveService: {}, + }, + Mailbox: api.MailboxInfo{ + Purpose: "user", + ErrGetMailBoxSetting: nil, + }, + }, + expectErr: require.NoError, + }, + { + name: "user does not exist", + user: uuid.NewString(), + expect: &api.UserInfo{ + ServicesEnabled: map[path.ServiceType]struct{}{}, + Mailbox: api.MailboxInfo{}, + }, + expectErr: require.Error, + }, + } + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + defer flush() + + result, err := GetUserInfo(ctx, suite.acct, test.user) + test.expectErr(t, err, clues.ToCore(err)) + + if err != nil { + return + } + + assert.Equal(t, test.expect.ServicesEnabled, result.ServicesEnabled) + }) + } +} + +func (suite *DiscoveryIntgSuite) TestGetUserInfo_userWithoutDrive() { + userID := tester.M365UserID(suite.T()) + + table := []struct { + name string + user string + expect *api.UserInfo + }{ + { + name: "user without drive and exchange", + user: "a53c26f7-5100-4acb-a910-4d20960b2c19", // User: testevents@10rqc2.onmicrosoft.com + expect: &api.UserInfo{ + ServicesEnabled: map[path.ServiceType]struct{}{}, + Mailbox: api.MailboxInfo{ + ErrGetMailBoxSetting: []error{api.ErrMailBoxSettingsNotFound}, + }, + }, + }, + { + name: "user with drive and exchange", + user: userID, + expect: &api.UserInfo{ + ServicesEnabled: map[path.ServiceType]struct{}{ + path.ExchangeService: {}, + path.OneDriveService: {}, + }, + Mailbox: api.MailboxInfo{ + Purpose: "user", + ErrGetMailBoxSetting: []error{}, + }, + }, + }, + } + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + defer flush() + + result, err := GetUserInfo(ctx, suite.acct, test.user) + require.NoError(t, err, clues.ToCore(err)) + assert.Equal(t, test.expect.ServicesEnabled, result.ServicesEnabled) + assert.Equal(t, test.expect.Mailbox.ErrGetMailBoxSetting, result.Mailbox.ErrGetMailBoxSetting) + assert.Equal(t, test.expect.Mailbox.Purpose, result.Mailbox.Purpose) + }) + } +}