Add common code to check if services are enabled (#4127)
<!-- PR description-->
Currently corso CLI & SDK have different checks for services enabled. These checks have diverged which can lead to bugs. For e.g. SDK users use [code](3e43028a88/src/pkg/services/m365/users.go (L92)) to check if OD is enabled, while corso uses [this code](https://github.com/alcionai/corso/blob/main/src/pkg/services/m365/api/users.go#L174). These funcs have different checks. This PR introduces common helpers to consolidate these checks in one place.
- Note: I decided against absorbing these helpers into api/users.go, because separation of concerns - getters shouldn't arbitrate on return vals.
- Note that this code is not yet wired up to SDK/CLI. It'll be added in a following [PR](https://github.com/alcionai/corso/pull/4096).
**Changes**
1. `Is*ServiceEnabled` helpers in internal/m365/common.go.
2. Add `GetMailboxInfo` to common code. This is currently a duplicate of code in `GetInfo`. That function will be removed once we port SDK users to `GetMailboxInfo`.
3. Unit tests for common code. Integration tests will be added in a later PR
- Note:`TestIsOneDriveServiceEnabled` is copied from `TestCheckUserHasDrives`. The older test will be removed in a later PR.
---
#### Does this PR need a docs update or release note?
- [ ] ✅ Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x] ⛔ No
#### Type of change
<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [x] 🧹 Tech Debt/Cleanup
#### Issue(s)
<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* https://github.com/alcionai/corso/issues/3844
#### Test Plan
<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [x] ⚡ Unit test
- [ ] 💚 E2E
This commit is contained in:
parent
b7598d1ebe
commit
c1c4218994
@ -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 {
|
||||
|
||||
100
src/internal/m365/service/exchange/enabled.go
Normal file
100
src/internal/m365/service/exchange/enabled.go
Normal file
@ -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
|
||||
}
|
||||
271
src/internal/m365/service/exchange/enabled_test.go
Normal file
271
src/internal/m365/service/exchange/enabled_test.go
Normal file
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
37
src/internal/m365/service/onedrive/enabled.go
Normal file
37
src/internal/m365/service/onedrive/enabled.go
Normal file
@ -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
|
||||
}
|
||||
134
src/internal/m365/service/onedrive/enabled_test.go
Normal file
134
src/internal/m365/service/onedrive/enabled_test.go
Normal file
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
31
src/internal/m365/service/sharepoint/enabled.go
Normal file
31
src/internal/m365/service/sharepoint/enabled.go
Normal file
@ -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
|
||||
}
|
||||
102
src/internal/m365/service/sharepoint/enabled_test.go
Normal file
102
src/internal/m365/service/sharepoint/enabled_test.go
Normal file
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
90
src/pkg/services/m365/api/mock/user_info.go
Normal file
90
src/pkg/services/m365/api/mock/user_info.go
Normal file
@ -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{},
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
// Variables
|
||||
var (
|
||||
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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user