diff --git a/src/internal/m365/graph/errors.go b/src/internal/m365/graph/errors.go index 1f2ba98c0..5c7d61dfb 100644 --- a/src/internal/m365/graph/errors.go +++ b/src/internal/m365/graph/errors.go @@ -34,7 +34,7 @@ const ( // failed for an attachment." cannotOpenFileAttachment errorCode = "ErrorCannotOpenFileAttachment" emailFolderNotFound errorCode = "ErrorSyncFolderNotFound" - errorAccessDenied errorCode = "ErrorAccessDenied" + 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 @@ -49,7 +49,7 @@ const ( // nameAlreadyExists occurs when a request with // @microsoft.graph.conflictBehavior=fail finds a conflicting file. nameAlreadyExists errorCode = "nameAlreadyExists" - quotaExceeded errorCode = "ErrorQuotaExceeded" + 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" @@ -137,7 +137,7 @@ func IsErrInvalidDelta(err error) bool { } func IsErrQuotaExceeded(err error) bool { - return hasErrorCode(err, quotaExceeded) + return hasErrorCode(err, QuotaExceeded) } func IsErrExchangeMailFolderNotFound(err error) bool { @@ -170,7 +170,7 @@ func IsErrCannotOpenFileAttachment(err error) bool { } func IsErrAccessDenied(err error) bool { - return hasErrorCode(err, errorAccessDenied) || clues.HasLabel(err, LabelStatus(http.StatusForbidden)) + return hasErrorCode(err, ErrorAccessDenied) || clues.HasLabel(err, LabelStatus(http.StatusForbidden)) } func IsErrTimeout(err error) bool { diff --git a/src/internal/m365/service/exchange/enabled.go b/src/internal/m365/service/exchange/enabled.go new file mode 100644 index 000000000..d7a330a28 --- /dev/null +++ b/src/internal/m365/service/exchange/enabled.go @@ -0,0 +1,100 @@ +package exchange + +import ( + "context" + + "github.com/alcionai/clues" + "github.com/microsoftgraph/msgraph-sdk-go/models" + + "github.com/alcionai/corso/src/internal/common/ptr" + "github.com/alcionai/corso/src/internal/m365/graph" + "github.com/alcionai/corso/src/pkg/logger" + "github.com/alcionai/corso/src/pkg/services/m365/api" +) + +type getMailInboxer interface { + GetMailInbox(ctx context.Context, userID string) (models.MailFolderable, error) +} + +func IsServiceEnabled( + ctx context.Context, + gmi getMailInboxer, + resource string, +) (bool, error) { + _, err := gmi.GetMailInbox(ctx, resource) + if err != nil { + if err := api.EvaluateMailboxError(err); err != nil { + logger.CtxErr(ctx, err).Error("getting user's mail folder") + return false, clues.Stack(err) + } + + logger.Ctx(ctx).Info("resource owner does not have a mailbox enabled") + + return false, nil + } + + return true, nil +} + +type getMailboxer interface { + GetMailInbox(ctx context.Context, userID string) (models.MailFolderable, error) + GetMailboxSettings(ctx context.Context, userID string) (models.Userable, error) + GetFirstInboxMessage(ctx context.Context, userID, inboxID string) error +} + +func GetMailboxInfo( + ctx context.Context, + gmb getMailboxer, + userID string, +) (api.MailboxInfo, error) { + mi := api.MailboxInfo{ + ErrGetMailBoxSetting: []error{}, + } + + // First check whether the user is able to access their inbox. + inbox, err := gmb.GetMailInbox(ctx, userID) + if err != nil { + if err := api.EvaluateMailboxError(graph.Stack(ctx, err)); err != nil { + logger.CtxErr(ctx, err).Error("getting user's mail folder") + + return mi, err + } + + logger.Ctx(ctx).Info("resource owner does not have a mailbox enabled") + + mi.ErrGetMailBoxSetting = append( + mi.ErrGetMailBoxSetting, + api.ErrMailBoxSettingsNotFound) + + return mi, nil + } + + mboxSettings, err := gmb.GetMailboxSettings(ctx, userID) + if err != nil { + logger.CtxErr(ctx, err).Info("err getting user's mailbox settings") + + if !graph.IsErrAccessDenied(err) { + return mi, graph.Wrap(ctx, err, "getting user's mailbox settings") + } + + logger.CtxErr(ctx, err).Info("mailbox settings access denied") + + mi.ErrGetMailBoxSetting = append( + mi.ErrGetMailBoxSetting, + api.ErrMailBoxSettingsAccessDenied, + ) + } else { + mi = api.ParseMailboxSettings(mboxSettings, mi) + } + + err = gmb.GetFirstInboxMessage(ctx, userID, ptr.Val(inbox.GetId())) + if err != nil { + if !graph.IsErrQuotaExceeded(err) { + return mi, clues.Stack(err) + } + + mi.QuotaExceeded = graph.IsErrQuotaExceeded(err) + } + + return mi, nil +} diff --git a/src/internal/m365/service/exchange/enabled_test.go b/src/internal/m365/service/exchange/enabled_test.go new file mode 100644 index 000000000..279637733 --- /dev/null +++ b/src/internal/m365/service/exchange/enabled_test.go @@ -0,0 +1,271 @@ +package exchange + +import ( + "context" + "testing" + + "github.com/alcionai/clues" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + "github.com/alcionai/corso/src/internal/m365/graph" + "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/pkg/services/m365/api" + "github.com/alcionai/corso/src/pkg/services/m365/api/mock" +) + +type EnabledUnitSuite struct { + tester.Suite +} + +func TestEnabledUnitSuite(t *testing.T) { + suite.Run(t, &EnabledUnitSuite{Suite: tester.NewUnitSuite(t)}) +} + +var _ getMailboxer = mockGMB{} + +type mockGMB struct { + mailbox models.MailFolderable + mailboxErr error + settings models.Userable + settingsErr error + inboxMessageErr error +} + +func (m mockGMB) GetMailInbox(context.Context, string) (models.MailFolderable, error) { + return m.mailbox, m.mailboxErr +} + +func (m mockGMB) GetMailboxSettings(context.Context, string) (models.Userable, error) { + return m.settings, m.settingsErr +} + +func (m mockGMB) GetFirstInboxMessage(context.Context, string, string) error { + return m.inboxMessageErr +} + +// TODO(pandeyabs): Duplicate of graph/errors_test.go. Remove +// this and identical funcs in od/sp and use the one in graph/errors_test.go +// instead. +func odErrMsg(code, message string) *odataerrors.ODataError { + odErr := odataerrors.NewODataError() + merr := odataerrors.NewMainError() + merr.SetCode(&code) + merr.SetMessage(&message) + odErr.SetErrorEscaped(merr) + + return odErr +} + +func (suite *EnabledUnitSuite) TestIsServiceEnabled() { + table := []struct { + name string + mock func(context.Context) getMailInboxer + expect assert.BoolAssertionFunc + expectErr func(*testing.T, error) + }{ + { + name: "ok", + mock: func(ctx context.Context) getMailInboxer { + return mockGMB{ + mailbox: models.NewMailFolder(), + } + }, + expect: assert.True, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + }, + { + name: "user has no mailbox", + mock: func(ctx context.Context) getMailInboxer { + odErr := odErrMsg(string(graph.ResourceNotFound), "message") + + return mockGMB{ + mailboxErr: graph.Stack(ctx, odErr), + } + }, + expect: assert.False, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + }, + { + name: "user not found", + mock: func(ctx context.Context) getMailInboxer { + odErr := odErrMsg(string(graph.RequestResourceNotFound), "message") + + return mockGMB{ + mailboxErr: graph.Stack(ctx, odErr), + } + }, + expect: assert.False, + expectErr: func(t *testing.T, err error) { + assert.Error(t, err, clues.ToCore(err)) + }, + }, + { + name: "overlapping resourcenotfound", + mock: func(ctx context.Context) getMailInboxer { + odErr := odErrMsg(string(graph.ResourceNotFound), "User not found") + + return mockGMB{ + mailboxErr: graph.Stack(ctx, odErr), + } + }, + expect: assert.False, + expectErr: func(t *testing.T, err error) { + assert.Error(t, err, clues.ToCore(err)) + }, + }, + { + name: "arbitrary error", + mock: func(ctx context.Context) getMailInboxer { + odErr := odErrMsg("code", "message") + + return mockGMB{ + mailboxErr: graph.Stack(ctx, odErr), + } + }, + expect: assert.False, + expectErr: func(t *testing.T, err error) { + assert.Error(t, err, clues.ToCore(err)) + }, + }, + } + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + defer flush() + + gmi := test.mock(ctx) + + ok, err := IsServiceEnabled(ctx, gmi, "resource_id") + test.expect(t, ok, "has mailbox flag") + test.expectErr(t, err) + }) + } +} + +func (suite *EnabledUnitSuite) TestGetMailboxInfo() { + table := []struct { + name string + mock func(context.Context) getMailboxer + expectErr func(*testing.T, error) + expect func(*testing.T) api.MailboxInfo + }{ + { + name: "ok", + mock: func(ctx context.Context) getMailboxer { + return mockGMB{ + mailbox: models.NewMailFolder(), + settings: mock.UserSettings(), + } + }, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + expect: func(t *testing.T) api.MailboxInfo { + return mock.UserMailboxInfo() + }, + }, + { + name: "user has no mailbox", + mock: func(ctx context.Context) getMailboxer { + err := odErrMsg(string(graph.ResourceNotFound), "message") + + return mockGMB{ + mailboxErr: graph.Stack(ctx, err), + } + }, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + expect: func(t *testing.T) api.MailboxInfo { + mi := api.MailboxInfo{} + mi.ErrGetMailBoxSetting = append( + mi.ErrGetMailBoxSetting, + api.ErrMailBoxSettingsNotFound) + + return mi + }, + }, + { + name: "settings access denied", + mock: func(ctx context.Context) getMailboxer { + err := odErrMsg(string(graph.ErrorAccessDenied), "message") + + return mockGMB{ + mailbox: models.NewMailFolder(), + settingsErr: err, + } + }, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + expect: func(t *testing.T) api.MailboxInfo { + mi := api.MailboxInfo{} + mi.ErrGetMailBoxSetting = append( + mi.ErrGetMailBoxSetting, + api.ErrMailBoxSettingsAccessDenied) + + return mi + }, + }, + { + name: "error fetching settings", + mock: func(ctx context.Context) getMailboxer { + return mockGMB{ + mailbox: models.NewMailFolder(), + settingsErr: assert.AnError, + } + }, + expectErr: func(t *testing.T, err error) { + assert.Error(t, err, clues.ToCore(err)) + }, + expect: func(t *testing.T) api.MailboxInfo { + return api.MailboxInfo{ + ErrGetMailBoxSetting: []error{}, + } + }, + }, + { + name: "mailbox quota exceeded", + mock: func(ctx context.Context) getMailboxer { + err := odErrMsg(string(graph.QuotaExceeded), "message") + return mockGMB{ + mailbox: models.NewMailFolder(), + settings: mock.UserSettings(), + inboxMessageErr: graph.Stack(ctx, err), + } + }, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + expect: func(t *testing.T) api.MailboxInfo { + mi := mock.UserMailboxInfo() + mi.QuotaExceeded = true + + return mi + }, + }, + } + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + defer flush() + + gmi := test.mock(ctx) + + mi, err := GetMailboxInfo(ctx, gmi, "resource_id") + test.expectErr(t, err) + assert.Equal(t, test.expect(t), mi) + }) + } +} diff --git a/src/internal/m365/service/onedrive/enabled.go b/src/internal/m365/service/onedrive/enabled.go new file mode 100644 index 000000000..cd29c8870 --- /dev/null +++ b/src/internal/m365/service/onedrive/enabled.go @@ -0,0 +1,37 @@ +package onedrive + +import ( + "context" + + "github.com/alcionai/clues" + "github.com/microsoftgraph/msgraph-sdk-go/models" + + "github.com/alcionai/corso/src/internal/m365/graph" +) + +type getDefaultDriver interface { + GetDefaultDrive(ctx context.Context, userID string) (models.Driveable, error) +} + +func IsServiceEnabled( + ctx context.Context, + gdd getDefaultDriver, + resource string, +) (bool, error) { + _, err := gdd.GetDefaultDrive(ctx, resource) + if err != nil { + // we consider this a non-error case, since it + // answers the question the caller is asking. + if clues.HasLabel(err, graph.LabelsMysiteNotFound) || clues.HasLabel(err, graph.LabelsNoSharePointLicense) { + return false, nil + } + + if graph.IsErrUserNotFound(err) { + return false, clues.Stack(graph.ErrResourceOwnerNotFound, err) + } + + return false, clues.Stack(err) + } + + return true, nil +} diff --git a/src/internal/m365/service/onedrive/enabled_test.go b/src/internal/m365/service/onedrive/enabled_test.go new file mode 100644 index 000000000..4ce77c3aa --- /dev/null +++ b/src/internal/m365/service/onedrive/enabled_test.go @@ -0,0 +1,134 @@ +package onedrive + +import ( + "context" + "testing" + + "github.com/alcionai/clues" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + "github.com/alcionai/corso/src/internal/m365/graph" + "github.com/alcionai/corso/src/internal/tester" +) + +type EnabledUnitSuite struct { + tester.Suite +} + +func TestEnabledUnitSuite(t *testing.T) { + suite.Run(t, &EnabledUnitSuite{Suite: tester.NewUnitSuite(t)}) +} + +var _ getDefaultDriver = mockDGDD{} + +type mockDGDD struct { + response models.Driveable + err error +} + +func (m mockDGDD) GetDefaultDrive(context.Context, string) (models.Driveable, error) { + return m.response, m.err +} + +// Copied from src/internal/m365/graph/errors_test.go +func odErrMsg(code, message string) *odataerrors.ODataError { + odErr := odataerrors.NewODataError() + merr := odataerrors.NewMainError() + merr.SetCode(&code) + merr.SetMessage(&message) + odErr.SetErrorEscaped(merr) + + return odErr +} + +func (suite *EnabledUnitSuite) TestIsServiceEnabled() { + table := []struct { + name string + mock func(context.Context) getDefaultDriver + expect assert.BoolAssertionFunc + expectErr func(*testing.T, error) + }{ + { + name: "ok", + mock: func(ctx context.Context) getDefaultDriver { + return mockDGDD{models.NewDrive(), nil} + }, + expect: assert.True, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + }, + { + name: "mysite not found", + mock: func(ctx context.Context) getDefaultDriver { + odErr := odErrMsg("code", string(graph.MysiteNotFound)) + return mockDGDD{nil, graph.Stack(ctx, odErr)} + }, + expect: assert.False, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + }, + { + name: "mysite URL not found", + mock: func(ctx context.Context) getDefaultDriver { + odErr := odErrMsg("code", string(graph.MysiteURLNotFound)) + return mockDGDD{nil, graph.Stack(ctx, odErr)} + }, + expect: assert.False, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + }, + { + name: "no sharepoint license", + mock: func(ctx context.Context) getDefaultDriver { + odErr := odErrMsg("code", string(graph.NoSPLicense)) + return mockDGDD{nil, graph.Stack(ctx, odErr)} + }, + expect: assert.False, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + }, + { + name: "user not found", + mock: func(ctx context.Context) getDefaultDriver { + odErr := odErrMsg(string(graph.RequestResourceNotFound), "message") + return mockDGDD{nil, graph.Stack(ctx, odErr)} + }, + expect: assert.False, + expectErr: func(t *testing.T, err error) { + assert.Error(t, err, clues.ToCore(err)) + }, + }, + { + name: "arbitrary error", + mock: func(ctx context.Context) getDefaultDriver { + odErr := odErrMsg("code", "message") + return mockDGDD{nil, graph.Stack(ctx, odErr)} + }, + expect: assert.False, + expectErr: func(t *testing.T, err error) { + assert.Error(t, err, clues.ToCore(err)) + }, + }, + } + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + defer flush() + + dgdd := test.mock(ctx) + + ok, err := IsServiceEnabled(ctx, dgdd, "resource_id") + test.expect(t, ok, "has drives flag") + test.expectErr(t, err) + }) + } +} diff --git a/src/internal/m365/service/sharepoint/enabled.go b/src/internal/m365/service/sharepoint/enabled.go new file mode 100644 index 000000000..d816cad7f --- /dev/null +++ b/src/internal/m365/service/sharepoint/enabled.go @@ -0,0 +1,31 @@ +package sharepoint + +import ( + "context" + + "github.com/alcionai/clues" + "github.com/microsoftgraph/msgraph-sdk-go/models" + + "github.com/alcionai/corso/src/internal/m365/graph" +) + +type getSiteRooter interface { + GetRoot(ctx context.Context) (models.Siteable, error) +} + +func IsServiceEnabled( + ctx context.Context, + gsr getSiteRooter, + resource string, +) (bool, error) { + _, err := gsr.GetRoot(ctx) + if err != nil { + if clues.HasLabel(err, graph.LabelsNoSharePointLicense) { + return false, nil + } + + return false, clues.Stack(err) + } + + return true, nil +} diff --git a/src/internal/m365/service/sharepoint/enabled_test.go b/src/internal/m365/service/sharepoint/enabled_test.go new file mode 100644 index 000000000..af9fe01fb --- /dev/null +++ b/src/internal/m365/service/sharepoint/enabled_test.go @@ -0,0 +1,102 @@ +package sharepoint + +import ( + "context" + "testing" + + "github.com/alcionai/clues" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + "github.com/alcionai/corso/src/internal/m365/graph" + "github.com/alcionai/corso/src/internal/tester" +) + +type EnabledUnitSuite struct { + tester.Suite +} + +func TestEnabledUnitSuite(t *testing.T) { + suite.Run(t, &EnabledUnitSuite{Suite: tester.NewUnitSuite(t)}) +} + +var _ getSiteRooter = mockGSR{} + +type mockGSR struct { + response models.Siteable + err error +} + +func (m mockGSR) GetRoot(context.Context) (models.Siteable, error) { + return m.response, m.err +} + +func odErrMsg(code, message string) *odataerrors.ODataError { + odErr := odataerrors.NewODataError() + merr := odataerrors.NewMainError() + merr.SetCode(&code) + merr.SetMessage(&message) + odErr.SetErrorEscaped(merr) + + return odErr +} + +func (suite *EnabledUnitSuite) TestIsServiceEnabled() { + table := []struct { + name string + mock func(context.Context) getSiteRooter + expect assert.BoolAssertionFunc + expectErr func(*testing.T, error) + }{ + { + name: "ok", + mock: func(ctx context.Context) getSiteRooter { + return mockGSR{models.NewSite(), nil} + }, + expect: assert.True, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + }, + { + name: "no sharepoint license", + mock: func(ctx context.Context) getSiteRooter { + odErr := odErrMsg("code", string(graph.NoSPLicense)) + + return mockGSR{nil, graph.Stack(ctx, odErr)} + }, + expect: assert.False, + expectErr: func(t *testing.T, err error) { + assert.NoError(t, err, clues.ToCore(err)) + }, + }, + { + name: "arbitrary error", + mock: func(ctx context.Context) getSiteRooter { + odErr := odErrMsg("code", "message") + + return mockGSR{nil, graph.Stack(ctx, odErr)} + }, + expect: assert.False, + expectErr: func(t *testing.T, err error) { + assert.Error(t, err, clues.ToCore(err)) + }, + }, + } + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + defer flush() + + gsr := test.mock(ctx) + + ok, err := IsServiceEnabled(ctx, gsr, "resource_id") + test.expect(t, ok, "has sites flag") + test.expectErr(t, err) + }) + } +} diff --git a/src/pkg/services/m365/api/mock/user_info.go b/src/pkg/services/m365/api/mock/user_info.go new file mode 100644 index 000000000..44b15ae47 --- /dev/null +++ b/src/pkg/services/m365/api/mock/user_info.go @@ -0,0 +1,90 @@ +package mock + +import ( + "github.com/microsoftgraph/msgraph-sdk-go/models" + + "github.com/alcionai/corso/src/pkg/services/m365/api" +) + +func UserSettings() models.Userable { + u := models.NewUser() + + u.SetAdditionalData( + map[string]any{ + "archiveFolder": "archive", + "timeZone": "UTC", + "dateFormat": "MM/dd/yyyy", + "timeFormat": "hh:mm tt", + "userPurpose": "user", + "delegateMeetingMessageDeliveryOptions": "test", + "automaticRepliesSetting": map[string]any{ + "status": "foo", + "externalAudience": "bar", + "externalReplyMessage": "baz", + "internalReplyMessage": "qux", + "scheduledStartDateTime": map[string]any{ + "dateTime": "2020-01-01T00:00:00Z", + "timeZone": "UTC", + }, + "scheduledEndDateTime": map[string]any{ + "dateTime": "2020-01-01T00:00:00Z", + "timeZone": "UTC", + }, + }, + "language": map[string]any{ + "displayName": "en-US", + "locale": "US", + }, + "workingHours": map[string]any{ + "daysOfWeek": []any{"monday"}, + "startTime": "08:00:00.0000000", + "endTime": "17:00:00.0000000", + "timeZone": map[string]any{ + "name": "UTC", + }, + }, + }) + + return u +} + +func UserMailboxInfo() api.MailboxInfo { + return api.MailboxInfo{ + Purpose: "user", + ArchiveFolder: "archive", + DateFormat: "MM/dd/yyyy", + TimeFormat: "hh:mm tt", + DelegateMeetMsgDeliveryOpt: "test", + Timezone: "UTC", + AutomaticRepliesSetting: api.AutomaticRepliesSettings{ + Status: "foo", + ExternalAudience: "bar", + ExternalReplyMessage: "baz", + InternalReplyMessage: "qux", + ScheduledStartDateTime: api.TimeInfo{ + DateTime: "2020-01-01T00:00:00Z", + Timezone: "UTC", + }, + ScheduledEndDateTime: api.TimeInfo{ + DateTime: "2020-01-01T00:00:00Z", + Timezone: "UTC", + }, + }, + Language: api.Language{ + DisplayName: "en-US", + Locale: "US", + }, + WorkingHours: api.WorkingHours{ + DaysOfWeek: []string{"monday"}, + StartTime: "08:00:00.0000000", + EndTime: "17:00:00.0000000", + TimeZone: struct { + Name string + }{ + Name: "UTC", + }, + }, + + ErrGetMailBoxSetting: []error{}, + } +} diff --git a/src/pkg/services/m365/api/user_info.go b/src/pkg/services/m365/api/user_info.go index f08c640c4..0b24d2f3d 100644 --- a/src/pkg/services/m365/api/user_info.go +++ b/src/pkg/services/m365/api/user_info.go @@ -35,12 +35,12 @@ type AutomaticRepliesSettings struct { ExternalAudience string ExternalReplyMessage string InternalReplyMessage string - ScheduledEndDateTime timeInfo - ScheduledStartDateTime timeInfo + ScheduledEndDateTime TimeInfo + ScheduledStartDateTime TimeInfo Status string } -type timeInfo struct { +type TimeInfo struct { DateTime string Timezone string } @@ -86,7 +86,7 @@ func (ui *UserInfo) CanMakeDeltaQueries() bool { return !ui.Mailbox.QuotaExceeded } -func parseMailboxSettings( +func ParseMailboxSettings( settings models.Userable, mi MailboxInfo, ) MailboxInfo { diff --git a/src/pkg/services/m365/api/users.go b/src/pkg/services/m365/api/users.go index 590eb7a70..efda53a40 100644 --- a/src/pkg/services/m365/api/users.go +++ b/src/pkg/services/m365/api/users.go @@ -20,7 +20,8 @@ import ( // Variables var ( - ErrMailBoxSettingsNotFound = clues.New("mailbox settings not found") + ErrMailBoxSettingsNotFound = clues.New("mailbox settings not found") + ErrMailBoxSettingsAccessDenied = clues.New("mailbox settings access denied") ) // --------------------------------------------------------------------------- @@ -219,7 +220,7 @@ func (c Users) GetInfo(ctx context.Context, userID string) (*UserInfo, error) { return userInfo, nil } - mboxSettings, err := c.getMailboxSettings(ctx, userID) + mboxSettings, err := c.GetMailboxSettings(ctx, userID) if err != nil { logger.CtxErr(ctx, err).Info("err getting user's mailbox settings") @@ -229,10 +230,10 @@ func (c Users) GetInfo(ctx context.Context, userID string) (*UserInfo, error) { mi.ErrGetMailBoxSetting = append(mi.ErrGetMailBoxSetting, clues.New("access denied")) } else { - mi = parseMailboxSettings(mboxSettings, mi) + mi = ParseMailboxSettings(mboxSettings, mi) } - err = c.getFirstInboxMessage(ctx, userID, ptr.Val(inbx.GetId())) + err = c.GetFirstInboxMessage(ctx, userID, ptr.Val(inbx.GetId())) if err != nil { if !graph.IsErrQuotaExceeded(err) { return nil, clues.Stack(err) @@ -266,7 +267,7 @@ func EvaluateMailboxError(err error) error { return err } -func (c Users) getMailboxSettings( +func (c Users) GetMailboxSettings( ctx context.Context, userID string, ) (models.Userable, error) { @@ -323,7 +324,7 @@ func (c Users) GetDefaultDrive( // exceeded error. Ideally(if available) we should convert this to // pull the user's usage via an api and compare if they have used // up their quota. -func (c Users) getFirstInboxMessage( +func (c Users) GetFirstInboxMessage( ctx context.Context, userID, inboxID string, ) error {