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."
|
// failed for an attachment."
|
||||||
cannotOpenFileAttachment errorCode = "ErrorCannotOpenFileAttachment"
|
cannotOpenFileAttachment errorCode = "ErrorCannotOpenFileAttachment"
|
||||||
emailFolderNotFound errorCode = "ErrorSyncFolderNotFound"
|
emailFolderNotFound errorCode = "ErrorSyncFolderNotFound"
|
||||||
errorAccessDenied errorCode = "ErrorAccessDenied"
|
ErrorAccessDenied errorCode = "ErrorAccessDenied"
|
||||||
errorItemNotFound errorCode = "ErrorItemNotFound"
|
errorItemNotFound errorCode = "ErrorItemNotFound"
|
||||||
// This error occurs when an attempt is made to create a folder that has
|
// 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
|
// the same name as another folder in the same parent. Such duplicate folder
|
||||||
@ -49,7 +49,7 @@ const (
|
|||||||
// nameAlreadyExists occurs when a request with
|
// nameAlreadyExists occurs when a request with
|
||||||
// @microsoft.graph.conflictBehavior=fail finds a conflicting file.
|
// @microsoft.graph.conflictBehavior=fail finds a conflicting file.
|
||||||
nameAlreadyExists errorCode = "nameAlreadyExists"
|
nameAlreadyExists errorCode = "nameAlreadyExists"
|
||||||
quotaExceeded errorCode = "ErrorQuotaExceeded"
|
QuotaExceeded errorCode = "ErrorQuotaExceeded"
|
||||||
RequestResourceNotFound errorCode = "Request_ResourceNotFound"
|
RequestResourceNotFound errorCode = "Request_ResourceNotFound"
|
||||||
// Returned when we try to get the inbox of a user that doesn't exist.
|
// Returned when we try to get the inbox of a user that doesn't exist.
|
||||||
ResourceNotFound errorCode = "ResourceNotFound"
|
ResourceNotFound errorCode = "ResourceNotFound"
|
||||||
@ -137,7 +137,7 @@ func IsErrInvalidDelta(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsErrQuotaExceeded(err error) bool {
|
func IsErrQuotaExceeded(err error) bool {
|
||||||
return hasErrorCode(err, quotaExceeded)
|
return hasErrorCode(err, QuotaExceeded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrExchangeMailFolderNotFound(err error) bool {
|
func IsErrExchangeMailFolderNotFound(err error) bool {
|
||||||
@ -170,7 +170,7 @@ func IsErrCannotOpenFileAttachment(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsErrAccessDenied(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 {
|
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
|
ExternalAudience string
|
||||||
ExternalReplyMessage string
|
ExternalReplyMessage string
|
||||||
InternalReplyMessage string
|
InternalReplyMessage string
|
||||||
ScheduledEndDateTime timeInfo
|
ScheduledEndDateTime TimeInfo
|
||||||
ScheduledStartDateTime timeInfo
|
ScheduledStartDateTime TimeInfo
|
||||||
Status string
|
Status string
|
||||||
}
|
}
|
||||||
|
|
||||||
type timeInfo struct {
|
type TimeInfo struct {
|
||||||
DateTime string
|
DateTime string
|
||||||
Timezone string
|
Timezone string
|
||||||
}
|
}
|
||||||
@ -86,7 +86,7 @@ func (ui *UserInfo) CanMakeDeltaQueries() bool {
|
|||||||
return !ui.Mailbox.QuotaExceeded
|
return !ui.Mailbox.QuotaExceeded
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMailboxSettings(
|
func ParseMailboxSettings(
|
||||||
settings models.Userable,
|
settings models.Userable,
|
||||||
mi MailboxInfo,
|
mi MailboxInfo,
|
||||||
) MailboxInfo {
|
) MailboxInfo {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import (
|
|||||||
// Variables
|
// Variables
|
||||||
var (
|
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
|
return userInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
mboxSettings, err := c.getMailboxSettings(ctx, userID)
|
mboxSettings, err := c.GetMailboxSettings(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.CtxErr(ctx, err).Info("err getting user's mailbox settings")
|
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"))
|
mi.ErrGetMailBoxSetting = append(mi.ErrGetMailBoxSetting, clues.New("access denied"))
|
||||||
} else {
|
} 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 err != nil {
|
||||||
if !graph.IsErrQuotaExceeded(err) {
|
if !graph.IsErrQuotaExceeded(err) {
|
||||||
return nil, clues.Stack(err)
|
return nil, clues.Stack(err)
|
||||||
@ -266,7 +267,7 @@ func EvaluateMailboxError(err error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Users) getMailboxSettings(
|
func (c Users) GetMailboxSettings(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID string,
|
userID string,
|
||||||
) (models.Userable, error) {
|
) (models.Userable, error) {
|
||||||
@ -323,7 +324,7 @@ func (c Users) GetDefaultDrive(
|
|||||||
// exceeded error. Ideally(if available) we should convert this to
|
// exceeded error. Ideally(if available) we should convert this to
|
||||||
// pull the user's usage via an api and compare if they have used
|
// pull the user's usage via an api and compare if they have used
|
||||||
// up their quota.
|
// up their quota.
|
||||||
func (c Users) getFirstInboxMessage(
|
func (c Users) GetFirstInboxMessage(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID, inboxID string,
|
userID, inboxID string,
|
||||||
) error {
|
) error {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user