clean up unused graph error sentinels (#4683)

A handful of the error sentinel values in graph/errors.go were only around to comply with an old expectation that IsErr funcs have a matching sentinel.  That assumption is out of date, and there's no point in keeping around unused sentinels.

This PR makes three changes to clean up that issue:
1. remove unused sentinels (unused is qualifed as "not produced").
2. fix external pacakges which referenced those errors in tests alone by replacing those sentinels with odata errors.
3. centralize odata error test code to help support upstream package odata error creation.

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🧹 Tech Debt/Cleanup

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-11-16 16:59:38 -07:00 committed by GitHub
parent 9ecefd2569
commit 2a85b61213
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 185 additions and 328 deletions

View File

@ -6,7 +6,6 @@ import (
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
@ -22,6 +21,7 @@ import (
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
"github.com/alcionai/corso/src/pkg/services/m365/api" "github.com/alcionai/corso/src/pkg/services/m365/api"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "github.com/alcionai/corso/src/pkg/services/m365/api/graph"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
"github.com/alcionai/corso/src/pkg/services/m365/api/mock" "github.com/alcionai/corso/src/pkg/services/m365/api/mock"
) )
@ -38,15 +38,6 @@ const (
userMysiteNotFound = "ResourceNotFound User's mysite not found" userMysiteNotFound = "ResourceNotFound User's mysite not found"
) )
func odErr(code string) *odataerrors.ODataError {
odErr := odataerrors.NewODataError()
merr := odataerrors.NewMainError()
merr.SetCode(&code)
odErr.SetErrorEscaped(merr)
return odErr
}
func (suite *ItemCollectorUnitSuite) TestDrives() { func (suite *ItemCollectorUnitSuite) TestDrives() {
t := suite.T() t := suite.T()
@ -60,8 +51,8 @@ func (suite *ItemCollectorUnitSuite) TestDrives() {
// These errors won't be the "correct" format when compared to what graph // These errors won't be the "correct" format when compared to what graph
// returns, but they're close enough to have the same info when the inner // returns, but they're close enough to have the same info when the inner
// details are extracted via support package. // details are extracted via support package.
mySiteURLNotFound := odErr(userMysiteURLNotFound) mySiteURLNotFound := graphTD.ODataErr(userMysiteURLNotFound)
mySiteNotFound := odErr(userMysiteNotFound) mySiteNotFound := graphTD.ODataErr(userMysiteNotFound)
resultDrives := make([]models.Driveable, 0, numDriveResults) resultDrives := make([]models.Driveable, 0, numDriveResults)

View File

@ -6,13 +6,13 @@ import (
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/tester" "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"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "github.com/alcionai/corso/src/pkg/services/m365/api/graph"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
"github.com/alcionai/corso/src/pkg/services/m365/api/mock" "github.com/alcionai/corso/src/pkg/services/m365/api/mock"
) )
@ -46,19 +46,6 @@ func (m mockGMB) GetFirstInboxMessage(context.Context, string, string) error {
return m.inboxMessageErr 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() { func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
table := []struct { table := []struct {
name string name string
@ -79,7 +66,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "user has no mailbox", name: "user has no mailbox",
mock: func(ctx context.Context) getMailInboxer { mock: func(ctx context.Context) getMailInboxer {
odErr := odErrMsg(string(graph.ResourceNotFound), "message") odErr := graphTD.ODataErrWithMsg(string(graph.ResourceNotFound), "message")
return mockGMB{ return mockGMB{
mailboxErr: graph.Stack(ctx, odErr), mailboxErr: graph.Stack(ctx, odErr),
@ -91,7 +78,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "user not found", name: "user not found",
mock: func(ctx context.Context) getMailInboxer { mock: func(ctx context.Context) getMailInboxer {
odErr := odErrMsg(string(graph.RequestResourceNotFound), "message") odErr := graphTD.ODataErrWithMsg(string(graph.RequestResourceNotFound), "message")
return mockGMB{ return mockGMB{
mailboxErr: graph.Stack(ctx, odErr), mailboxErr: graph.Stack(ctx, odErr),
@ -103,7 +90,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "overlapping resourcenotfound", name: "overlapping resourcenotfound",
mock: func(ctx context.Context) getMailInboxer { mock: func(ctx context.Context) getMailInboxer {
odErr := odErrMsg(string(graph.ResourceNotFound), "User not found") odErr := graphTD.ODataErrWithMsg(string(graph.ResourceNotFound), "User not found")
return mockGMB{ return mockGMB{
mailboxErr: graph.Stack(ctx, odErr), mailboxErr: graph.Stack(ctx, odErr),
@ -115,7 +102,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "arbitrary error", name: "arbitrary error",
mock: func(ctx context.Context) getMailInboxer { mock: func(ctx context.Context) getMailInboxer {
odErr := odErrMsg("code", "message") odErr := graphTD.ODataErrWithMsg("code", "message")
return mockGMB{ return mockGMB{
mailboxErr: graph.Stack(ctx, odErr), mailboxErr: graph.Stack(ctx, odErr),
@ -166,7 +153,7 @@ func (suite *EnabledUnitSuite) TestGetMailboxInfo() {
{ {
name: "user has no mailbox", name: "user has no mailbox",
mock: func(ctx context.Context) getMailboxer { mock: func(ctx context.Context) getMailboxer {
err := odErrMsg(string(graph.ResourceNotFound), "message") err := graphTD.ODataErrWithMsg(string(graph.ResourceNotFound), "message")
return mockGMB{ return mockGMB{
mailboxErr: graph.Stack(ctx, err), mailboxErr: graph.Stack(ctx, err),
@ -187,7 +174,7 @@ func (suite *EnabledUnitSuite) TestGetMailboxInfo() {
{ {
name: "settings access denied", name: "settings access denied",
mock: func(ctx context.Context) getMailboxer { mock: func(ctx context.Context) getMailboxer {
err := odErrMsg(string(graph.ErrorAccessDenied), "message") err := graphTD.ODataErrWithMsg(string(graph.ErrorAccessDenied), "message")
return mockGMB{ return mockGMB{
mailbox: models.NewMailFolder(), mailbox: models.NewMailFolder(),
@ -226,7 +213,7 @@ func (suite *EnabledUnitSuite) TestGetMailboxInfo() {
{ {
name: "mailbox quota exceeded", name: "mailbox quota exceeded",
mock: func(ctx context.Context) getMailboxer { mock: func(ctx context.Context) getMailboxer {
err := odErrMsg(string(graph.QuotaExceeded), "message") err := graphTD.ODataErrWithMsg(string(graph.QuotaExceeded), "message")
return mockGMB{ return mockGMB{
mailbox: models.NewMailFolder(), mailbox: models.NewMailFolder(),
settings: mock.UserSettings(), settings: mock.UserSettings(),

View File

@ -6,13 +6,13 @@ import (
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/tester" "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"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "github.com/alcionai/corso/src/pkg/services/m365/api/graph"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
type EnabledUnitSuite struct { type EnabledUnitSuite struct {
@ -38,19 +38,6 @@ func (m mockGBI) GetByID(
return m.group, m.err return m.group, m.err
} }
// 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() { func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
var ( var (
unified = models.NewGroup() unified = models.NewGroup()
@ -89,7 +76,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
name: "group not found", name: "group not found",
mock: func(ctx context.Context) api.GetByIDer[models.Groupable] { mock: func(ctx context.Context) api.GetByIDer[models.Groupable] {
return mockGBI{ return mockGBI{
err: graph.Stack(ctx, odErrMsg(string(graph.RequestResourceNotFound), "message")), err: graph.Stack(ctx, graphTD.ODataErrWithMsg(string(graph.RequestResourceNotFound), "message")),
} }
}, },
expect: assert.False, expect: assert.False,

View File

@ -6,12 +6,12 @@ import (
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "github.com/alcionai/corso/src/pkg/services/m365/api/graph"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
type EnabledUnitSuite struct { type EnabledUnitSuite struct {
@ -33,17 +33,6 @@ func (m mockDGDD) GetDefaultDrive(context.Context, string) (models.Driveable, er
return m.response, m.err return m.response, m.err
} }
// Copied from src/pkg/services/m365/api/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() { func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
table := []struct { table := []struct {
name string name string
@ -64,7 +53,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "mysite not found", name: "mysite not found",
mock: func(ctx context.Context) getDefaultDriver { mock: func(ctx context.Context) getDefaultDriver {
odErr := odErrMsg("code", string(graph.MysiteNotFound)) odErr := graphTD.ODataErrWithMsg("code", string(graph.MysiteNotFound))
return mockDGDD{nil, graph.Stack(ctx, odErr)} return mockDGDD{nil, graph.Stack(ctx, odErr)}
}, },
expect: assert.False, expect: assert.False,
@ -75,7 +64,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "mysite URL not found", name: "mysite URL not found",
mock: func(ctx context.Context) getDefaultDriver { mock: func(ctx context.Context) getDefaultDriver {
odErr := odErrMsg("code", string(graph.MysiteURLNotFound)) odErr := graphTD.ODataErrWithMsg("code", string(graph.MysiteURLNotFound))
return mockDGDD{nil, graph.Stack(ctx, odErr)} return mockDGDD{nil, graph.Stack(ctx, odErr)}
}, },
expect: assert.False, expect: assert.False,
@ -86,7 +75,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "no sharepoint license", name: "no sharepoint license",
mock: func(ctx context.Context) getDefaultDriver { mock: func(ctx context.Context) getDefaultDriver {
odErr := odErrMsg("code", string(graph.NoSPLicense)) odErr := graphTD.ODataErrWithMsg("code", string(graph.NoSPLicense))
return mockDGDD{nil, graph.Stack(ctx, odErr)} return mockDGDD{nil, graph.Stack(ctx, odErr)}
}, },
expect: assert.False, expect: assert.False,
@ -97,7 +86,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "user not found", name: "user not found",
mock: func(ctx context.Context) getDefaultDriver { mock: func(ctx context.Context) getDefaultDriver {
odErr := odErrMsg(string(graph.RequestResourceNotFound), "message") odErr := graphTD.ODataErrWithMsg(string(graph.RequestResourceNotFound), "message")
return mockDGDD{nil, graph.Stack(ctx, odErr)} return mockDGDD{nil, graph.Stack(ctx, odErr)}
}, },
expect: assert.False, expect: assert.False,
@ -108,7 +97,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "resource locked", name: "resource locked",
mock: func(ctx context.Context) getDefaultDriver { mock: func(ctx context.Context) getDefaultDriver {
odErr := odErrMsg(string(graph.NotAllowed), "resource") odErr := graphTD.ODataErrWithMsg(string(graph.NotAllowed), "resource")
return mockDGDD{nil, graph.Stack(ctx, odErr)} return mockDGDD{nil, graph.Stack(ctx, odErr)}
}, },
expect: assert.False, expect: assert.False,
@ -119,7 +108,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "arbitrary error", name: "arbitrary error",
mock: func(ctx context.Context) getDefaultDriver { mock: func(ctx context.Context) getDefaultDriver {
odErr := odErrMsg("code", "message") odErr := graphTD.ODataErrWithMsg("code", "message")
return mockDGDD{nil, graph.Stack(ctx, odErr)} return mockDGDD{nil, graph.Stack(ctx, odErr)}
}, },
expect: assert.False, expect: assert.False,

View File

@ -6,13 +6,13 @@ import (
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/tester" "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"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "github.com/alcionai/corso/src/pkg/services/m365/api/graph"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
type EnabledUnitSuite struct { type EnabledUnitSuite struct {
@ -37,16 +37,6 @@ func (m mockGSR) GetRoot(
return m.response, m.err 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() { func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
table := []struct { table := []struct {
name string name string
@ -67,7 +57,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "no sharepoint license", name: "no sharepoint license",
mock: func(ctx context.Context) getSiteRooter { mock: func(ctx context.Context) getSiteRooter {
odErr := odErrMsg("code", string(graph.NoSPLicense)) odErr := graphTD.ODataErrWithMsg("code", string(graph.NoSPLicense))
return mockGSR{nil, graph.Stack(ctx, odErr)} return mockGSR{nil, graph.Stack(ctx, odErr)}
}, },
@ -79,7 +69,7 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
{ {
name: "arbitrary error", name: "arbitrary error",
mock: func(ctx context.Context) getSiteRooter { mock: func(ctx context.Context) getSiteRooter {
odErr := odErrMsg("code", "message") odErr := graphTD.ODataErrWithMsg("code", "message")
return mockGSR{nil, graph.Stack(ctx, odErr)} return mockGSR{nil, graph.Stack(ctx, odErr)}
}, },

View File

@ -15,6 +15,7 @@ import (
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/internal/tester/tconfig" "github.com/alcionai/corso/src/internal/tester/tconfig"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
// called by the pager test, since it is already enumerating // called by the pager test, since it is already enumerating
@ -161,7 +162,7 @@ func (suite *ConversationAPIIntgSuite) TestConversations_attachmentListDownload(
"posts", "posts",
pid). pid).
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), itm)) JSON(graphTD.ParseableToMap(suite.T(), itm))
}, },
expect: assert.NoError, expect: assert.NoError,
}, },
@ -187,7 +188,7 @@ func (suite *ConversationAPIIntgSuite) TestConversations_attachmentListDownload(
"posts", "posts",
pid). pid).
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), itm)) JSON(graphTD.ParseableToMap(suite.T(), itm))
}, },
attachmentCount: 1, attachmentCount: 1,
size: 50, size: 50,
@ -217,7 +218,7 @@ func (suite *ConversationAPIIntgSuite) TestConversations_attachmentListDownload(
"posts", "posts",
pid). pid).
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), itm)) JSON(graphTD.ParseableToMap(suite.T(), itm))
}, },
attachmentCount: 5, attachmentCount: 5,
size: 1000, size: 1000,

View File

@ -18,6 +18,7 @@ import (
"github.com/alcionai/corso/src/internal/tester/tconfig" "github.com/alcionai/corso/src/internal/tester/tconfig"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control/testdata" "github.com/alcionai/corso/src/pkg/control/testdata"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
type EventsAPIUnitSuite struct { type EventsAPIUnitSuite struct {
@ -362,7 +363,7 @@ func (suite *EventsAPIIntgSuite) TestEvents_GetContainerByName_mocked() {
{ {
name: "zero", name: "zero",
results: func(t *testing.T) map[string]any { results: func(t *testing.T) map[string]any {
return requireParseableToMap(t, models.NewCalendarCollectionResponse()) return graphTD.ParseableToMap(t, models.NewCalendarCollectionResponse())
}, },
expectErr: assert.Error, expectErr: assert.Error,
}, },
@ -372,7 +373,7 @@ func (suite *EventsAPIIntgSuite) TestEvents_GetContainerByName_mocked() {
mfcr := models.NewCalendarCollectionResponse() mfcr := models.NewCalendarCollectionResponse()
mfcr.SetValue([]models.Calendarable{c}) mfcr.SetValue([]models.Calendarable{c})
return requireParseableToMap(t, mfcr) return graphTD.ParseableToMap(t, mfcr)
}, },
expectErr: assert.NoError, expectErr: assert.NoError,
}, },
@ -382,7 +383,7 @@ func (suite *EventsAPIIntgSuite) TestEvents_GetContainerByName_mocked() {
mfcr := models.NewCalendarCollectionResponse() mfcr := models.NewCalendarCollectionResponse()
mfcr.SetValue([]models.Calendarable{c, c}) mfcr.SetValue([]models.Calendarable{c, c})
return requireParseableToMap(t, mfcr) return graphTD.ParseableToMap(t, mfcr)
}, },
expectErr: assert.NoError, expectErr: assert.NoError,
}, },

View File

@ -78,7 +78,7 @@ const (
MysiteURLNotFound errorMessage = "unable to retrieve user's mysite url" MysiteURLNotFound errorMessage = "unable to retrieve user's mysite url"
MysiteNotFound errorMessage = "user's mysite not found" MysiteNotFound errorMessage = "user's mysite not found"
NoSPLicense errorMessage = "Tenant does not have a SPO license" NoSPLicense errorMessage = "Tenant does not have a SPO license"
parameterDeltaTokenNotSupported errorMessage = "Parameter 'DeltaToken' not supported for this request" ParameterDeltaTokenNotSupported errorMessage = "Parameter 'DeltaToken' not supported for this request"
usersCannotBeResolved errorMessage = "One or more users could not be resolved" usersCannotBeResolved errorMessage = "One or more users could not be resolved"
requestedSiteCouldNotBeFound errorMessage = "Requested site could not be found" requestedSiteCouldNotBeFound errorMessage = "Requested site could not be found"
) )
@ -101,15 +101,6 @@ var (
// it and when we tried to fetch data for it. // it and when we tried to fetch data for it.
ErrDeletedInFlight = clues.New("deleted in flight") ErrDeletedInFlight = clues.New("deleted in flight")
// Delta tokens can be desycned or expired. In either case, the token
// becomes invalid, and cannot be used again.
// https://learn.microsoft.com/en-us/graph/errors#code-property
ErrInvalidDelta = clues.New("invalid delta token")
// Not all systems support delta queries. This must be handled separately
// from invalid delta token cases.
ErrDeltaNotSupported = clues.New("delta not supported")
// ErrItemAlreadyExistsConflict denotes that a post or put attempted to create // ErrItemAlreadyExistsConflict denotes that a post or put attempted to create
// an item which already exists by some unique identifier. The identifier is // an item which already exists by some unique identifier. The identifier is
// not always the id. For example, in onedrive, this error can be produced // not always the id. For example, in onedrive, this error can be produced
@ -132,16 +123,9 @@ var (
// access to a given service. // access to a given service.
ErrServiceNotEnabled = clues.New("service is not enabled for that resource owner") ErrServiceNotEnabled = clues.New("service is not enabled for that resource owner")
// Timeout errors are identified for tracking the need to retry calls.
// Other delay errors, like throttling, are already handled by the
// graph client's built-in retries.
// https://github.com/microsoftgraph/msgraph-sdk-go/issues/302
ErrTimeout = clues.New("communication timeout")
ErrResourceOwnerNotFound = clues.New("resource owner not found in tenant") ErrResourceOwnerNotFound = clues.New("resource owner not found in tenant")
ErrTokenExpired = clues.New("jwt token expired") ErrTokenExpired = clues.New("jwt token expired")
ErrTokenInvalid = clues.New("jwt token invalid")
) )
func IsErrApplicationThrottled(err error) bool { func IsErrApplicationThrottled(err error) bool {
@ -174,13 +158,11 @@ func IsErrItemNotFound(err error) bool {
} }
func IsErrInvalidDelta(err error) bool { func IsErrInvalidDelta(err error) bool {
return errors.Is(err, ErrInvalidDelta) || return hasErrorCode(err, syncStateNotFound, resyncRequired, syncStateInvalid)
hasErrorCode(err, syncStateNotFound, resyncRequired, syncStateInvalid)
} }
func IsErrDeltaNotSupported(err error) bool { func IsErrDeltaNotSupported(err error) bool {
return errors.Is(err, ErrDeltaNotSupported) || return hasErrorMessage(err, ParameterDeltaTokenNotSupported)
hasErrorMessage(err, parameterDeltaTokenNotSupported)
} }
func IsErrQuotaExceeded(err error) bool { func IsErrQuotaExceeded(err error) bool {
@ -227,8 +209,7 @@ func IsErrTimeout(err error) bool {
return err.Timeout() return err.Timeout()
} }
return errors.Is(err, ErrTimeout) || return errors.Is(err, context.Canceled) ||
errors.Is(err, context.Canceled) ||
errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.DeadlineExceeded) ||
errors.Is(err, http.ErrHandlerTimeout) || errors.Is(err, http.ErrHandlerTimeout) ||
os.IsTimeout(err) os.IsTimeout(err)
@ -241,14 +222,11 @@ func IsErrConnectionReset(err error) bool {
func IsErrUnauthorizedOrBadToken(err error) bool { func IsErrUnauthorizedOrBadToken(err error) bool {
return clues.HasLabel(err, LabelStatus(http.StatusUnauthorized)) || return clues.HasLabel(err, LabelStatus(http.StatusUnauthorized)) ||
hasErrorCode(err, invalidAuthenticationToken) || hasErrorCode(err, invalidAuthenticationToken) ||
errors.Is(err, ErrTokenExpired) || errors.Is(err, ErrTokenExpired)
errors.Is(err, ErrTokenInvalid)
} }
func IsErrBadJWTToken(err error) bool { func IsErrBadJWTToken(err error) bool {
return hasErrorCode(err, invalidAuthenticationToken) || return hasErrorCode(err, invalidAuthenticationToken)
errors.Is(err, ErrTokenExpired) ||
errors.Is(err, ErrTokenInvalid)
} }
func IsErrItemAlreadyExistsConflict(err error) bool { func IsErrItemAlreadyExistsConflict(err error) bool {

View File

@ -2,24 +2,21 @@ package graph
import ( import (
"context" "context"
"encoding/json"
"net/http" "net/http"
"strings" "strings"
"syscall" "syscall"
"testing" "testing"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/microsoft/kiota-abstractions-go/serialization"
kjson "github.com/microsoft/kiota-serialization-json-go"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
type GraphErrorsUnitSuite struct { type GraphErrorsUnitSuite struct {
@ -30,41 +27,6 @@ func TestGraphErrorsUnitSuite(t *testing.T) {
suite.Run(t, &GraphErrorsUnitSuite{Suite: tester.NewUnitSuite(t)}) suite.Run(t, &GraphErrorsUnitSuite{Suite: tester.NewUnitSuite(t)})
} }
func odErr(code string) *odataerrors.ODataError {
odErr := odataerrors.NewODataError()
merr := odataerrors.NewMainError()
merr.SetCode(&code)
odErr.SetErrorEscaped(merr)
return odErr
}
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 parseableToMap(t *testing.T, thing serialization.Parsable) map[string]any {
sw := kjson.NewJsonSerializationWriter()
err := sw.WriteObjectValue("", thing)
require.NoError(t, err, "serialize")
content, err := sw.GetSerializedContent()
require.NoError(t, err, "deserialize")
var out map[string]any
err = json.Unmarshal([]byte(content), &out)
require.NoError(t, err, "unmarshall")
return out
}
func (suite *GraphErrorsUnitSuite) TestIsErrConnectionReset() { func (suite *GraphErrorsUnitSuite) TestIsErrConnectionReset() {
table := []struct { table := []struct {
name string name string
@ -112,12 +74,12 @@ func (suite *GraphErrorsUnitSuite) TestIsErrApplicationThrottled() {
}, },
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("fnords"), err: graphTD.ODataErr("fnords"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "applicationThrottled oDataErr", name: "applicationThrottled oDataErr",
err: odErr(string(applicationThrottled)), err: graphTD.ODataErr(string(applicationThrottled)),
expect: assert.True, expect: assert.True,
}, },
} }
@ -146,12 +108,12 @@ func (suite *GraphErrorsUnitSuite) TestIsErrAuthenticationError() {
}, },
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("fnords"), err: graphTD.ODataErr("fnords"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "authenticationError oDataErr", name: "authenticationError oDataErr",
err: odErr(string(AuthenticationError)), err: graphTD.ODataErr(string(AuthenticationError)),
expect: assert.True, expect: assert.True,
}, },
} }
@ -185,17 +147,17 @@ func (suite *GraphErrorsUnitSuite) TestIsErrDeletedInFlight() {
}, },
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("fnords"), err: graphTD.ODataErr("fnords"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "not-found oDataErr", name: "not-found oDataErr",
err: odErr(string(errorItemNotFound)), err: graphTD.ODataErr(string(errorItemNotFound)),
expect: assert.True, expect: assert.True,
}, },
{ {
name: "sync-not-found oDataErr", name: "sync-not-found oDataErr",
err: odErr(string(syncFolderNotFound)), err: graphTD.ODataErr(string(syncFolderNotFound)),
expect: assert.True, expect: assert.True,
}, },
} }
@ -222,40 +184,35 @@ func (suite *GraphErrorsUnitSuite) TestIsErrInvalidDelta() {
err: assert.AnError, err: assert.AnError,
expect: assert.False, expect: assert.False,
}, },
{
name: "as",
err: ErrInvalidDelta,
expect: assert.True,
},
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("fnords"), err: graphTD.ODataErr("fnords"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "non-matching oDataErrMsg", name: "non-matching oDataErrMsg",
err: odErrMsg("fnords", "deltatoken not supported"), err: graphTD.ODataErrWithMsg("fnords", "deltatoken not supported"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "resync-required oDataErr", name: "resync-required oDataErr",
err: odErr(string(resyncRequired)), err: graphTD.ODataErr(string(resyncRequired)),
expect: assert.True, expect: assert.True,
}, },
{ {
name: "sync state invalid oDataErr", name: "sync state invalid oDataErr",
err: odErr(string(syncStateInvalid)), err: graphTD.ODataErr(string(syncStateInvalid)),
expect: assert.True, expect: assert.True,
}, },
// next two tests are to make sure the checks are case insensitive // next two tests are to make sure the checks are case insensitive
{ {
name: "resync-required oDataErr camelcase", name: "resync-required oDataErr camelcase",
err: odErr("resyncRequired"), err: graphTD.ODataErr("resyncRequired"),
expect: assert.True, expect: assert.True,
}, },
{ {
name: "resync-required oDataErr lowercase", name: "resync-required oDataErr lowercase",
err: odErr("resyncrequired"), err: graphTD.ODataErr("resyncrequired"),
expect: assert.True, expect: assert.True,
}, },
} }
@ -282,29 +239,24 @@ func (suite *GraphErrorsUnitSuite) TestIsErrDeltaNotSupported() {
err: assert.AnError, err: assert.AnError,
expect: assert.False, expect: assert.False,
}, },
{
name: "as",
err: ErrDeltaNotSupported,
expect: assert.True,
},
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("fnords"), err: graphTD.ODataErr("fnords"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "non-matching oDataErrMsg", name: "non-matching oDataErrMsg",
err: odErrMsg("fnords", "deltatoken not supported"), err: graphTD.ODataErrWithMsg("fnords", "deltatoken not supported"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "deltatoken not supported oDataErrMsg", name: "deltatoken not supported oDataErrMsg",
err: odErrMsg("fnords", string(parameterDeltaTokenNotSupported)), err: graphTD.ODataErrWithMsg("fnords", string(ParameterDeltaTokenNotSupported)),
expect: assert.True, expect: assert.True,
}, },
{ {
name: "deltatoken not supported oDataErrMsg with punctuation", name: "deltatoken not supported oDataErrMsg with punctuation",
err: odErrMsg("fnords", string(parameterDeltaTokenNotSupported)+"."), err: graphTD.ODataErrWithMsg("fnords", string(ParameterDeltaTokenNotSupported)+"."),
expect: assert.True, expect: assert.True,
}, },
} }
@ -331,19 +283,14 @@ func (suite *GraphErrorsUnitSuite) TestIsErrQuotaExceeded() {
err: assert.AnError, err: assert.AnError,
expect: assert.False, expect: assert.False,
}, },
{
name: "as",
err: ErrInvalidDelta,
expect: assert.False,
},
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("fnords"), err: graphTD.ODataErr("fnords"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "quota-exceeded oDataErr", name: "quota-exceeded oDataErr",
err: odErr("ErrorQuotaExceeded"), err: graphTD.ODataErr("ErrorQuotaExceeded"),
expect: assert.True, expect: assert.True,
}, },
} }
@ -372,13 +319,13 @@ func (suite *GraphErrorsUnitSuite) TestIsErrUserNotFound() {
}, },
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("fnords"), err: graphTD.ODataErr("fnords"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "non-matching resource not found", name: "non-matching resource not found",
err: func() error { err: func() error {
res := odErr(string(ResourceNotFound)) res := graphTD.ODataErr(string(ResourceNotFound))
res.GetErrorEscaped().SetMessage(ptr.To("Calendar not found")) res.GetErrorEscaped().SetMessage(ptr.To("Calendar not found"))
return res return res
@ -387,18 +334,18 @@ func (suite *GraphErrorsUnitSuite) TestIsErrUserNotFound() {
}, },
{ {
name: "request resource not found oDataErr", name: "request resource not found oDataErr",
err: odErr(string(RequestResourceNotFound)), err: graphTD.ODataErr(string(RequestResourceNotFound)),
expect: assert.True, expect: assert.True,
}, },
{ {
name: "invalid user oDataErr", name: "invalid user oDataErr",
err: odErr(string(invalidUser)), err: graphTD.ODataErr(string(invalidUser)),
expect: assert.True, expect: assert.True,
}, },
{ {
name: "resource not found oDataErr", name: "resource not found oDataErr",
err: func() error { err: func() error {
res := odErrMsg(string(ResourceNotFound), "User not found") res := graphTD.ODataErrWithMsg(string(ResourceNotFound), "User not found")
return res return res
}(), }(),
expect: assert.True, expect: assert.True,
@ -406,7 +353,7 @@ func (suite *GraphErrorsUnitSuite) TestIsErrUserNotFound() {
{ {
name: "resource not found oDataErr wrapped", name: "resource not found oDataErr wrapped",
err: func() error { err: func() error {
res := odErrMsg(string(ResourceNotFound), "User not found") res := graphTD.ODataErrWithMsg(string(ResourceNotFound), "User not found")
return clues.Wrap(res, "getting mail folder") return clues.Wrap(res, "getting mail folder")
}(), }(),
expect: assert.True, expect: assert.True,
@ -414,7 +361,7 @@ func (suite *GraphErrorsUnitSuite) TestIsErrUserNotFound() {
{ {
name: "resource not found oDataErr stacked", name: "resource not found oDataErr stacked",
err: func() error { err: func() error {
res := odErrMsg(string(ResourceNotFound), "User not found") res := graphTD.ODataErrWithMsg(string(ResourceNotFound), "User not found")
return clues.Stack(res, assert.AnError) return clues.Stack(res, assert.AnError)
}(), }(),
expect: assert.True, expect: assert.True,
@ -443,11 +390,6 @@ func (suite *GraphErrorsUnitSuite) TestIsErrTimeout() {
err: assert.AnError, err: assert.AnError,
expect: assert.False, expect: assert.False,
}, },
{
name: "as",
err: ErrTimeout,
expect: assert.True,
},
{ {
name: "context deadline", name: "context deadline",
err: context.DeadlineExceeded, err: context.DeadlineExceeded,
@ -479,7 +421,7 @@ func (suite *GraphErrorsUnitSuite) TestIsErrUnauthorizedOrBadToken() {
}, },
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("folder doesn't exist"), err: graphTD.ODataErr("folder doesn't exist"),
expect: assert.False, expect: assert.False,
}, },
{ {
@ -495,12 +437,7 @@ func (suite *GraphErrorsUnitSuite) TestIsErrUnauthorizedOrBadToken() {
}, },
{ {
name: "oDataErr code invalid auth token ", name: "oDataErr code invalid auth token ",
err: odErr(string(invalidAuthenticationToken)), err: graphTD.ODataErr(string(invalidAuthenticationToken)),
expect: assert.True,
},
{
name: "err token invalid",
err: clues.Stack(assert.AnError, ErrTokenInvalid),
expect: assert.True, expect: assert.True,
}, },
} }
@ -529,7 +466,7 @@ func (suite *GraphErrorsUnitSuite) TestIsErrIsErrBadJWTToken() {
}, },
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("folder doesn't exist"), err: graphTD.ODataErr("folder doesn't exist"),
expect: assert.False, expect: assert.False,
}, },
{ {
@ -541,16 +478,11 @@ func (suite *GraphErrorsUnitSuite) TestIsErrIsErrBadJWTToken() {
{ {
name: "err token expired", name: "err token expired",
err: clues.Stack(assert.AnError, ErrTokenExpired), err: clues.Stack(assert.AnError, ErrTokenExpired),
expect: assert.True, expect: assert.False,
}, },
{ {
name: "oDataErr code invalid auth token ", name: "oDataErr code invalid auth token ",
err: odErr(string(invalidAuthenticationToken)), err: graphTD.ODataErr(string(invalidAuthenticationToken)),
expect: assert.True,
},
{
name: "err token invalid",
err: clues.Stack(assert.AnError, ErrTokenInvalid),
expect: assert.True, expect: assert.True,
}, },
} }
@ -621,23 +553,23 @@ func (suite *GraphErrorsUnitSuite) TestIsErrFolderExists() {
}, },
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("folder doesn't exist"), err: graphTD.ODataErr("folder doesn't exist"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "matching oDataErr msg", name: "matching oDataErr msg",
err: odErr(string(folderExists)), err: graphTD.ODataErr(string(folderExists)),
expect: assert.True, expect: assert.True,
}, },
// next two tests are to make sure the checks are case insensitive // next two tests are to make sure the checks are case insensitive
{ {
name: "oDataErr camelcase", name: "oDataErr camelcase",
err: odErr("ErrorFolderExists"), err: graphTD.ODataErr("ErrorFolderExists"),
expect: assert.True, expect: assert.True,
}, },
{ {
name: "oDataErr lowercase", name: "oDataErr lowercase",
err: odErr("errorfolderexists"), err: graphTD.ODataErr("errorfolderexists"),
expect: assert.True, expect: assert.True,
}, },
} }
@ -666,28 +598,28 @@ func (suite *GraphErrorsUnitSuite) TestIsErrUsersCannotBeResolved() {
}, },
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErrMsg("InvalidRequest", "cant resolve users"), err: graphTD.ODataErrWithMsg("InvalidRequest", "cant resolve users"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "matching oDataErr code", name: "matching oDataErr code",
err: odErrMsg(string(noResolvedUsers), "usersCannotBeResolved"), err: graphTD.ODataErrWithMsg(string(noResolvedUsers), "usersCannotBeResolved"),
expect: assert.True, expect: assert.True,
}, },
{ {
name: "matching oDataErr msg", name: "matching oDataErr msg",
err: odErrMsg("InvalidRequest", string(usersCannotBeResolved)), err: graphTD.ODataErrWithMsg("InvalidRequest", string(usersCannotBeResolved)),
expect: assert.True, expect: assert.True,
}, },
// next two tests are to make sure the checks are case insensitive // next two tests are to make sure the checks are case insensitive
{ {
name: "oDataErr uppercase", name: "oDataErr uppercase",
err: odErrMsg("InvalidRequest", strings.ToUpper(string(usersCannotBeResolved))), err: graphTD.ODataErrWithMsg("InvalidRequest", strings.ToUpper(string(usersCannotBeResolved))),
expect: assert.True, expect: assert.True,
}, },
{ {
name: "oDataErr lowercase", name: "oDataErr lowercase",
err: odErrMsg("InvalidRequest", strings.ToLower(string(usersCannotBeResolved))), err: graphTD.ODataErrWithMsg("InvalidRequest", strings.ToLower(string(usersCannotBeResolved))),
expect: assert.True, expect: assert.True,
}, },
} }
@ -716,23 +648,23 @@ func (suite *GraphErrorsUnitSuite) TestIsErrSiteCouldNotBeFound() {
}, },
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErrMsg("InvalidRequest", "cant resolve sites"), err: graphTD.ODataErrWithMsg("InvalidRequest", "cant resolve sites"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "matching oDataErr msg", name: "matching oDataErr msg",
err: odErrMsg("InvalidRequest", string(requestedSiteCouldNotBeFound)), err: graphTD.ODataErrWithMsg("InvalidRequest", string(requestedSiteCouldNotBeFound)),
expect: assert.True, expect: assert.True,
}, },
// next two tests are to make sure the checks are case insensitive // next two tests are to make sure the checks are case insensitive
{ {
name: "oDataErr uppercase", name: "oDataErr uppercase",
err: odErrMsg("InvalidRequest", strings.ToUpper(string(requestedSiteCouldNotBeFound))), err: graphTD.ODataErrWithMsg("InvalidRequest", strings.ToUpper(string(requestedSiteCouldNotBeFound))),
expect: assert.True, expect: assert.True,
}, },
{ {
name: "oDataErr lowercase", name: "oDataErr lowercase",
err: odErrMsg("InvalidRequest", strings.ToLower(string(requestedSiteCouldNotBeFound))), err: graphTD.ODataErrWithMsg("InvalidRequest", strings.ToLower(string(requestedSiteCouldNotBeFound))),
expect: assert.True, expect: assert.True,
}, },
} }
@ -759,19 +691,14 @@ func (suite *GraphErrorsUnitSuite) TestIsErrCannotOpenFileAttachment() {
err: assert.AnError, err: assert.AnError,
expect: assert.False, expect: assert.False,
}, },
{
name: "as",
err: ErrInvalidDelta,
expect: assert.False,
},
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("fnords"), err: graphTD.ODataErr("fnords"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "quota-exceeded oDataErr", name: "quota-exceeded oDataErr",
err: odErr(string(cannotOpenFileAttachment)), err: graphTD.ODataErr(string(cannotOpenFileAttachment)),
expect: assert.True, expect: assert.True,
}, },
} }
@ -800,22 +727,22 @@ func (suite *GraphErrorsUnitSuite) TestGraphStack_labels() {
}, },
{ {
name: "oDataErr matches no labels", name: "oDataErr matches no labels",
err: odErr("code"), err: graphTD.ODataErr("code"),
expect: []string{}, expect: []string{},
}, },
{ {
name: "mysite not found", name: "mysite not found",
err: odErrMsg("code", string(MysiteNotFound)), err: graphTD.ODataErrWithMsg("code", string(MysiteNotFound)),
expect: []string{LabelsMysiteNotFound}, expect: []string{LabelsMysiteNotFound},
}, },
{ {
name: "mysite url not found", name: "mysite url not found",
err: odErrMsg("code", string(MysiteURLNotFound)), err: graphTD.ODataErrWithMsg("code", string(MysiteURLNotFound)),
expect: []string{LabelsMysiteNotFound}, expect: []string{LabelsMysiteNotFound},
}, },
{ {
name: "no sp license", name: "no sp license",
err: odErrMsg("code", string(NoSPLicense)), err: graphTD.ODataErrWithMsg("code", string(NoSPLicense)),
expect: []string{LabelsNoSharePointLicense}, expect: []string{LabelsNoSharePointLicense},
}, },
} }
@ -856,19 +783,14 @@ func (suite *GraphErrorsUnitSuite) TestIsErrItemNotFound() {
err: assert.AnError, err: assert.AnError,
expect: assert.False, expect: assert.False,
}, },
{
name: "as",
err: ErrInvalidDelta,
expect: assert.False,
},
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErr("fnords"), err: graphTD.ODataErr("fnords"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "item nott found oDataErr", name: "item nott found oDataErr",
err: odErr(string(itemNotFound)), err: graphTD.ODataErr(string(itemNotFound)),
expect: assert.True, expect: assert.True,
}, },
} }
@ -880,7 +802,7 @@ func (suite *GraphErrorsUnitSuite) TestIsErrItemNotFound() {
} }
func (suite *GraphErrorsUnitSuite) TestIsErrResourceLocked() { func (suite *GraphErrorsUnitSuite) TestIsErrResourceLocked() {
innerMatch := odErr("not-match") innerMatch := graphTD.ODataErr("not-match")
merr := odataerrors.NewMainError() merr := odataerrors.NewMainError()
inerr := odataerrors.NewInnerError() inerr := odataerrors.NewInnerError()
inerr.SetAdditionalData(map[string]any{ inerr.SetAdditionalData(map[string]any{
@ -907,12 +829,12 @@ func (suite *GraphErrorsUnitSuite) TestIsErrResourceLocked() {
}, },
{ {
name: "non-matching oDataErr", name: "non-matching oDataErr",
err: odErrMsg("InvalidRequest", "resource is locked"), err: graphTD.ODataErrWithMsg("InvalidRequest", "resource is locked"),
expect: assert.False, expect: assert.False,
}, },
{ {
name: "matching oDataErr code", name: "matching oDataErr code",
err: odErr(string(NotAllowed)), err: graphTD.ODataErr(string(NotAllowed)),
expect: assert.True, expect: assert.True,
}, },
{ {

View File

@ -27,6 +27,7 @@ import (
"github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/count" "github.com/alcionai/corso/src/pkg/count"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
type mwReturns struct { type mwReturns struct {
@ -304,8 +305,8 @@ func (suite *RetryMWIntgSuite) TestRetryMiddleware_RetryResponse_maintainBodyAft
InitializeConcurrencyLimiter(ctx, false, -1) InitializeConcurrencyLimiter(ctx, false, -1)
odem := odErrMsg("SystemDown", "The System, Is Down, bah-dup-da-woo-woo!") odem := graphTD.ODataErrWithMsg("SystemDown", "The System, Is Down, bah-dup-da-woo-woo!")
m := parseableToMap(t, odem) m := graphTD.ParseableToMap(t, odem)
body, err := json.Marshal(m) body, err := json.Marshal(m)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))

View File

@ -1,8 +1,6 @@
package graph package graph
import ( import (
"bytes"
"io"
"net/http" "net/http"
"strconv" "strconv"
"syscall" "syscall"
@ -10,8 +8,6 @@ import (
"time" "time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/microsoft/kiota-abstractions-go/serialization"
kjson "github.com/microsoft/kiota-serialization-json-go"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/users" "github.com/microsoftgraph/msgraph-sdk-go/users"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -22,6 +18,7 @@ import (
"github.com/alcionai/corso/src/internal/tester/tconfig" "github.com/alcionai/corso/src/internal/tester/tconfig"
"github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/count" "github.com/alcionai/corso/src/pkg/count"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
type GraphIntgSuite struct { type GraphIntgSuite struct {
@ -251,23 +248,11 @@ func (suite *GraphIntgSuite) TestAdapterWrap_retriesConnectionClose() {
require.Equal(t, 16, retryInc, "number of retries") require.Equal(t, 16, retryInc, "number of retries")
} }
func requireParseableToReader(t *testing.T, thing serialization.Parsable) (int64, io.ReadCloser) {
sw := kjson.NewJsonSerializationWriter()
err := sw.WriteObjectValue("", thing)
require.NoError(t, err, "serialize")
content, err := sw.GetSerializedContent()
require.NoError(t, err, "deserialize")
return int64(len(content)), io.NopCloser(bytes.NewReader(content))
}
func (suite *GraphIntgSuite) TestAdapterWrap_retriesBadJWTToken() { func (suite *GraphIntgSuite) TestAdapterWrap_retriesBadJWTToken() {
var ( var (
t = suite.T() t = suite.T()
retryInc = 0 retryInc = 0
odErr = odErrMsg(string(invalidAuthenticationToken), string(invalidAuthenticationToken)) odErr = graphTD.ODataErr(string(invalidAuthenticationToken))
) )
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
@ -278,7 +263,7 @@ func (suite *GraphIntgSuite) TestAdapterWrap_retriesBadJWTToken() {
alternate: func(req *http.Request) (bool, *http.Response, error) { alternate: func(req *http.Request) (bool, *http.Response, error) {
retryInc++ retryInc++
l, b := requireParseableToReader(t, odErr) l, b := graphTD.ParseableToReader(t, odErr)
header := http.Header{} header := http.Header{}
header.Set("Content-Length", strconv.Itoa(int(l))) header.Set("Content-Length", strconv.Itoa(int(l)))

View File

@ -0,0 +1,62 @@
package testdata
import (
"bytes"
"encoding/json"
"io"
"testing"
"github.com/microsoft/kiota-abstractions-go/serialization"
kjson "github.com/microsoft/kiota-serialization-json-go"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/stretchr/testify/require"
)
func ODataErr(code string) *odataerrors.ODataError {
odErr := odataerrors.NewODataError()
merr := odataerrors.NewMainError()
merr.SetCode(&code)
// graph sdk expects the message to be available
merr.SetMessage(&code)
odErr.SetErrorEscaped(merr)
return odErr
}
func ODataErrWithMsg(code, message string) *odataerrors.ODataError {
odErr := odataerrors.NewODataError()
merr := odataerrors.NewMainError()
merr.SetCode(&code)
merr.SetMessage(&message)
odErr.SetErrorEscaped(merr)
return odErr
}
func ParseableToMap(t *testing.T, thing serialization.Parsable) map[string]any {
sw := kjson.NewJsonSerializationWriter()
err := sw.WriteObjectValue("", thing)
require.NoError(t, err, "serialize parsable")
content, err := sw.GetSerializedContent()
require.NoError(t, err, "deserialize parsable")
var out map[string]any
err = json.Unmarshal([]byte(content), &out)
require.NoError(t, err, "unmarshal parsable")
return out
}
func ParseableToReader(t *testing.T, thing serialization.Parsable) (int64, io.ReadCloser) {
sw := kjson.NewJsonSerializationWriter()
err := sw.WriteObjectValue("", thing)
require.NoError(t, err, "serialize parsable")
content, err := sw.GetSerializedContent()
require.NoError(t, err, "deserialize parsable")
return int64(len(content)), io.NopCloser(bytes.NewReader(content))
}

View File

@ -1,15 +1,11 @@
package api package api
import ( import (
"encoding/json"
"strings" "strings"
"testing" "testing"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/h2non/gock" "github.com/h2non/gock"
"github.com/microsoft/kiota-abstractions-go/serialization"
kjson "github.com/microsoft/kiota-serialization-json-go"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
@ -60,42 +56,6 @@ func interceptV1Path(pathParts ...string) *gock.Request {
return gock.New(graphAPIHostURL).Get(v1APIURLPath(pathParts...)) return gock.New(graphAPIHostURL).Get(v1APIURLPath(pathParts...))
} }
func odErr(code string) *odataerrors.ODataError {
odErr := odataerrors.NewODataError()
merr := odataerrors.NewMainError()
merr.SetCode(&code)
merr.SetMessage(&code) // sdk expect message to be available
odErr.SetErrorEscaped(merr)
return odErr
}
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 requireParseableToMap(t *testing.T, thing serialization.Parsable) map[string]any {
sw := kjson.NewJsonSerializationWriter()
err := sw.WriteObjectValue("", thing)
require.NoError(t, err, "serialize")
content, err := sw.GetSerializedContent()
require.NoError(t, err, "deserialize")
var out map[string]any
err = json.Unmarshal([]byte(content), &out)
require.NoError(t, err, "unmarshall")
return out
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Suite Setup // Suite Setup
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -18,6 +18,7 @@ import (
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control/testdata" "github.com/alcionai/corso/src/pkg/control/testdata"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
type MailAPIUnitSuite struct { type MailAPIUnitSuite struct {
@ -223,7 +224,7 @@ func (suite *MailAPIIntgSuite) TestMail_attachmentListDownload() {
interceptV1Path("users", "user", "messages", mid). interceptV1Path("users", "user", "messages", mid).
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), mitem)) JSON(graphTD.ParseableToMap(suite.T(), mitem))
}, },
expect: assert.NoError, expect: assert.NoError,
}, },
@ -236,7 +237,7 @@ func (suite *MailAPIIntgSuite) TestMail_attachmentListDownload() {
interceptV1Path("users", "user", "messages", mid). interceptV1Path("users", "user", "messages", mid).
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), email)) JSON(graphTD.ParseableToMap(suite.T(), email))
atts := models.NewAttachmentCollectionResponse() atts := models.NewAttachmentCollectionResponse()
attch := models.NewAttachment() attch := models.NewAttachment()
@ -247,7 +248,7 @@ func (suite *MailAPIIntgSuite) TestMail_attachmentListDownload() {
interceptV1Path("users", "user", "messages", mid, "attachments"). interceptV1Path("users", "user", "messages", mid, "attachments").
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), atts)) JSON(graphTD.ParseableToMap(suite.T(), atts))
}, },
attachmentCount: 1, attachmentCount: 1,
size: 50, size: 50,
@ -262,7 +263,7 @@ func (suite *MailAPIIntgSuite) TestMail_attachmentListDownload() {
interceptV1Path("users", "user", "messages", mid). interceptV1Path("users", "user", "messages", mid).
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), email)) JSON(graphTD.ParseableToMap(suite.T(), email))
atts := models.NewAttachmentCollectionResponse() atts := models.NewAttachmentCollectionResponse()
attch := models.NewAttachment() attch := models.NewAttachment()
@ -278,11 +279,11 @@ func (suite *MailAPIIntgSuite) TestMail_attachmentListDownload() {
interceptV1Path("users", "user", "messages", mid, "attachments"). interceptV1Path("users", "user", "messages", mid, "attachments").
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), atts)) JSON(graphTD.ParseableToMap(suite.T(), atts))
interceptV1Path("users", "user", "messages", mid, "attachments", aid). interceptV1Path("users", "user", "messages", mid, "attachments", aid).
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), attch)) JSON(graphTD.ParseableToMap(suite.T(), attch))
}, },
attachmentCount: 1, attachmentCount: 1,
size: 200, size: 200,
@ -298,7 +299,7 @@ func (suite *MailAPIIntgSuite) TestMail_attachmentListDownload() {
interceptV1Path("users", "user", "messages", mid). interceptV1Path("users", "user", "messages", mid).
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), email)) JSON(graphTD.ParseableToMap(suite.T(), email))
atts := models.NewAttachmentCollectionResponse() atts := models.NewAttachmentCollectionResponse()
attch := models.NewAttachment() attch := models.NewAttachment()
@ -314,12 +315,12 @@ func (suite *MailAPIIntgSuite) TestMail_attachmentListDownload() {
interceptV1Path("users", "user", "messages", mid, "attachments"). interceptV1Path("users", "user", "messages", mid, "attachments").
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), atts)) JSON(graphTD.ParseableToMap(suite.T(), atts))
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
interceptV1Path("users", "user", "messages", mid, "attachments", aid). interceptV1Path("users", "user", "messages", mid, "attachments", aid).
Reply(200). Reply(200).
JSON(requireParseableToMap(suite.T(), attch)) JSON(graphTD.ParseableToMap(suite.T(), attch))
} }
}, },
attachmentCount: 5, attachmentCount: 5,
@ -477,7 +478,7 @@ func (suite *MailAPIIntgSuite) TestMail_GetContainerByName_mocked() {
{ {
name: "zero", name: "zero",
results: func(t *testing.T) map[string]any { results: func(t *testing.T) map[string]any {
return requireParseableToMap(t, models.NewMailFolderCollectionResponse()) return graphTD.ParseableToMap(t, models.NewMailFolderCollectionResponse())
}, },
expectErr: assert.Error, expectErr: assert.Error,
}, },
@ -487,7 +488,7 @@ func (suite *MailAPIIntgSuite) TestMail_GetContainerByName_mocked() {
mfcr := models.NewMailFolderCollectionResponse() mfcr := models.NewMailFolderCollectionResponse()
mfcr.SetValue([]models.MailFolderable{mf}) mfcr.SetValue([]models.MailFolderable{mf})
return requireParseableToMap(t, mfcr) return graphTD.ParseableToMap(t, mfcr)
}, },
expectErr: assert.NoError, expectErr: assert.NoError,
}, },
@ -497,7 +498,7 @@ func (suite *MailAPIIntgSuite) TestMail_GetContainerByName_mocked() {
mfcr := models.NewMailFolderCollectionResponse() mfcr := models.NewMailFolderCollectionResponse()
mfcr.SetValue([]models.MailFolderable{mf, mf}) mfcr.SetValue([]models.MailFolderable{mf, mf})
return requireParseableToMap(t, mfcr) return graphTD.ParseableToMap(t, mfcr)
}, },
expectErr: assert.Error, expectErr: assert.Error,
}, },

