make a not found core error (#4975)
make a canonical 'not found' error for our core sentinels. This replaces the graph.DeletedInFlight error, which acted as an interpretation of "not found". --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🧹 Tech Debt/Cleanup #### Issue(s) * #4685 #### Test Plan - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
940d6e8245
commit
1694581e26
@ -171,8 +171,7 @@ func verifyBackupInputs(sels selectors.Selector, cachedIDs []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !filters.Contains(ids).Compare(sels.ID()) {
|
if !filters.Contains(ids).Compare(sels.ID()) {
|
||||||
return clues.Stack(core.ErrResourceOwnerNotFound).
|
return clues.Stack(core.ErrNotFound).With("selector_protected_resource", sels.DiscreteOwner)
|
||||||
With("selector_protected_resource", sels.DiscreteOwner)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/spatialcurrent/go-lazy/pkg/lazy"
|
"github.com/spatialcurrent/go-lazy/pkg/lazy"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/idname"
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
@ -21,6 +22,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"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/extensions"
|
"github.com/alcionai/corso/src/pkg/extensions"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
@ -302,7 +304,7 @@ func (oc *Collection) getDriveItemContent(
|
|||||||
return nil, clues.Wrap(err, "malware item").Label(graph.LabelsSkippable)
|
return nil, clues.Wrap(err, "malware item").Label(graph.LabelsSkippable)
|
||||||
}
|
}
|
||||||
|
|
||||||
if clues.HasLabel(err, graph.LabelStatus(http.StatusNotFound)) || graph.IsErrDeletedInFlight(err) {
|
if clues.HasLabel(err, graph.LabelStatus(http.StatusNotFound)) || errors.Is(err, core.ErrNotFound) {
|
||||||
logger.CtxErr(ctx, err).Info("item not found, probably deleted in flight")
|
logger.CtxErr(ctx, err).Info("item not found, probably deleted in flight")
|
||||||
return nil, clues.Wrap(err, "deleted item").Label(graph.LabelsSkippable)
|
return nil, clues.Wrap(err, "deleted item").Label(graph.LabelsSkippable)
|
||||||
}
|
}
|
||||||
@ -421,7 +423,7 @@ func readItemContents(
|
|||||||
// Handle newly deleted items
|
// Handle newly deleted items
|
||||||
if props.isDeleted {
|
if props.isDeleted {
|
||||||
logger.Ctx(ctx).Info("item deleted in cache")
|
logger.Ctx(ctx).Info("item deleted in cache")
|
||||||
return nil, graph.ErrDeletedInFlight
|
return nil, core.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
rc, err := downloadFile(ctx, iaag, props.downloadURL)
|
rc, err := downloadFile(ctx, iaag, props.downloadURL)
|
||||||
@ -605,7 +607,7 @@ func (oc *Collection) streamDriveItem(
|
|||||||
itemMeta, itemMetaSize, err = downloadItemMeta(ctx, oc.handler, oc.driveID, item)
|
itemMeta, itemMetaSize, err = downloadItemMeta(ctx, oc.handler, oc.driveID, item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Skip deleted items
|
// Skip deleted items
|
||||||
if !clues.HasLabel(err, graph.LabelStatus(http.StatusNotFound)) && !graph.IsErrDeletedInFlight(err) {
|
if !clues.HasLabel(err, graph.LabelStatus(http.StatusNotFound)) && !errors.Is(err, core.ErrNotFound) {
|
||||||
errs.AddRecoverable(ctx, clues.Wrap(err, "getting item metadata").Label(fault.LabelForceNoBackupCreation))
|
errs.AddRecoverable(ctx, clues.Wrap(err, "getting item metadata").Label(fault.LabelForceNoBackupCreation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,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"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/extensions"
|
"github.com/alcionai/corso/src/pkg/extensions"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -767,10 +768,10 @@ func (suite *GetDriveItemUnitTestSuite) TestDownloadContent() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "url refreshed from cache but item deleted",
|
name: "url refreshed from cache but item deleted",
|
||||||
mgi: getsItem{Item: itemWID, Err: graph.ErrDeletedInFlight},
|
mgi: getsItem{Item: itemWID, Err: core.ErrNotFound},
|
||||||
itemInfo: details.ItemInfo{},
|
itemInfo: details.ItemInfo{},
|
||||||
respBody: []io.ReadCloser{nil, nil, nil},
|
respBody: []io.ReadCloser{nil, nil, nil},
|
||||||
getErr: []error{errUnauth, graph.ErrDeletedInFlight, graph.ErrDeletedInFlight},
|
getErr: []error{errUnauth, core.ErrNotFound, core.ErrNotFound},
|
||||||
expectErr: require.Error,
|
expectErr: require.Error,
|
||||||
expect: require.Nil,
|
expect: require.Nil,
|
||||||
muc: &mockURLCache{
|
muc: &mockURLCache{
|
||||||
|
|||||||
@ -25,6 +25,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"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -281,7 +282,7 @@ func restoreItem(
|
|||||||
itemData,
|
itemData,
|
||||||
ctr)
|
ctr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, graph.ErrItemAlreadyExistsConflict) && rcc.RestoreConfig.OnCollision == control.Skip {
|
if errors.Is(err, core.ErrAlreadyExists) && rcc.RestoreConfig.OnCollision == control.Skip {
|
||||||
return details.ItemInfo{}, true, nil
|
return details.ItemInfo{}, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,7 +340,7 @@ func restoreItem(
|
|||||||
ctr,
|
ctr,
|
||||||
errs)
|
errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, graph.ErrItemAlreadyExistsConflict) && rcc.RestoreConfig.OnCollision == control.Skip {
|
if errors.Is(err, core.ErrAlreadyExists) && rcc.RestoreConfig.OnCollision == control.Skip {
|
||||||
return details.ItemInfo{}, true, nil
|
return details.ItemInfo{}, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,7 +366,7 @@ func restoreItem(
|
|||||||
ctr,
|
ctr,
|
||||||
errs)
|
errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, graph.ErrItemAlreadyExistsConflict) && rcc.RestoreConfig.OnCollision == control.Skip {
|
if errors.Is(err, core.ErrAlreadyExists) && rcc.RestoreConfig.OnCollision == control.Skip {
|
||||||
return details.ItemInfo{}, true, nil
|
return details.ItemInfo{}, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -671,7 +672,7 @@ func createFolder(
|
|||||||
|
|
||||||
// ErrItemAlreadyExistsConflict can only occur for folders if the
|
// ErrItemAlreadyExistsConflict can only occur for folders if the
|
||||||
// item being replaced is a file, not another folder.
|
// item being replaced is a file, not another folder.
|
||||||
if err != nil && !errors.Is(err, graph.ErrItemAlreadyExistsConflict) {
|
if err != nil && !errors.Is(err, core.ErrAlreadyExists) {
|
||||||
return nil, clues.Wrap(err, "creating folder")
|
return nil, clues.Wrap(err, "creating folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -742,7 +743,7 @@ func restoreFile(
|
|||||||
ctr.Inc(count.CollisionSkip)
|
ctr.Inc(count.CollisionSkip)
|
||||||
log.Debug("skipping item with collision")
|
log.Debug("skipping item with collision")
|
||||||
|
|
||||||
return "", details.ItemInfo{}, graph.ErrItemAlreadyExistsConflict
|
return "", details.ItemInfo{}, core.ErrAlreadyExists
|
||||||
}
|
}
|
||||||
|
|
||||||
collision = dci
|
collision = dci
|
||||||
@ -757,7 +758,8 @@ func restoreFile(
|
|||||||
// risk failures in the middle, or we post w/ copy, then delete, then patch
|
// risk failures in the middle, or we post w/ copy, then delete, then patch
|
||||||
// the name, which could triple our graph calls in the worst case.
|
// the name, which could triple our graph calls in the worst case.
|
||||||
if shouldDeleteOriginal {
|
if shouldDeleteOriginal {
|
||||||
if err := ir.DeleteItem(ctx, driveID, collision.ItemID); err != nil && !graph.IsErrDeletedInFlight(err) {
|
err := ir.DeleteItem(ctx, driveID, collision.ItemID)
|
||||||
|
if err != nil && !errors.Is(err, core.ErrNotFound) {
|
||||||
return "", details.ItemInfo{}, clues.New("deleting colliding item")
|
return "", details.ItemInfo{}, clues.New("deleting colliding item")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -970,7 +972,7 @@ func ensureDriveExists(
|
|||||||
ictx := clues.Add(ctx, "new_drive_name", clues.Hide(nextDriveName))
|
ictx := clues.Add(ctx, "new_drive_name", clues.Hide(nextDriveName))
|
||||||
|
|
||||||
newDrive, err = pdagrf.PostDrive(ictx, protectedResourceID, nextDriveName)
|
newDrive, err = pdagrf.PostDrive(ictx, protectedResourceID, nextDriveName)
|
||||||
if err != nil && !errors.Is(err, graph.ErrItemAlreadyExistsConflict) {
|
if err != nil && !errors.Is(err, core.ErrAlreadyExists) {
|
||||||
return driveInfo{}, clues.Wrap(err, "creating new drive")
|
return driveInfo{}, clues.Wrap(err, "creating new drive")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
@ -122,7 +123,7 @@ func (suite *RestoreUnitSuite) TestRestoreItem_collisionHandling() {
|
|||||||
mock.DriveItemFileName: {ItemID: "smarf"},
|
mock.DriveItemFileName: {ItemID: "smarf"},
|
||||||
},
|
},
|
||||||
onCollision: control.Replace,
|
onCollision: control.Replace,
|
||||||
deleteErr: graph.ErrDeletedInFlight,
|
deleteErr: core.ErrNotFound,
|
||||||
expectSkipped: assert.False,
|
expectSkipped: assert.False,
|
||||||
expectMock: func(t *testing.T, rh *mockRestoreHandler) {
|
expectMock: func(t *testing.T, rh *mockRestoreHandler) {
|
||||||
assert.True(t, rh.CalledPostItem, "new item posted")
|
assert.True(t, rh.CalledPostItem, "new item posted")
|
||||||
@ -298,7 +299,7 @@ func (suite *RestoreUnitSuite) TestCreateFolder() {
|
|||||||
{
|
{
|
||||||
name: "good with copy",
|
name: "good with copy",
|
||||||
mock: &mockPIIC{
|
mock: &mockPIIC{
|
||||||
errs: []error{graph.ErrItemAlreadyExistsConflict, nil},
|
errs: []error{core.ErrAlreadyExists, nil},
|
||||||
items: []models.DriveItemable{nil, models.NewDriveItem()},
|
items: []models.DriveItemable{nil, models.NewDriveItem()},
|
||||||
},
|
},
|
||||||
expectErr: assert.NoError,
|
expectErr: assert.NoError,
|
||||||
@ -316,7 +317,7 @@ func (suite *RestoreUnitSuite) TestCreateFolder() {
|
|||||||
{
|
{
|
||||||
name: "bad with copy",
|
name: "bad with copy",
|
||||||
mock: &mockPIIC{
|
mock: &mockPIIC{
|
||||||
errs: []error{graph.ErrItemAlreadyExistsConflict, assert.AnError},
|
errs: []error{core.ErrAlreadyExists, assert.AnError},
|
||||||
items: []models.DriveItemable{nil, nil},
|
items: []models.DriveItemable{nil, nil},
|
||||||
},
|
},
|
||||||
expectErr: assert.Error,
|
expectErr: assert.Error,
|
||||||
@ -759,7 +760,7 @@ func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
|
|||||||
dp: dp,
|
dp: dp,
|
||||||
mock: &mockPDAGRF{
|
mock: &mockPDAGRF{
|
||||||
postResp: []models.Driveable{nil, makeMD()},
|
postResp: []models.Driveable{nil, makeMD()},
|
||||||
postErr: []error{graph.ErrItemAlreadyExistsConflict, nil},
|
postErr: []error{core.ErrAlreadyExists, nil},
|
||||||
grf: grf,
|
grf: grf,
|
||||||
},
|
},
|
||||||
rc: NewRestoreCaches(nil),
|
rc: NewRestoreCaches(nil),
|
||||||
@ -773,7 +774,7 @@ func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
|
|||||||
dp: oldDP,
|
dp: oldDP,
|
||||||
mock: &mockPDAGRF{
|
mock: &mockPDAGRF{
|
||||||
postResp: []models.Driveable{nil, makeMD()},
|
postResp: []models.Driveable{nil, makeMD()},
|
||||||
postErr: []error{graph.ErrItemAlreadyExistsConflict, nil},
|
postErr: []error{core.ErrAlreadyExists, nil},
|
||||||
grf: grf,
|
grf: grf,
|
||||||
},
|
},
|
||||||
rc: NewRestoreCaches(oldDriveIDNames),
|
rc: NewRestoreCaches(oldDriveIDNames),
|
||||||
@ -787,7 +788,7 @@ func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
|
|||||||
dp: dp,
|
dp: dp,
|
||||||
mock: &mockPDAGRF{
|
mock: &mockPDAGRF{
|
||||||
postResp: []models.Driveable{nil, makeMD()},
|
postResp: []models.Driveable{nil, makeMD()},
|
||||||
postErr: []error{graph.ErrItemAlreadyExistsConflict, nil},
|
postErr: []error{core.ErrAlreadyExists, nil},
|
||||||
grf: grf,
|
grf: grf,
|
||||||
},
|
},
|
||||||
rc: populatedCache(driveID),
|
rc: populatedCache(driveID),
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package exchange
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
@ -15,6 +16,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/backup/metadata"
|
"github.com/alcionai/corso/src/pkg/backup/metadata"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -265,7 +267,7 @@ func populateCollections(
|
|||||||
prevDelta,
|
prevDelta,
|
||||||
itemConfig)
|
itemConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !graph.IsErrDeletedInFlight(err) {
|
if !errors.Is(err, core.ErrNotFound) {
|
||||||
el.AddRecoverable(ctx, clues.Stack(err).Label(fault.LabelForceNoBackupCreation))
|
el.AddRecoverable(ctx, clues.Stack(err).Label(fault.LabelForceNoBackupCreation))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/backup/metadata"
|
"github.com/alcionai/corso/src/pkg/backup/metadata"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
@ -1010,7 +1011,7 @@ func (suite *CollectionPopulationSuite) TestPopulateCollections() {
|
|||||||
added: []string{"a1", "a2", "a3"},
|
added: []string{"a1", "a2", "a3"},
|
||||||
removed: []string{"r1", "r2", "r3"},
|
removed: []string{"r1", "r2", "r3"},
|
||||||
newDelta: pagers.DeltaUpdate{URL: "delta_url"},
|
newDelta: pagers.DeltaUpdate{URL: "delta_url"},
|
||||||
err: graph.ErrDeletedInFlight,
|
err: core.ErrNotFound,
|
||||||
}
|
}
|
||||||
container1 = mockContainer{
|
container1 = mockContainer{
|
||||||
id: strPtr("1"),
|
id: strPtr("1"),
|
||||||
|
|||||||
@ -6,6 +6,7 @@ package exchange
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -19,6 +20,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/observe"
|
"github.com/alcionai/corso/src/internal/observe"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -278,7 +280,7 @@ func (col *prefetchCollection) streamItems(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// Handle known error cases
|
// Handle known error cases
|
||||||
switch {
|
switch {
|
||||||
case graph.IsErrDeletedInFlight(err):
|
case errors.Is(err, core.ErrNotFound):
|
||||||
// Don't report errors for deleted items as there's no way for us to
|
// Don't report errors for deleted items as there's no way for us to
|
||||||
// back up data that is gone. Record it as a "success", since there's
|
// back up data that is gone. Record it as a "success", since there's
|
||||||
// nothing else we can do, and not reporting it will make the status
|
// nothing else we can do, and not reporting it will make the status
|
||||||
@ -490,7 +492,7 @@ func (lig *lazyItemGetter) GetData(
|
|||||||
//
|
//
|
||||||
// The item will be deleted from kopia on the next backup when the
|
// The item will be deleted from kopia on the next backup when the
|
||||||
// delta token shows it's removed.
|
// delta token shows it's removed.
|
||||||
if graph.IsErrDeletedInFlight(err) {
|
if errors.Is(err, core.ErrNotFound) {
|
||||||
logger.CtxErr(ctx, err).Info("item not found")
|
logger.CtxErr(ctx, err).Info("item not found")
|
||||||
return nil, nil, true, nil
|
return nil, nil, true, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,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"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
@ -200,10 +201,10 @@ func (suite *CollectionUnitSuite) TestGetItemWithRetries() {
|
|||||||
{
|
{
|
||||||
name: "deleted in flight",
|
name: "deleted in flight",
|
||||||
items: &mock.ItemGetSerialize{
|
items: &mock.ItemGetSerialize{
|
||||||
GetErr: graph.ErrDeletedInFlight,
|
GetErr: core.ErrNotFound,
|
||||||
},
|
},
|
||||||
expectErr: func(t *testing.T, err error) {
|
expectErr: func(t *testing.T, err error) {
|
||||||
assert.True(t, graph.IsErrDeletedInFlight(err), "is ErrDeletedInFlight")
|
assert.ErrorIs(t, err, core.ErrNotFound, "is ErrItemNotFound")
|
||||||
},
|
},
|
||||||
expectGetCalls: 1,
|
expectGetCalls: 1,
|
||||||
},
|
},
|
||||||
@ -682,7 +683,7 @@ func (suite *CollectionUnitSuite) TestLazyItem_ReturnsEmptyReaderOnDeletedInFlig
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
getter := &mock.ItemGetSerialize{GetErr: graph.ErrDeletedInFlight}
|
getter := &mock.ItemGetSerialize{GetErr: core.ErrNotFound}
|
||||||
|
|
||||||
li := data.NewLazyItemWithInfo(
|
li := data.NewLazyItemWithInfo(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package exchange
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
@ -10,6 +11,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"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -120,7 +122,7 @@ func restoreContact(
|
|||||||
ctr.Inc(count.CollisionSkip)
|
ctr.Inc(count.CollisionSkip)
|
||||||
log.Debug("skipping item with collision")
|
log.Debug("skipping item with collision")
|
||||||
|
|
||||||
return nil, graph.ErrItemAlreadyExistsConflict
|
return nil, core.ErrAlreadyExists
|
||||||
}
|
}
|
||||||
|
|
||||||
collisionID = id
|
collisionID = id
|
||||||
@ -138,7 +140,8 @@ func restoreContact(
|
|||||||
// at least we'll have accidentally over-produced data instead of deleting
|
// at least we'll have accidentally over-produced data instead of deleting
|
||||||
// the user's data.
|
// the user's data.
|
||||||
if shouldDeleteOriginal {
|
if shouldDeleteOriginal {
|
||||||
if err := cr.DeleteItem(ctx, userID, collisionID); err != nil && !graph.IsErrDeletedInFlight(err) {
|
err := cr.DeleteItem(ctx, userID, collisionID)
|
||||||
|
if err != nil && !errors.Is(err, core.ErrNotFound) {
|
||||||
return nil, graph.Wrap(ctx, err, "deleting colliding contact")
|
return nil, graph.Wrap(ctx, err, "deleting colliding contact")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,10 +16,10 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ contactRestorer = &contactRestoreMock{}
|
var _ contactRestorer = &contactRestoreMock{}
|
||||||
@ -153,7 +153,7 @@ func (suite *ContactsRestoreIntgSuite) TestRestoreContact() {
|
|||||||
collisionMap: map[string]string{collisionKey: "smarf"},
|
collisionMap: map[string]string{collisionKey: "smarf"},
|
||||||
onCollision: control.Skip,
|
onCollision: control.Skip,
|
||||||
expectErr: func(t *testing.T, err error) {
|
expectErr: func(t *testing.T, err error) {
|
||||||
assert.ErrorIs(t, err, graph.ErrItemAlreadyExistsConflict, clues.ToCore(err))
|
assert.ErrorIs(t, err, core.ErrAlreadyExists, clues.ToCore(err))
|
||||||
},
|
},
|
||||||
expectMock: func(t *testing.T, m *contactRestoreMock) {
|
expectMock: func(t *testing.T, m *contactRestoreMock) {
|
||||||
assert.False(t, m.calledPost, "new item posted")
|
assert.False(t, m.calledPost, "new item posted")
|
||||||
@ -191,7 +191,7 @@ func (suite *ContactsRestoreIntgSuite) TestRestoreContact() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "collision: replace - err already deleted",
|
name: "collision: replace - err already deleted",
|
||||||
apiMock: &contactRestoreMock{deleteItemErr: graph.ErrDeletedInFlight},
|
apiMock: &contactRestoreMock{deleteItemErr: core.ErrNotFound},
|
||||||
collisionMap: map[string]string{collisionKey: "smarf"},
|
collisionMap: map[string]string{collisionKey: "smarf"},
|
||||||
onCollision: control.Replace,
|
onCollision: control.Replace,
|
||||||
expectErr: func(t *testing.T, err error) {
|
expectErr: func(t *testing.T, err error) {
|
||||||
|
|||||||
@ -2,11 +2,13 @@ package exchange
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -95,7 +97,7 @@ func (cr *containerResolver) refreshContainer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
c, err := cr.refresher.refreshContainer(ctx, id)
|
c, err := cr.refresher.refreshContainer(ctx, id)
|
||||||
if err != nil && graph.IsErrDeletedInFlight(err) {
|
if err != nil && errors.Is(err, core.ErrNotFound) {
|
||||||
logger.Ctx(ctx).Debug("container deleted")
|
logger.Ctx(ctx).Debug("container deleted")
|
||||||
return nil, true, nil
|
return nil, true, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
@ -670,7 +671,7 @@ func (r mockContainerRefresher) refreshContainer(
|
|||||||
rr, ok := r.entries[id]
|
rr, ok := r.entries[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
// May not be this precise error, but it's easy to get a handle on.
|
// May not be this precise error, but it's easy to get a handle on.
|
||||||
return nil, graph.ErrDeletedInFlight
|
return nil, core.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if rr.err != nil {
|
if rr.err != nil {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package exchange
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
@ -11,6 +12,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"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -126,7 +128,7 @@ func restoreEvent(
|
|||||||
ctr.Inc(count.CollisionSkip)
|
ctr.Inc(count.CollisionSkip)
|
||||||
log.Debug("skipping item with collision")
|
log.Debug("skipping item with collision")
|
||||||
|
|
||||||
return nil, graph.ErrItemAlreadyExistsConflict
|
return nil, core.ErrAlreadyExists
|
||||||
}
|
}
|
||||||
|
|
||||||
collisionID = id
|
collisionID = id
|
||||||
@ -155,7 +157,8 @@ func restoreEvent(
|
|||||||
// at least we'll have accidentally over-produced data instead of deleting
|
// at least we'll have accidentally over-produced data instead of deleting
|
||||||
// the user's data.
|
// the user's data.
|
||||||
if shouldDeleteOriginal {
|
if shouldDeleteOriginal {
|
||||||
if err := er.DeleteItem(ctx, userID, collisionID); err != nil && !graph.IsErrDeletedInFlight(err) {
|
err := er.DeleteItem(ctx, userID, collisionID)
|
||||||
|
if err != nil && !errors.Is(err, core.ErrNotFound) {
|
||||||
return nil, graph.Wrap(ctx, err, "deleting colliding event")
|
return nil, graph.Wrap(ctx, err, "deleting colliding event")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,10 +17,10 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ eventRestorer = &eventRestoreMock{}
|
var _ eventRestorer = &eventRestoreMock{}
|
||||||
@ -201,7 +201,7 @@ func (suite *EventsRestoreIntgSuite) TestRestoreEvent() {
|
|||||||
collisionMap: map[string]string{collisionKey: "smarf"},
|
collisionMap: map[string]string{collisionKey: "smarf"},
|
||||||
onCollision: control.Skip,
|
onCollision: control.Skip,
|
||||||
expectErr: func(t *testing.T, err error) {
|
expectErr: func(t *testing.T, err error) {
|
||||||
assert.ErrorIs(t, err, graph.ErrItemAlreadyExistsConflict, clues.ToCore(err))
|
assert.ErrorIs(t, err, core.ErrAlreadyExists, clues.ToCore(err))
|
||||||
},
|
},
|
||||||
expectMock: func(t *testing.T, m *eventRestoreMock) {
|
expectMock: func(t *testing.T, m *eventRestoreMock) {
|
||||||
assert.False(t, m.calledPost, "new item posted")
|
assert.False(t, m.calledPost, "new item posted")
|
||||||
@ -239,7 +239,7 @@ func (suite *EventsRestoreIntgSuite) TestRestoreEvent() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "collision: replace - err already deleted",
|
name: "collision: replace - err already deleted",
|
||||||
apiMock: &eventRestoreMock{deleteItemErr: graph.ErrDeletedInFlight},
|
apiMock: &eventRestoreMock{deleteItemErr: core.ErrNotFound},
|
||||||
collisionMap: map[string]string{collisionKey: "smarf"},
|
collisionMap: map[string]string{collisionKey: "smarf"},
|
||||||
onCollision: control.Replace,
|
onCollision: control.Replace,
|
||||||
expectErr: func(t *testing.T, err error) {
|
expectErr: func(t *testing.T, err error) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package exchange
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
@ -11,6 +12,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/dttm"
|
"github.com/alcionai/corso/src/pkg/dttm"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -126,7 +128,7 @@ func restoreMail(
|
|||||||
ctr.Inc(count.CollisionSkip)
|
ctr.Inc(count.CollisionSkip)
|
||||||
log.Debug("skipping item with collision")
|
log.Debug("skipping item with collision")
|
||||||
|
|
||||||
return nil, graph.ErrItemAlreadyExistsConflict
|
return nil, core.ErrAlreadyExists
|
||||||
}
|
}
|
||||||
|
|
||||||
collisionID = id
|
collisionID = id
|
||||||
@ -150,7 +152,8 @@ func restoreMail(
|
|||||||
// at least we'll have accidentally over-produced data instead of deleting
|
// at least we'll have accidentally over-produced data instead of deleting
|
||||||
// the user's data.
|
// the user's data.
|
||||||
if shouldDeleteOriginal {
|
if shouldDeleteOriginal {
|
||||||
if err := mr.DeleteItem(ctx, userID, collisionID); err != nil && !graph.IsErrDeletedInFlight(err) {
|
err := mr.DeleteItem(ctx, userID, collisionID)
|
||||||
|
if err != nil && !errors.Is(err, core.ErrNotFound) {
|
||||||
return nil, graph.Wrap(ctx, err, "deleting colliding mail message")
|
return nil, graph.Wrap(ctx, err, "deleting colliding mail message")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,10 +17,10 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ mailRestorer = &mailRestoreMock{}
|
var _ mailRestorer = &mailRestoreMock{}
|
||||||
@ -170,7 +170,7 @@ func (suite *MailRestoreIntgSuite) TestRestoreMail() {
|
|||||||
collisionMap: map[string]string{collisionKey: "smarf"},
|
collisionMap: map[string]string{collisionKey: "smarf"},
|
||||||
onCollision: control.Skip,
|
onCollision: control.Skip,
|
||||||
expectErr: func(t *testing.T, err error) {
|
expectErr: func(t *testing.T, err error) {
|
||||||
assert.ErrorIs(t, err, graph.ErrItemAlreadyExistsConflict, clues.ToCore(err))
|
assert.ErrorIs(t, err, core.ErrAlreadyExists, clues.ToCore(err))
|
||||||
},
|
},
|
||||||
expectMock: func(t *testing.T, m *mailRestoreMock) {
|
expectMock: func(t *testing.T, m *mailRestoreMock) {
|
||||||
assert.False(t, m.calledPost, "new item posted")
|
assert.False(t, m.calledPost, "new item posted")
|
||||||
@ -208,7 +208,7 @@ func (suite *MailRestoreIntgSuite) TestRestoreMail() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "collision: replace - err already deleted",
|
name: "collision: replace - err already deleted",
|
||||||
apiMock: &mailRestoreMock{deleteItemErr: graph.ErrDeletedInFlight},
|
apiMock: &mailRestoreMock{deleteItemErr: core.ErrNotFound},
|
||||||
collisionMap: map[string]string{collisionKey: "smarf"},
|
collisionMap: map[string]string{collisionKey: "smarf"},
|
||||||
onCollision: control.Replace,
|
onCollision: control.Replace,
|
||||||
expectErr: func(t *testing.T, err error) {
|
expectErr: func(t *testing.T, err error) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package exchange
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"runtime/trace"
|
"runtime/trace"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -18,6 +19,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"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -87,7 +89,7 @@ func RestoreCollection(
|
|||||||
errs,
|
errs,
|
||||||
ctr)
|
ctr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !graph.IsErrItemAlreadyExistsConflict(err) {
|
if !errors.Is(err, core.ErrAlreadyExists) {
|
||||||
el.AddRecoverable(ictx, clues.Wrap(err, "restoring item"))
|
el.AddRecoverable(ictx, clues.Wrap(err, "restoring item"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +258,7 @@ func uploadAttachments(
|
|||||||
|
|
||||||
retryBackoff.Reset()
|
retryBackoff.Reset()
|
||||||
|
|
||||||
for (retry == 0 || graph.IsErrItemNotFound(err)) && retry <= maxRetries {
|
for (retry == 0 || errors.Is(err, core.ErrNotFound)) && retry <= maxRetries {
|
||||||
retry++
|
retry++
|
||||||
|
|
||||||
ictx := clues.Add(actx, "attempt_num", retry)
|
ictx := clues.Add(actx, "attempt_num", retry)
|
||||||
@ -272,7 +274,7 @@ func uploadAttachments(
|
|||||||
// Sometimes graph returns a 404 when we try to post the attachment.
|
// Sometimes graph returns a 404 when we try to post the attachment.
|
||||||
// We're not sure why, but maybe it has to do with attaching many items.
|
// We're not sure why, but maybe it has to do with attaching many items.
|
||||||
// In any case, wait a little while and try again.
|
// In any case, wait a little while and try again.
|
||||||
if graph.IsErrItemNotFound(err) && retry <= maxRetries {
|
if errors.Is(err, core.ErrNotFound) && retry <= maxRetries {
|
||||||
waitTime := retryBackoff.NextBackOff()
|
waitTime := retryBackoff.NextBackOff()
|
||||||
|
|
||||||
logger.Ctx(ictx).Infow(
|
logger.Ctx(ictx).Infow(
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/backup/metadata"
|
"github.com/alcionai/corso/src/pkg/backup/metadata"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
@ -239,7 +240,7 @@ func (suite *BackupUnitSuite) TestPopulateCollections() {
|
|||||||
name: "err: deleted in flight",
|
name: "err: deleted in flight",
|
||||||
mock: mockBackupHandler{
|
mock: mockBackupHandler{
|
||||||
channels: testdata.StubChannels("one"),
|
channels: testdata.StubChannels("one"),
|
||||||
messagesErr: graph.ErrDeletedInFlight,
|
messagesErr: core.ErrNotFound,
|
||||||
},
|
},
|
||||||
expectErr: require.Error,
|
expectErr: require.Error,
|
||||||
expectColls: 1,
|
expectColls: 1,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package groups
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
@ -17,6 +18,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/observe"
|
"github.com/alcionai/corso/src/internal/observe"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -420,7 +422,7 @@ func (lig *lazyItemGetter[C, I]) GetData(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// For items that were deleted in flight, add the skip label so that
|
// For items that were deleted in flight, add the skip label so that
|
||||||
// they don't lead to recoverable failures during backup.
|
// they don't lead to recoverable failures during backup.
|
||||||
if clues.HasLabel(err, graph.LabelStatus(http.StatusNotFound)) || graph.IsErrDeletedInFlight(err) {
|
if clues.HasLabel(err, graph.LabelStatus(http.StatusNotFound)) || errors.Is(err, core.ErrNotFound) {
|
||||||
logger.CtxErr(ctx, err).Info("item deleted in flight. skipping")
|
logger.CtxErr(ctx, err).Info("item deleted in flight. skipping")
|
||||||
|
|
||||||
// Returning delInFlight as true here for correctness, although the caller is going
|
// Returning delInFlight as true here for correctness, although the caller is going
|
||||||
|
|||||||
@ -22,9 +22,9 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CollectionUnitSuite struct {
|
type CollectionUnitSuite struct {
|
||||||
@ -533,7 +533,7 @@ func (suite *CollectionUnitSuite) TestLazyItem_ReturnsEmptyReaderOnDeletedInFlig
|
|||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
m := getAndAugmentConversation{
|
m := getAndAugmentConversation{
|
||||||
GetItemErr: graph.ErrDeletedInFlight,
|
GetItemErr: core.ErrNotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
li := data.NewLazyItemWithInfo(
|
li := data.NewLazyItemWithInfo(
|
||||||
@ -558,7 +558,7 @@ func (suite *CollectionUnitSuite) TestLazyItem_ReturnsEmptyReaderOnDeletedInFlig
|
|||||||
"item mod time")
|
"item mod time")
|
||||||
|
|
||||||
_, err := readers.NewVersionedRestoreReader(li.ToReader())
|
_, err := readers.NewVersionedRestoreReader(li.ToReader())
|
||||||
assert.ErrorIs(t, err, graph.ErrDeletedInFlight, "item should be marked deleted in flight")
|
assert.ErrorIs(t, err, core.ErrNotFound, "item should be marked deleted in flight")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *CollectionUnitSuite) TestLazyItem() {
|
func (suite *CollectionUnitSuite) TestLazyItem() {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package site
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
@ -23,6 +24,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"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -501,7 +503,7 @@ func (lig *lazyItemGetter) GetData(
|
|||||||
) (io.ReadCloser, *details.ItemInfo, bool, error) {
|
) (io.ReadCloser, *details.ItemInfo, bool, error) {
|
||||||
list, info, err := lig.getter.GetItemByID(ctx, lig.itemID)
|
list, info, err := lig.getter.GetItemByID(ctx, lig.itemID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if clues.HasLabel(err, graph.LabelStatus(http.StatusNotFound)) || graph.IsErrDeletedInFlight(err) {
|
if clues.HasLabel(err, graph.LabelStatus(http.StatusNotFound)) || errors.Is(err, core.ErrNotFound) {
|
||||||
logger.CtxErr(ctx, err).Info("item deleted in flight. skipping")
|
logger.CtxErr(ctx, err).Info("item deleted in flight. skipping")
|
||||||
|
|
||||||
// Returning delInFlight as true here for correctness, although the caller is going
|
// Returning delInFlight as true here for correctness, although the caller is going
|
||||||
|
|||||||
@ -25,11 +25,11 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SharePointCollectionUnitSuite struct {
|
type SharePointCollectionUnitSuite struct {
|
||||||
@ -469,7 +469,7 @@ func (suite *SharePointCollectionSuite) TestLazyItem_ReturnsEmptyReaderOnDeleted
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
lh := mock.NewListHandler(nil, "", graph.ErrDeletedInFlight)
|
lh := mock.NewListHandler(nil, "", core.ErrNotFound)
|
||||||
|
|
||||||
li := data.NewLazyItemWithInfo(
|
li := data.NewLazyItemWithInfo(
|
||||||
ctx,
|
ctx,
|
||||||
@ -491,6 +491,6 @@ func (suite *SharePointCollectionSuite) TestLazyItem_ReturnsEmptyReaderOnDeleted
|
|||||||
"item mod time")
|
"item mod time")
|
||||||
|
|
||||||
r, err := readers.NewVersionedRestoreReader(li.ToReader())
|
r, err := readers.NewVersionedRestoreReader(li.ToReader())
|
||||||
assert.ErrorIs(t, err, graph.ErrDeletedInFlight, "item should be marked deleted in flight")
|
assert.ErrorIs(t, err, core.ErrNotFound, "item should be marked deleted in flight")
|
||||||
assert.Nil(t, r)
|
assert.Nil(t, r)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -259,7 +259,7 @@ func (r resourceGetter) GetResourceIDAndNameFrom(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(id) == 0 || len(name) == 0 {
|
if len(id) == 0 || len(name) == 0 {
|
||||||
return nil, clues.Stack(core.ErrResourceOwnerNotFound)
|
return nil, clues.Stack(core.ErrNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
return idname.NewProvider(id, name), nil
|
return idname.NewProvider(id, name), nil
|
||||||
|
|||||||
@ -92,14 +92,14 @@ func (suite *EnabledUnitSuite) TestIsServiceEnabled() {
|
|||||||
name: "overlapping resourcenotfound",
|
name: "overlapping resourcenotfound",
|
||||||
mock: func(ctx context.Context) getMailInboxer {
|
mock: func(ctx context.Context) getMailInboxer {
|
||||||
odErr := graphTD.ODataErrWithMsg(string(graph.ResourceNotFound), "User not found")
|
odErr := graphTD.ODataErrWithMsg(string(graph.ResourceNotFound), "User not found")
|
||||||
err := clues.Stack(core.ErrResourceOwnerNotFound, odErr)
|
err := clues.Stack(core.ErrNotFound, odErr)
|
||||||
|
|
||||||
return mockGMB{
|
return mockGMB{
|
||||||
mailboxErr: graph.Stack(ctx, err),
|
mailboxErr: graph.Stack(ctx, err),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
expect: assert.False,
|
expect: assert.False,
|
||||||
expectErr: assert.Error,
|
expectErr: assert.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "arbitrary error",
|
name: "arbitrary error",
|
||||||
|
|||||||
@ -51,16 +51,32 @@ func (e Err) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// occurs when creation of an entity (usually by restful POST or PUT) errors
|
||||||
|
// because some other entity already already exists with a conflicting identifier.
|
||||||
|
// The identifier is not always the id. For example: duplicate filenames
|
||||||
|
// in the same directory will cause conflicts, even with different IDs.
|
||||||
|
ErrAlreadyExists = &Err{msg: "conflict: already exists"}
|
||||||
// currently we have no internal throttling controls. We only try to match
|
// currently we have no internal throttling controls. We only try to match
|
||||||
// external throttling requirements. This sentinel assumes that an external
|
// external throttling requirements. This sentinel assumes that an external
|
||||||
// server has returned one or more throttling errors which has stopped
|
// server has returned one or more throttling errors which has stopped
|
||||||
// operation progress.
|
// operation progress.
|
||||||
ErrApplicationThrottled = &Err{msg: "application throttled"}
|
ErrApplicationThrottled = &Err{msg: "application throttled"}
|
||||||
|
// for use when a short-lived auth token (a jwt or something similar) expires.
|
||||||
|
ErrAuthTokenExpired = &Err{msg: "auth token expired"}
|
||||||
// about what it sounds like: we tried to look for a backup by ID, but the
|
// about what it sounds like: we tried to look for a backup by ID, but the
|
||||||
// storage layer couldn't find anything for that ID.
|
// storage layer couldn't find anything for that ID.
|
||||||
ErrBackupNotFound = &Err{msg: "backup not found"}
|
ErrBackupNotFound = &Err{msg: "backup not found"}
|
||||||
// a catch-all for downstream api auth issues. doesn't matter which api.
|
// a catch-all for downstream api auth issues. doesn't matter which api.
|
||||||
ErrInsufficientAuthorization = &Err{msg: "insufficient authorization"}
|
ErrInsufficientAuthorization = &Err{msg: "insufficient authorization"}
|
||||||
|
// happens when we look up something using an identifier other than a canonical ID
|
||||||
|
// (ex: filtering, searching, etc). This error should only be returned if a unique
|
||||||
|
// result is an expected constraint of the behavior. If it's possible to
|
||||||
|
// opportunistically select one of the many results, no error should get returned.
|
||||||
|
ErrMultipleResultsMatchIdentifier = &Err{msg: "multiple results match the identifier"}
|
||||||
|
// basically what it sounds like: we went looking for something by ID and
|
||||||
|
// it wasn't found. This might be because it was deleted in flight, or
|
||||||
|
// was never created, or some other reason.
|
||||||
|
ErrNotFound = &Err{msg: "not found"}
|
||||||
// specifically for repository creation: if we tried to create a repo and
|
// specifically for repository creation: if we tried to create a repo and
|
||||||
// it already exists with those credentials, we return this error.
|
// it already exists with those credentials, we return this error.
|
||||||
ErrRepoAlreadyExists = &Err{msg: "repository already exists"}
|
ErrRepoAlreadyExists = &Err{msg: "repository already exists"}
|
||||||
@ -72,10 +88,6 @@ var (
|
|||||||
// it, but are told by the external system that the resource is somehow
|
// it, but are told by the external system that the resource is somehow
|
||||||
// unusable.
|
// unusable.
|
||||||
ErrResourceNotAccessible = &Err{msg: "resource not accesible"}
|
ErrResourceNotAccessible = &Err{msg: "resource not accesible"}
|
||||||
// use this when a resource (user, etc; whatever owner is used to own the
|
|
||||||
// data in the given backup) cannot be found in the system by the ID that
|
|
||||||
// the end user provided.
|
|
||||||
ErrResourceOwnerNotFound = &Err{msg: "resource owner not found"}
|
|
||||||
// a service is the set of application data within a given provider. eg:
|
// a service is the set of application data within a given provider. eg:
|
||||||
// if m365 is the provider, then exchange is a service, so is oneDrive.
|
// if m365 is the provider, then exchange is a service, so is oneDrive.
|
||||||
// this sentinel is used to indicate that the service in question is not
|
// this sentinel is used to indicate that the service in question is not
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/pkg/errs/core"
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/repository"
|
"github.com/alcionai/corso/src/pkg/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// map of enums to errors. We might want to re-use an enum for multiple
|
// map of enums to errors. We might want to re-use an enum for multiple
|
||||||
@ -13,24 +12,14 @@ import (
|
|||||||
var externalToInternal = map[*core.Err][]error{
|
var externalToInternal = map[*core.Err][]error{
|
||||||
core.ErrBackupNotFound: {repository.ErrorBackupNotFound},
|
core.ErrBackupNotFound: {repository.ErrorBackupNotFound},
|
||||||
core.ErrRepoAlreadyExists: {repository.ErrorRepoAlreadyExists},
|
core.ErrRepoAlreadyExists: {repository.ErrorRepoAlreadyExists},
|
||||||
core.ErrResourceNotAccessible: {graph.ErrResourceLocked},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrCheck func(error) bool
|
type ErrCheck func(error) bool
|
||||||
|
|
||||||
// map of enums to error comparators. The above map assumes that we
|
|
||||||
// always stack or wrap the sentinel error in the returned error. But in
|
|
||||||
// many places of error handling, we primarily rely on error comparison
|
|
||||||
// checks. This allows us to apply those comparison checks instead of relying
|
|
||||||
// only on sentinels.
|
|
||||||
var externalToInternalCheck = map[*core.Err][]ErrCheck{
|
|
||||||
core.ErrResourceOwnerNotFound: {graph.IsErrItemNotFound},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal returns the internal errors and error checking functions which
|
// Internal returns the internal errors and error checking functions which
|
||||||
// match to the public error enum.
|
// match to the public error enum.
|
||||||
func Internal(ce *core.Err) ([]error, []ErrCheck) {
|
func Internal(ce *core.Err) []error {
|
||||||
return externalToInternal[ce], externalToInternalCheck[ce]
|
return externalToInternal[ce]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is checks if the provided error contains an internal error that matches
|
// Is checks if the provided error contains an internal error that matches
|
||||||
@ -49,14 +38,5 @@ func Is(err error, ce *core.Err) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internalChecks, ok := externalToInternalCheck[ce]
|
|
||||||
if ok {
|
|
||||||
for _, check := range internalChecks {
|
|
||||||
if check(err) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,8 +10,6 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/errs/core"
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/repository"
|
"github.com/alcionai/corso/src/pkg/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
|
||||||
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrUnitSuite struct {
|
type ErrUnitSuite struct {
|
||||||
@ -35,70 +33,11 @@ func (suite *ErrUnitSuite) TestInternal_errs() {
|
|||||||
get: core.ErrBackupNotFound,
|
get: core.ErrBackupNotFound,
|
||||||
expect: []error{repository.ErrorBackupNotFound},
|
expect: []error{repository.ErrorBackupNotFound},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
get: core.ErrResourceNotAccessible,
|
|
||||||
expect: []error{graph.ErrResourceLocked},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.get.Error(), func() {
|
suite.Run(test.get.Error(), func() {
|
||||||
// can't compare func signatures
|
// can't compare func signatures
|
||||||
errs, _ := Internal(test.get)
|
assert.ElementsMatch(suite.T(), test.expect, Internal(test.get))
|
||||||
assert.ElementsMatch(suite.T(), test.expect, errs)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *ErrUnitSuite) TestInternal_checks() {
|
|
||||||
table := []struct {
|
|
||||||
get *core.Err
|
|
||||||
err error
|
|
||||||
expectHasChecks assert.ValueAssertionFunc
|
|
||||||
expect assert.BoolAssertionFunc
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
get: core.ErrRepoAlreadyExists,
|
|
||||||
err: graphTD.ODataErr(string(graph.ApplicationThrottled)),
|
|
||||||
expectHasChecks: assert.Empty,
|
|
||||||
expect: assert.False,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
get: core.ErrBackupNotFound,
|
|
||||||
err: repository.ErrorBackupNotFound,
|
|
||||||
expectHasChecks: assert.Empty,
|
|
||||||
expect: assert.False,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
get: core.ErrResourceOwnerNotFound,
|
|
||||||
err: graphTD.ODataErr(string(graph.ItemNotFound)),
|
|
||||||
expectHasChecks: assert.NotEmpty,
|
|
||||||
expect: assert.True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
get: core.ErrResourceOwnerNotFound,
|
|
||||||
err: graphTD.ODataErr(string(graph.ErrorItemNotFound)),
|
|
||||||
expectHasChecks: assert.NotEmpty,
|
|
||||||
expect: assert.True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range table {
|
|
||||||
suite.Run(test.get.Error(), func() {
|
|
||||||
t := suite.T()
|
|
||||||
|
|
||||||
_, checks := Internal(test.get)
|
|
||||||
|
|
||||||
test.expectHasChecks(t, checks)
|
|
||||||
|
|
||||||
var result bool
|
|
||||||
|
|
||||||
for _, check := range checks {
|
|
||||||
if check(test.err) {
|
|
||||||
result = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test.expect(t, result)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,10 +55,6 @@ func (suite *ErrUnitSuite) TestIs() {
|
|||||||
target: core.ErrBackupNotFound,
|
target: core.ErrBackupNotFound,
|
||||||
err: repository.ErrorBackupNotFound,
|
err: repository.ErrorBackupNotFound,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
target: core.ErrResourceNotAccessible,
|
|
||||||
err: graph.ErrResourceLocked,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.target.Error(), func() {
|
suite.Run(test.target.Error(), func() {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/common/sanitize"
|
"github.com/alcionai/corso/src/internal/common/sanitize"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
)
|
)
|
||||||
@ -143,7 +144,7 @@ func (c Contacts) GetContainerByName(
|
|||||||
// Return an error if multiple container exist (unlikely) or if no container
|
// Return an error if multiple container exist (unlikely) or if no container
|
||||||
// is found.
|
// is found.
|
||||||
if len(gv) != 1 {
|
if len(gv) != 1 {
|
||||||
return nil, clues.StackWC(ctx, graph.ErrMultipleResultsMatchIdentifier).
|
return nil, clues.StackWC(ctx, core.ErrMultipleResultsMatchIdentifier).
|
||||||
With("returned_container_count", len(gv))
|
With("returned_container_count", len(gv))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,11 @@ import (
|
|||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/drives"
|
"github.com/microsoftgraph/msgraph-sdk-go/drives"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ func (c Drives) GetFolderByName(
|
|||||||
|
|
||||||
foundItem, err := builder.Get(ctx, nil)
|
foundItem, err := builder.Get(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if graph.IsErrDeletedInFlight(err) {
|
if errors.Is(err, core.ErrNotFound) {
|
||||||
return nil, graph.Stack(ctx, clues.Stack(ErrFolderNotFound, err))
|
return nil, graph.Stack(ctx, clues.Stack(ErrFolderNotFound, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,10 +184,6 @@ func (c Drives) PostItemInContainer(
|
|||||||
|
|
||||||
newItem, err := builder.Post(ctx, newItem, nil)
|
newItem, err := builder.Post(ctx, newItem, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if graph.IsErrItemAlreadyExistsConflict(err) {
|
|
||||||
return nil, clues.Stack(graph.ErrItemAlreadyExistsConflict, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, graph.Wrap(ctx, err, "creating item in folder")
|
return nil, graph.Wrap(ctx, err, "creating item in folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -114,7 +115,7 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer() {
|
|||||||
onCollision: control.Skip,
|
onCollision: control.Skip,
|
||||||
postItem: folder,
|
postItem: folder,
|
||||||
expectErr: func(t *testing.T, err error) {
|
expectErr: func(t *testing.T, err error) {
|
||||||
assert.ErrorIs(t, err, graph.ErrItemAlreadyExistsConflict, clues.ToCore(err))
|
assert.ErrorIs(t, err, core.ErrAlreadyExists, clues.ToCore(err))
|
||||||
},
|
},
|
||||||
expectItem: func(t *testing.T, i models.DriveItemable) {
|
expectItem: func(t *testing.T, i models.DriveItemable) {
|
||||||
assert.Nil(t, i)
|
assert.Nil(t, i)
|
||||||
@ -165,7 +166,7 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer() {
|
|||||||
onCollision: control.Skip,
|
onCollision: control.Skip,
|
||||||
postItem: file,
|
postItem: file,
|
||||||
expectErr: func(t *testing.T, err error) {
|
expectErr: func(t *testing.T, err error) {
|
||||||
assert.ErrorIs(t, err, graph.ErrItemAlreadyExistsConflict, clues.ToCore(err))
|
assert.ErrorIs(t, err, core.ErrAlreadyExists, clues.ToCore(err))
|
||||||
},
|
},
|
||||||
expectItem: func(t *testing.T, i models.DriveItemable) {
|
expectItem: func(t *testing.T, i models.DriveItemable) {
|
||||||
assert.Nil(t, i)
|
assert.Nil(t, i)
|
||||||
@ -201,7 +202,7 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer() {
|
|||||||
// onCollision: control.Replace,
|
// onCollision: control.Replace,
|
||||||
// postItem: file,
|
// postItem: file,
|
||||||
// expectErr: func(t *testing.T, err error) {
|
// expectErr: func(t *testing.T, err error) {
|
||||||
// assert.ErrorIs(t, err, graph.ErrItemAlreadyExistsConflict, clues.ToCore(err))
|
// assert.ErrorIs(t, err, core.ErrConflictAlreadyExists, clues.ToCore(err))
|
||||||
// },
|
// },
|
||||||
// expectItem: func(t *testing.T, i models.DriveItemable) {
|
// expectItem: func(t *testing.T, i models.DriveItemable) {
|
||||||
// assert.Nil(t, i)
|
// assert.Nil(t, i)
|
||||||
|
|||||||
@ -107,87 +107,89 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// error sentinels & categorization
|
// error categorization
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// These errors are graph specific. That means they don't have a clear parallel in
|
|
||||||
// pkg/errs/core. If these errors need to trickle outward to non-m365 layers, we
|
|
||||||
// need to find a sufficiently coarse errs/core sentinel to use as transformation.
|
|
||||||
var (
|
|
||||||
// The folder or item was deleted between the time we identified
|
|
||||||
// it and when we tried to fetch data for it.
|
|
||||||
ErrDeletedInFlight = clues.New("deleted in flight")
|
|
||||||
|
|
||||||
// ErrItemAlreadyExistsConflict denotes that a post or put attempted to create
|
|
||||||
// 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
|
|
||||||
// when filenames collide in a @microsoft.graph.conflictBehavior=fail request.
|
|
||||||
ErrItemAlreadyExistsConflict = clues.New("item already exists")
|
|
||||||
|
|
||||||
// ErrMultipleResultsMatchIdentifier describes a situation where we're doing a lookup
|
|
||||||
// in some way other than by canonical url ID (ex: filtering, searching, etc).
|
|
||||||
// This error should only be returned if a unique result is an expected constraint
|
|
||||||
// of the call results. If it's possible to opportunistically select one of the many
|
|
||||||
// replies, no error should get returned.
|
|
||||||
ErrMultipleResultsMatchIdentifier = clues.New("multiple results match the identifier")
|
|
||||||
|
|
||||||
// ErrResourceLocked occurs when a resource has had its access locked.
|
|
||||||
// Example case: https://learn.microsoft.com/en-us/sharepoint/manage-lock-status
|
|
||||||
// This makes the resource inaccessible for any Corso operations.
|
|
||||||
ErrResourceLocked = clues.New("resource has been locked and must be unlocked by an administrator")
|
|
||||||
|
|
||||||
ErrTokenExpired = clues.New("jwt token expired")
|
|
||||||
)
|
|
||||||
|
|
||||||
func stackWithCoreErr(ctx context.Context, err error, traceDepth int) error {
|
func stackWithCoreErr(ctx context.Context, err error, traceDepth int) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ode := parseODataErr(err)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case isErrApplicationThrottled(err):
|
case isErrBadJWTToken(ode, err):
|
||||||
|
err = clues.Stack(core.ErrAuthTokenExpired)
|
||||||
|
case isErrApplicationThrottled(ode, err):
|
||||||
err = clues.Stack(core.ErrApplicationThrottled, err)
|
err = clues.Stack(core.ErrApplicationThrottled, err)
|
||||||
case isErrResourceLocked(err):
|
case isErrUserNotFound(ode, err):
|
||||||
|
err = clues.Stack(core.ErrNotFound, err)
|
||||||
|
case isErrResourceLocked(ode, err):
|
||||||
err = clues.Stack(core.ErrResourceNotAccessible, err)
|
err = clues.Stack(core.ErrResourceNotAccessible, err)
|
||||||
case isErrUserNotFound(err):
|
case isErrInsufficientAuthorization(ode, err):
|
||||||
err = clues.Stack(core.ErrResourceOwnerNotFound, err)
|
|
||||||
case isErrInsufficientAuthorization(err):
|
|
||||||
err = clues.Stack(core.ErrInsufficientAuthorization, err)
|
err = clues.Stack(core.ErrInsufficientAuthorization, err)
|
||||||
|
case isErrNotFound(ode, err):
|
||||||
|
err = clues.Stack(core.ErrNotFound, err)
|
||||||
|
case isErrItemAlreadyExists(ode, err):
|
||||||
|
err = clues.Stack(core.ErrAlreadyExists, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return stackWithDepth(ctx, err, 1+traceDepth)
|
return stackWithDepth(ctx, err, 1+traceDepth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isErrApplicationThrottled(err error) bool {
|
// unexported categorizers, for use with stackWithCoreErr
|
||||||
return parseODataErr(err).hasErrorCode(err, ApplicationThrottled)
|
|
||||||
|
func isErrApplicationThrottled(ode oDataErr, err error) bool {
|
||||||
|
return ode.hasErrorCode(err, ApplicationThrottled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrAuthenticationError(err error) bool {
|
func isErrInsufficientAuthorization(ode oDataErr, err error) bool {
|
||||||
return parseODataErr(err).hasErrorCode(err, AuthenticationError)
|
return ode.hasErrorCode(err, AuthorizationRequestDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isErrInsufficientAuthorization(err error) bool {
|
func isErrNotFound(ode oDataErr, err error) bool {
|
||||||
return parseODataErr(err).hasErrorCode(err, AuthorizationRequestDenied)
|
return clues.HasLabel(err, LabelStatus(http.StatusNotFound)) ||
|
||||||
}
|
ode.hasErrorCode(
|
||||||
|
|
||||||
func IsErrDeletedInFlight(err error) bool {
|
|
||||||
if errors.Is(err, ErrDeletedInFlight) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if parseODataErr(err).hasErrorCode(
|
|
||||||
err,
|
err,
|
||||||
ErrorItemNotFound,
|
ErrorItemNotFound,
|
||||||
ItemNotFound,
|
ItemNotFound,
|
||||||
syncFolderNotFound) {
|
syncFolderNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isErrUserNotFound(ode oDataErr, err error) bool {
|
||||||
|
if ode.hasErrorCode(err, RequestResourceNotFound, invalidUser) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ode.hasErrorCode(err, ResourceNotFound) {
|
||||||
|
return strings.Contains(strings.ToLower(ode.Main.Message), "user")
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrItemNotFound(err error) bool {
|
func isErrBadJWTToken(ode oDataErr, err error) bool {
|
||||||
return parseODataErr(err).hasErrorCode(err, ItemNotFound, ErrorItemNotFound)
|
return ode.hasErrorCode(err, invalidAuthenticationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isErrItemAlreadyExists(ode oDataErr, err error) bool {
|
||||||
|
return ode.hasErrorCode(err, nameAlreadyExists)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isErrResourceLocked(ode oDataErr, err error) bool {
|
||||||
|
return ode.hasInnerErrorCode(err, ResourceLocked) ||
|
||||||
|
ode.hasErrorCode(err, NotAllowed) ||
|
||||||
|
ode.errMessageMatchesAllFilters(
|
||||||
|
err,
|
||||||
|
filters.In([]string{"the service principal for resource"}),
|
||||||
|
filters.In([]string{"this indicate that a subscription within the tenant has lapsed"}),
|
||||||
|
filters.In([]string{"preventing tokens from being issued for it"}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// exported categorizers
|
||||||
|
|
||||||
|
func IsErrAuthenticationError(err error) bool {
|
||||||
|
return parseODataErr(err).hasErrorCode(err, AuthenticationError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrInvalidDelta(err error) bool {
|
func IsErrInvalidDelta(err error) bool {
|
||||||
@ -213,20 +215,6 @@ func IsErrExchangeMailFolderNotFound(err error) bool {
|
|||||||
return parseODataErr(err).hasErrorCode(err, ResourceNotFound, ErrorItemNotFound, MailboxNotEnabledForRESTAPI)
|
return parseODataErr(err).hasErrorCode(err, ResourceNotFound, ErrorItemNotFound, MailboxNotEnabledForRESTAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isErrUserNotFound(err error) bool {
|
|
||||||
ode := parseODataErr(err)
|
|
||||||
|
|
||||||
if ode.hasErrorCode(err, RequestResourceNotFound, invalidUser) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if ode.hasErrorCode(err, ResourceNotFound) {
|
|
||||||
return strings.Contains(strings.ToLower(ode.Main.Message), "user")
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsErrInvalidRecipients(err error) bool {
|
func IsErrInvalidRecipients(err error) bool {
|
||||||
return parseODataErr(err).hasErrorCode(err, ErrorInvalidRecipients)
|
return parseODataErr(err).hasErrorCode(err, ErrorInvalidRecipients)
|
||||||
}
|
}
|
||||||
@ -259,16 +247,7 @@ 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)) ||
|
||||||
parseODataErr(err).hasErrorCode(err, invalidAuthenticationToken) ||
|
parseODataErr(err).hasErrorCode(err, invalidAuthenticationToken) ||
|
||||||
errors.Is(err, ErrTokenExpired)
|
errors.Is(err, core.ErrAuthTokenExpired)
|
||||||
}
|
|
||||||
|
|
||||||
func IsErrBadJWTToken(err error) bool {
|
|
||||||
return parseODataErr(err).hasErrorCode(err, invalidAuthenticationToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsErrItemAlreadyExistsConflict(err error) bool {
|
|
||||||
return errors.Is(err, ErrItemAlreadyExistsConflict) ||
|
|
||||||
parseODataErr(err).hasErrorCode(err, nameAlreadyExists)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LabelStatus transforms the provided statusCode into
|
// LabelStatus transforms the provided statusCode into
|
||||||
@ -305,19 +284,6 @@ func IsErrSiteNotFound(err error) bool {
|
|||||||
return parseODataErr(err).hasErrorMessage(err, requestedSiteCouldNotBeFound)
|
return parseODataErr(err).hasErrorMessage(err, requestedSiteCouldNotBeFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isErrResourceLocked(err error) bool {
|
|
||||||
ode := parseODataErr(err)
|
|
||||||
|
|
||||||
return errors.Is(err, ErrResourceLocked) ||
|
|
||||||
ode.hasInnerErrorCode(err, ResourceLocked) ||
|
|
||||||
ode.hasErrorCode(err, NotAllowed) ||
|
|
||||||
ode.errMessageMatchesAllFilters(
|
|
||||||
err,
|
|
||||||
filters.In([]string{"the service principal for resource"}),
|
|
||||||
filters.In([]string{"this indicate that a subscription within the tenant has lapsed"}),
|
|
||||||
filters.In([]string{"preventing tokens from being issued for it"}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsErrSharingDisabled(err error) bool {
|
func IsErrSharingDisabled(err error) bool {
|
||||||
return parseODataErr(err).hasInnerErrorCode(err, sharingDisabled)
|
return parseODataErr(err).hasInnerErrorCode(err, sharingDisabled)
|
||||||
}
|
}
|
||||||
@ -679,7 +645,7 @@ func IsURLExpired(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if expired {
|
if expired {
|
||||||
return clues.StackWC(ctx, ErrTokenExpired), nil
|
return clues.StackWC(ctx, core.ErrAuthTokenExpired), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@ -14,6 +14,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/errs/core"
|
||||||
"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"
|
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/custom"
|
"github.com/alcionai/corso/src/pkg/services/m365/custom"
|
||||||
@ -85,7 +86,8 @@ func (suite *GraphErrorsUnitSuite) TestIsErrApplicationThrottled() {
|
|||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
test.expect(suite.T(), isErrApplicationThrottled(test.err))
|
ode := parseODataErr(test.err)
|
||||||
|
test.expect(suite.T(), isErrApplicationThrottled(ode, test.err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,12 +155,13 @@ func (suite *GraphErrorsUnitSuite) TestIsErrInsufficientAuthorization() {
|
|||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
test.expect(suite.T(), isErrInsufficientAuthorization(test.err))
|
ode := parseODataErr(test.err)
|
||||||
|
test.expect(suite.T(), isErrInsufficientAuthorization(ode, test.err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GraphErrorsUnitSuite) TestIsErrDeletedInFlight() {
|
func (suite *GraphErrorsUnitSuite) TestIsErrNotFound() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
err error
|
err error
|
||||||
@ -174,11 +177,6 @@ func (suite *GraphErrorsUnitSuite) TestIsErrDeletedInFlight() {
|
|||||||
err: assert.AnError,
|
err: assert.AnError,
|
||||||
expect: assert.False,
|
expect: assert.False,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "as",
|
|
||||||
err: ErrDeletedInFlight,
|
|
||||||
expect: assert.True,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "non-matching oDataErr",
|
name: "non-matching oDataErr",
|
||||||
err: graphTD.ODataErr("fnords"),
|
err: graphTD.ODataErr("fnords"),
|
||||||
@ -197,7 +195,8 @@ func (suite *GraphErrorsUnitSuite) TestIsErrDeletedInFlight() {
|
|||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
test.expect(suite.T(), IsErrDeletedInFlight(test.err))
|
ode := parseODataErr(test.err)
|
||||||
|
test.expect(suite.T(), isErrNotFound(ode, test.err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -481,7 +480,8 @@ func (suite *GraphErrorsUnitSuite) TestIsErrUserNotFound() {
|
|||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
test.expect(suite.T(), isErrUserNotFound(test.err))
|
ode := parseODataErr(test.err)
|
||||||
|
test.expect(suite.T(), isErrUserNotFound(ode, test.err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -544,7 +544,7 @@ func (suite *GraphErrorsUnitSuite) TestIsErrUnauthorizedOrBadToken() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "err token expired",
|
name: "err token expired",
|
||||||
err: clues.Stack(assert.AnError, ErrTokenExpired),
|
err: clues.Stack(assert.AnError, core.ErrAuthTokenExpired),
|
||||||
expect: assert.True,
|
expect: assert.True,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -587,11 +587,6 @@ func (suite *GraphErrorsUnitSuite) TestIsErrIsErrBadJWTToken() {
|
|||||||
Label(LabelStatus(http.StatusUnauthorized)),
|
Label(LabelStatus(http.StatusUnauthorized)),
|
||||||
expect: assert.False,
|
expect: assert.False,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "err token expired",
|
|
||||||
err: clues.Stack(assert.AnError, ErrTokenExpired),
|
|
||||||
expect: assert.False,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "oDataErr code invalid auth token ",
|
name: "oDataErr code invalid auth token ",
|
||||||
err: graphTD.ODataErr(string(invalidAuthenticationToken)),
|
err: graphTD.ODataErr(string(invalidAuthenticationToken)),
|
||||||
@ -600,7 +595,8 @@ func (suite *GraphErrorsUnitSuite) TestIsErrIsErrBadJWTToken() {
|
|||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
test.expect(suite.T(), IsErrBadJWTToken(test.err))
|
ode := parseODataErr(test.err)
|
||||||
|
test.expect(suite.T(), isErrBadJWTToken(ode, test.err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -879,45 +875,6 @@ func (suite *GraphErrorsUnitSuite) TestGraphStack_labels() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GraphErrorsUnitSuite) TestIsErrItemNotFound() {
|
|
||||||
table := []struct {
|
|
||||||
name string
|
|
||||||
err error
|
|
||||||
expect assert.BoolAssertionFunc
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nil",
|
|
||||||
err: nil,
|
|
||||||
expect: assert.False,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-matching",
|
|
||||||
err: assert.AnError,
|
|
||||||
expect: assert.False,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-matching oDataErr",
|
|
||||||
err: graphTD.ODataErr("fnords"),
|
|
||||||
expect: assert.False,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "item not found oDataErr",
|
|
||||||
err: graphTD.ODataErr(string(ItemNotFound)),
|
|
||||||
expect: assert.True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "error item not found oDataErr",
|
|
||||||
err: graphTD.ODataErr(string(ErrorItemNotFound)),
|
|
||||||
expect: assert.True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range table {
|
|
||||||
suite.Run(test.name, func() {
|
|
||||||
test.expect(suite.T(), IsErrItemNotFound(test.err))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *GraphErrorsUnitSuite) TestIsErrResourceLocked() {
|
func (suite *GraphErrorsUnitSuite) TestIsErrResourceLocked() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
@ -960,15 +917,11 @@ func (suite *GraphErrorsUnitSuite) TestIsErrResourceLocked() {
|
|||||||
"deadbeef-7f1e-4578-8215-36004a2c935c Timestamp: 2023-12-05 19:31:01Z"),
|
"deadbeef-7f1e-4578-8215-36004a2c935c Timestamp: 2023-12-05 19:31:01Z"),
|
||||||
expect: assert.True,
|
expect: assert.True,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "matching err sentinel",
|
|
||||||
err: ErrResourceLocked,
|
|
||||||
expect: assert.True,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
test.expect(suite.T(), isErrResourceLocked(test.err))
|
ode := parseODataErr(test.err)
|
||||||
|
test.expect(suite.T(), isErrResourceLocked(ode, test.err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,11 +13,13 @@ import (
|
|||||||
khttp "github.com/microsoft/kiota-http-go"
|
khttp "github.com/microsoft/kiota-http-go"
|
||||||
msgraphsdkgo "github.com/microsoftgraph/msgraph-sdk-go"
|
msgraphsdkgo "github.com/microsoftgraph/msgraph-sdk-go"
|
||||||
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
|
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/crash"
|
"github.com/alcionai/corso/src/internal/common/crash"
|
||||||
"github.com/alcionai/corso/src/internal/common/idname"
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
"github.com/alcionai/corso/src/internal/events"
|
"github.com/alcionai/corso/src/internal/events"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/filters"
|
"github.com/alcionai/corso/src/pkg/filters"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -413,7 +415,7 @@ func (aw *adapterWrap) Send(
|
|||||||
if IsErrConnectionReset(err) || connectionEnded.Compare(err.Error()) {
|
if IsErrConnectionReset(err) || connectionEnded.Compare(err.Error()) {
|
||||||
logger.Ctx(ictx).Debug("http connection error")
|
logger.Ctx(ictx).Debug("http connection error")
|
||||||
events.Inc(events.APICall, "connectionerror")
|
events.Inc(events.APICall, "connectionerror")
|
||||||
} else if IsErrBadJWTToken(err) {
|
} else if errors.Is(err, core.ErrAuthTokenExpired) {
|
||||||
logger.Ctx(ictx).Debug("bad jwt token")
|
logger.Ctx(ictx).Debug("bad jwt token")
|
||||||
events.Inc(events.APICall, "badjwttoken")
|
events.Inc(events.APICall, "badjwttoken")
|
||||||
} else if requestInfo.Method.String() == http.MethodGet && IsErrInvalidRequest(err) {
|
} else if requestInfo.Method.String() == http.MethodGet && IsErrInvalidRequest(err) {
|
||||||
|
|||||||
@ -22,6 +22,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"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
|
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -313,14 +314,14 @@ func (suite *GraphIntgSuite) TestAdapterWrap_retriesBadJWTToken() {
|
|||||||
_, err = users.
|
_, err = users.
|
||||||
NewItemCalendarsItemEventsDeltaRequestBuilder("https://graph.microsoft.com/fnords/beaux/regard", adpt).
|
NewItemCalendarsItemEventsDeltaRequestBuilder("https://graph.microsoft.com/fnords/beaux/regard", adpt).
|
||||||
Get(ctx, nil)
|
Get(ctx, nil)
|
||||||
assert.True(t, IsErrBadJWTToken(err), clues.ToCore(err))
|
assert.ErrorIs(t, err, core.ErrAuthTokenExpired, clues.ToCore(err))
|
||||||
assert.Equal(t, 4, retryInc, "number of retries")
|
assert.Equal(t, 4, retryInc, "number of retries")
|
||||||
|
|
||||||
retryInc = 0
|
retryInc = 0
|
||||||
|
|
||||||
// the query doesn't matter
|
// the query doesn't matter
|
||||||
_, err = NewService(adpt).Client().Users().Get(ctx, nil)
|
_, err = NewService(adpt).Client().Users().Get(ctx, nil)
|
||||||
assert.True(t, IsErrBadJWTToken(err), clues.ToCore(err))
|
assert.ErrorIs(t, err, core.ErrAuthTokenExpired, clues.ToCore(err))
|
||||||
assert.Equal(t, 4, retryInc, "number of retries")
|
assert.Equal(t, 4, retryInc, "number of retries")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -218,9 +218,9 @@ func getGroupFromResponse(ctx context.Context, resp models.GroupCollectionRespon
|
|||||||
vs := resp.GetValue()
|
vs := resp.GetValue()
|
||||||
|
|
||||||
if len(vs) == 0 {
|
if len(vs) == 0 {
|
||||||
return nil, clues.StackWC(ctx, core.ErrResourceOwnerNotFound)
|
return nil, clues.StackWC(ctx, core.ErrNotFound)
|
||||||
} else if len(vs) > 1 {
|
} else if len(vs) > 1 {
|
||||||
return nil, clues.StackWC(ctx, graph.ErrMultipleResultsMatchIdentifier)
|
return nil, clues.StackWC(ctx, core.ErrMultipleResultsMatchIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
return vs[0], nil
|
return vs[0], nil
|
||||||
|
|||||||
@ -203,7 +203,7 @@ func (suite *GroupsIntgSuite) TestGroups_GetByID() {
|
|||||||
name: "invalid id",
|
name: "invalid id",
|
||||||
id: uuid.NewString(),
|
id: uuid.NewString(),
|
||||||
expectErr: func(t *testing.T, err error) {
|
expectErr: func(t *testing.T, err error) {
|
||||||
assert.ErrorIs(t, err, core.ErrResourceOwnerNotFound, clues.ToCore(err))
|
assert.ErrorIs(t, err, core.ErrNotFound, clues.ToCore(err))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -217,7 +217,7 @@ func (suite *GroupsIntgSuite) TestGroups_GetByID() {
|
|||||||
name: "invalid displayName",
|
name: "invalid displayName",
|
||||||
id: "jabberwocky",
|
id: "jabberwocky",
|
||||||
expectErr: func(t *testing.T, err error) {
|
expectErr: func(t *testing.T, err error) {
|
||||||
assert.ErrorIs(t, err, core.ErrResourceOwnerNotFound, clues.ToCore(err))
|
assert.ErrorIs(t, err, core.ErrNotFound, clues.ToCore(err))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,10 +105,6 @@ func (c Lists) PostDrive(
|
|||||||
Lists()
|
Lists()
|
||||||
|
|
||||||
newList, err := builder.Post(ctx, list, nil)
|
newList, err := builder.Post(ctx, list, nil)
|
||||||
if graph.IsErrItemAlreadyExistsConflict(err) {
|
|
||||||
return nil, clues.StackWC(ctx, graph.ErrItemAlreadyExistsConflict, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, graph.Wrap(ctx, err, "creating documentLibrary list")
|
return nil, graph.Wrap(ctx, err, "creating documentLibrary list")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,8 +18,8 @@ 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"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
|
||||||
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
|
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -553,7 +553,7 @@ func (suite *ListsAPIIntgSuite) TestLists_PostDrive() {
|
|||||||
|
|
||||||
// second post, same name, should error on name conflict]
|
// second post, same name, should error on name conflict]
|
||||||
_, err = acl.PostDrive(ctx, siteID, driveName)
|
_, err = acl.PostDrive(ctx, siteID, driveName)
|
||||||
require.ErrorIs(t, err, graph.ErrItemAlreadyExistsConflict, clues.ToCore(err))
|
require.ErrorIs(t, err, core.ErrAlreadyExists, clues.ToCore(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ListsAPIIntgSuite) TestLists_GetListByID() {
|
func (suite *ListsAPIIntgSuite) TestLists_GetListByID() {
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/sanitize"
|
"github.com/alcionai/corso/src/internal/common/sanitize"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/dttm"
|
"github.com/alcionai/corso/src/pkg/dttm"
|
||||||
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
@ -171,7 +172,7 @@ func (c Mail) GetContainerByName(
|
|||||||
// Return an error if multiple container exist (unlikely) or if no container
|
// Return an error if multiple container exist (unlikely) or if no container
|
||||||
// is found.
|
// is found.
|
||||||
if len(gv) != 1 {
|
if len(gv) != 1 {
|
||||||
return nil, clues.StackWC(ctx, graph.ErrMultipleResultsMatchIdentifier).
|
return nil, clues.StackWC(ctx, core.ErrMultipleResultsMatchIdentifier).
|
||||||
With("returned_container_count", len(gv))
|
With("returned_container_count", len(gv))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,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/errs/core"
|
||||||
"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"
|
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
|
||||||
)
|
)
|
||||||
@ -635,7 +636,7 @@ func (suite *PagerUnitSuite) TestGetAddedAndRemovedItemIDs() {
|
|||||||
t: t,
|
t: t,
|
||||||
pages: []pageResult{
|
pages: []pageResult{
|
||||||
{
|
{
|
||||||
err: graph.ErrDeletedInFlight,
|
err: core.ErrNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
validModTimes: validModTimes,
|
validModTimes: validModTimes,
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/microsoftgraph/msgraph-sdk-go/sites"
|
"github.com/microsoftgraph/msgraph-sdk-go/sites"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/pkg/errs/core"
|
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
)
|
)
|
||||||
@ -150,13 +149,6 @@ func (c Sites) GetByID(
|
|||||||
Get(ctx, options)
|
Get(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := graph.Wrap(ctx, err, "getting site by id")
|
err := graph.Wrap(ctx, err, "getting site by id")
|
||||||
|
|
||||||
// a 404 when getting sites by ID returns an itemNotFound
|
|
||||||
// error code, instead of something more sensible.
|
|
||||||
if graph.IsErrItemNotFound(err) {
|
|
||||||
err = clues.Stack(core.ErrResourceOwnerNotFound, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,13 +184,6 @@ func (c Sites) GetByID(
|
|||||||
Get(ctx, nil)
|
Get(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := graph.Wrap(ctx, err, "getting site by weburl")
|
err := graph.Wrap(ctx, err, "getting site by weburl")
|
||||||
|
|
||||||
// a 404 when getting sites by ID returns an itemNotFound
|
|
||||||
// error code, instead of something more sensible.
|
|
||||||
if graph.IsErrItemNotFound(err) {
|
|
||||||
err = clues.Stack(core.ErrResourceOwnerNotFound, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -181,7 +181,7 @@ func (suite *SitesIntgSuite) TestSites_GetByID() {
|
|||||||
name: "random id",
|
name: "random id",
|
||||||
id: uuid.NewString() + "," + uuid.NewString(),
|
id: uuid.NewString() + "," + uuid.NewString(),
|
||||||
expectErr: func(t *testing.T, err error) bool {
|
expectErr: func(t *testing.T, err error) bool {
|
||||||
assert.ErrorIs(t, err, core.ErrResourceOwnerNotFound, clues.ToCore(err))
|
assert.ErrorIs(t, err, core.ErrNotFound, clues.ToCore(err))
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -213,7 +213,7 @@ func (suite *SitesIntgSuite) TestSites_GetByID() {
|
|||||||
name: "well formed url, no sites match",
|
name: "well formed url, no sites match",
|
||||||
id: modifiedSiteURL,
|
id: modifiedSiteURL,
|
||||||
expectErr: func(t *testing.T, err error) bool {
|
expectErr: func(t *testing.T, err error) bool {
|
||||||
assert.ErrorIs(t, err, core.ErrResourceOwnerNotFound, clues.ToCore(err))
|
assert.ErrorIs(t, err, core.ErrNotFound, clues.ToCore(err))
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -194,11 +194,13 @@ func EvaluateMailboxError(err error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// must occur before MailFolderNotFound, due to overlapping cases.
|
// must occur before MailFolderNotFound, due to overlapping cases.
|
||||||
if errors.Is(err, core.ErrResourceOwnerNotFound) || errors.Is(err, core.ErrResourceNotAccessible) {
|
if errors.Is(err, core.ErrResourceNotAccessible) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if graph.IsErrExchangeMailFolderNotFound(err) || graph.IsErrAuthenticationError(err) {
|
if errors.Is(err, core.ErrNotFound) ||
|
||||||
|
graph.IsErrExchangeMailFolderNotFound(err) ||
|
||||||
|
graph.IsErrAuthenticationError(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -81,9 +81,9 @@ func (suite *UsersUnitSuite) TestEvaluateMailboxError() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mail inbox err - user not found",
|
name: "mail inbox err - user not found",
|
||||||
err: core.ErrResourceOwnerNotFound,
|
err: core.ErrNotFound,
|
||||||
expect: func(t *testing.T, err error) {
|
expect: func(t *testing.T, err error) {
|
||||||
assert.ErrorIs(t, err, core.ErrResourceOwnerNotFound, clues.ToCore(err))
|
assert.NoError(t, err, clues.ToCore(err))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -108,8 +108,8 @@ func (suite *GroupsIntgSuite) TestGroupByID_notFound() {
|
|||||||
|
|
||||||
group, err := suite.cli.GroupByID(ctx, uuid.NewString())
|
group, err := suite.cli.GroupByID(ctx, uuid.NewString())
|
||||||
require.Nil(t, group)
|
require.Nil(t, group)
|
||||||
require.ErrorIs(t, err, core.ErrResourceOwnerNotFound, clues.ToCore(err))
|
require.ErrorIs(t, err, core.ErrNotFound, clues.ToCore(err))
|
||||||
require.True(t, errs.Is(err, core.ErrResourceOwnerNotFound))
|
require.True(t, errs.Is(err, core.ErrNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsIntgSuite) TestGroups() {
|
func (suite *GroupsIntgSuite) TestGroups() {
|
||||||
|
|||||||
@ -175,13 +175,14 @@ func (suite *userIntegrationSuite) TestUserGetMailboxInfo() {
|
|||||||
name: "invalid user",
|
name: "invalid user",
|
||||||
user: uuid.NewString(),
|
user: uuid.NewString(),
|
||||||
expect: func(t *testing.T, info api.MailboxInfo) {
|
expect: func(t *testing.T, info api.MailboxInfo) {
|
||||||
mi := api.MailboxInfo{
|
require.NotNil(t, info)
|
||||||
ErrGetMailBoxSetting: []error{},
|
assert.Contains(t, info.ErrGetMailBoxSetting, api.ErrMailBoxNotFound)
|
||||||
}
|
|
||||||
|
|
||||||
require.Equal(t, mi, info)
|
|
||||||
},
|
},
|
||||||
expectErr: require.Error,
|
// may seem odd, but we assume the user themselves
|
||||||
|
// has already been vetted, which turns this into a
|
||||||
|
// notFound error in the same way a mailboxNotFound
|
||||||
|
// is handled.
|
||||||
|
expectErr: require.NoError,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user