View File

@ -15,6 +15,7 @@ import (
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "github.com/alcionai/corso/src/pkg/services/m365/api/graph"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -1057,7 +1058,7 @@ func (suite *PagerUnitSuite) TestGetAddedAndRemovedItemIDs_FallbackPagers() {
t: t, t: t,
pages: []pageResult{ pages: []pageResult{
{ {
err: graph.ErrDeltaNotSupported, err: graphTD.ODataErr(string(graph.ParameterDeltaTokenNotSupported)),
needsReset: true, needsReset: true,
}, },
}, },
@ -1111,7 +1112,7 @@ func (suite *PagerUnitSuite) TestGetAddedAndRemovedItemIDs_FallbackPagers() {
t: t, t: t,
pages: []pageResult{ pages: []pageResult{
{ {
err: graph.ErrDeltaNotSupported, err: graphTD.ODataErr(string(graph.ParameterDeltaTokenNotSupported)),
needsReset: true, needsReset: true,
}, },
}, },
@ -1168,7 +1169,7 @@ func (suite *PagerUnitSuite) TestGetAddedAndRemovedItemIDs_FallbackPagers() {
}, },
}, },
{ {
err: graph.ErrDeltaNotSupported, err: graphTD.ODataErr(string(graph.ParameterDeltaTokenNotSupported)),
needsReset: true, needsReset: true,
}, },
}, },
@ -1283,7 +1284,7 @@ func (suite *PagerUnitSuite) TestGetAddedAndRemovedItemIDs_FallbackPagers() {
}, },
}, },
{ {
err: graph.ErrDeltaNotSupported, err: graphTD.ODataErr(string(graph.ParameterDeltaTokenNotSupported)),
needsReset: true, needsReset: true,
}, },
}, },

View File

@ -10,6 +10,7 @@ import (
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "github.com/alcionai/corso/src/pkg/services/m365/api/graph"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
type UsersUnitSuite struct { type UsersUnitSuite struct {
@ -79,35 +80,35 @@ func (suite *UsersUnitSuite) TestEvaluateMailboxError() {
}, },
{ {
name: "mail inbox err - user not found", name: "mail inbox err - user not found",
err: odErr(string(graph.RequestResourceNotFound)), err: graphTD.ODataErr(string(graph.RequestResourceNotFound)),
expect: func(t *testing.T, err error) { expect: func(t *testing.T, err error) {
assert.ErrorIs(t, err, graph.ErrResourceOwnerNotFound, clues.ToCore(err)) assert.ErrorIs(t, err, graph.ErrResourceOwnerNotFound, clues.ToCore(err))
}, },
}, },
{ {
name: "mail inbox err - resoruceLocked", name: "mail inbox err - resoruceLocked",
err: odErr(string(graph.NotAllowed)), err: graphTD.ODataErr(string(graph.NotAllowed)),
expect: func(t *testing.T, err error) { expect: func(t *testing.T, err error) {
assert.ErrorIs(t, err, graph.ErrResourceLocked, clues.ToCore(err)) assert.ErrorIs(t, err, graph.ErrResourceLocked, clues.ToCore(err))
}, },
}, },
{ {
name: "mail inbox err - user not found", name: "mail inbox err - user not found",
err: odErr(string(graph.MailboxNotEnabledForRESTAPI)), err: graphTD.ODataErr(string(graph.MailboxNotEnabledForRESTAPI)),
expect: func(t *testing.T, err error) { expect: func(t *testing.T, err error) {
assert.NoError(t, err, clues.ToCore(err)) assert.NoError(t, err, clues.ToCore(err))
}, },
}, },
{ {
name: "mail inbox err - authenticationError", name: "mail inbox err - authenticationError",
err: odErr(string(graph.AuthenticationError)), err: graphTD.ODataErr(string(graph.AuthenticationError)),
expect: func(t *testing.T, err error) { expect: func(t *testing.T, err error) {
assert.NoError(t, err, clues.ToCore(err)) assert.NoError(t, err, clues.ToCore(err))
}, },
}, },
{ {
name: "mail inbox err - other error", name: "mail inbox err - other error",
err: odErrMsg("somecode", "somemessage"), err: graphTD.ODataErrWithMsg("somecode", "somemessage"),
expect: func(t *testing.T, err error) { expect: func(t *testing.T, err error) {
assert.Error(t, err, clues.ToCore(err)) assert.Error(t, err, clues.ToCore(err))
}, },