add namespace to fault items (#3415)

prevent fault items and skips from clobbering eachother by adding a namespace to the fault item that defines a deduplication boundary.  This allows multiple items in a single service operation to have identical ids so long as their namespace differs.

---

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

- [x]  No

#### Type of change

- [x] 🐛 Bugfix
- [x] 🤖 Supportability/Tests

#### Issue(s)

* #3283

#### Test Plan

- [x] 💪 Manual
- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-05-16 11:18:51 -06:00 committed by GitHub
parent 5ca595b7b0
commit 6febc3ce5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 99 additions and 61 deletions

View File

@ -318,6 +318,7 @@ func (i *Item) ModTime() time.Time { return i.info.Modified() }
// getDriveItemContent fetch drive item's contents with retries // getDriveItemContent fetch drive item's contents with retries
func (oc *Collection) getDriveItemContent( func (oc *Collection) getDriveItemContent(
ctx context.Context, ctx context.Context,
driveID string,
item models.DriveItemable, item models.DriveItemable,
errs *fault.Bus, errs *fault.Bus,
) (io.ReadCloser, error) { ) (io.ReadCloser, error) {
@ -338,14 +339,14 @@ func (oc *Collection) getDriveItemContent(
if err != nil { if err != nil {
if clues.HasLabel(err, graph.LabelsMalware) || (item != nil && item.GetMalware() != nil) { if clues.HasLabel(err, graph.LabelsMalware) || (item != nil && item.GetMalware() != nil) {
logger.CtxErr(ctx, err).With("skipped_reason", fault.SkipMalware).Info("item flagged as malware") logger.CtxErr(ctx, err).With("skipped_reason", fault.SkipMalware).Info("item flagged as malware")
el.AddSkip(fault.FileSkip(fault.SkipMalware, itemID, itemName, graph.ItemInfo(item))) el.AddSkip(fault.FileSkip(fault.SkipMalware, driveID, itemID, itemName, graph.ItemInfo(item)))
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)) || graph.IsErrDeletedInFlight(err) {
logger.CtxErr(ctx, err).With("skipped_reason", fault.SkipNotFound).Info("item not found") logger.CtxErr(ctx, err).With("skipped_reason", fault.SkipNotFound).Info("item not found")
el.AddSkip(fault.FileSkip(fault.SkipNotFound, itemID, itemName, graph.ItemInfo(item))) el.AddSkip(fault.FileSkip(fault.SkipNotFound, driveID, itemID, itemName, graph.ItemInfo(item)))
return nil, clues.Wrap(err, "deleted item").Label(graph.LabelsSkippable) return nil, clues.Wrap(err, "deleted item").Label(graph.LabelsSkippable)
} }
@ -360,7 +361,7 @@ func (oc *Collection) getDriveItemContent(
// restore, or we have to handle it separately by somehow // restore, or we have to handle it separately by somehow
// deleting the entire collection. // deleting the entire collection.
logger.CtxErr(ctx, err).With("skipped_reason", fault.SkipBigOneNote).Info("max OneNote file size exceeded") logger.CtxErr(ctx, err).With("skipped_reason", fault.SkipBigOneNote).Info("max OneNote file size exceeded")
el.AddSkip(fault.FileSkip(fault.SkipBigOneNote, itemID, itemName, graph.ItemInfo(item))) el.AddSkip(fault.FileSkip(fault.SkipBigOneNote, driveID, itemID, itemName, graph.ItemInfo(item)))
return nil, clues.Wrap(err, "max oneNote item").Label(graph.LabelsSkippable) return nil, clues.Wrap(err, "max oneNote item").Label(graph.LabelsSkippable)
} }
@ -528,7 +529,7 @@ func (oc *Collection) populateItems(ctx context.Context, errs *fault.Bus) {
// attempts to read bytes. Assumption is that kopia will check things // attempts to read bytes. Assumption is that kopia will check things
// like file modtimes before attempting to read. // like file modtimes before attempting to read.
itemReader := lazy.NewLazyReadCloser(func() (io.ReadCloser, error) { itemReader := lazy.NewLazyReadCloser(func() (io.ReadCloser, error) {
itemData, err := oc.getDriveItemContent(ctx, item, errs) itemData, err := oc.getDriveItemContent(ctx, oc.driveID, item, errs)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -710,7 +710,7 @@ func (suite *GetDriveItemUnitTestSuite) TestGetDriveItem_error() {
return item, nil return item, nil
} }
_, err := col.getDriveItemContent(ctx, item, errs) _, err := col.getDriveItemContent(ctx, "driveID", item, errs)
if test.err == nil { if test.err == nil {
assert.NoError(t, err, clues.ToCore(err)) assert.NoError(t, err, clues.ToCore(err))
return return

View File

@ -717,10 +717,10 @@ func (c *Collections) UpdateCollections(
if item.GetMalware() != nil { if item.GetMalware() != nil {
addtl := graph.ItemInfo(item) addtl := graph.ItemInfo(item)
skip := fault.FileSkip(fault.SkipMalware, itemID, itemName, addtl) skip := fault.FileSkip(fault.SkipMalware, driveID, itemID, itemName, addtl)
if isFolder { if isFolder {
skip = fault.ContainerSkip(fault.SkipMalware, itemID, itemName, addtl) skip = fault.ContainerSkip(fault.SkipMalware, driveID, itemID, itemName, addtl)
} }
errs.AddSkip(skip) errs.AddSkip(skip)

View File

@ -108,8 +108,8 @@ func (suite *StreamStoreIntgSuite) TestStreamer() {
bus := fault.New(false) bus := fault.New(false)
bus.Fail(clues.New("foo")) bus.Fail(clues.New("foo"))
bus.AddRecoverable(clues.New("bar")) bus.AddRecoverable(clues.New("bar"))
bus.AddRecoverable(fault.FileErr(clues.New("file"), "file-id", "file-name", map[string]any{"foo": "bar"})) bus.AddRecoverable(fault.FileErr(clues.New("file"), "ns", "file-id", "file-name", map[string]any{"foo": "bar"}))
bus.AddSkip(fault.FileSkip(fault.SkipMalware, "file-id", "file-name", map[string]any{"foo": "bar"})) bus.AddSkip(fault.FileSkip(fault.SkipMalware, "ns", "file-id", "file-name", map[string]any{"foo": "bar"}))
fe := bus.Errors() fe := bus.Errors()
return fe return fe
@ -137,8 +137,8 @@ func (suite *StreamStoreIntgSuite) TestStreamer() {
bus := fault.New(false) bus := fault.New(false)
bus.Fail(clues.New("foo")) bus.Fail(clues.New("foo"))
bus.AddRecoverable(clues.New("bar")) bus.AddRecoverable(clues.New("bar"))
bus.AddRecoverable(fault.FileErr(clues.New("file"), "file-id", "file-name", map[string]any{"foo": "bar"})) bus.AddRecoverable(fault.FileErr(clues.New("file"), "ns", "file-id", "file-name", map[string]any{"foo": "bar"}))
bus.AddSkip(fault.FileSkip(fault.SkipMalware, "file-id", "file-name", map[string]any{"foo": "bar"})) bus.AddSkip(fault.FileSkip(fault.SkipMalware, "ns", "file-id", "file-name", map[string]any{"foo": "bar"}))
fe := bus.Errors() fe := bus.Errors()
return fe return fe

View File

@ -428,6 +428,7 @@ func ExampleBus_AddSkip() {
// error. Our only option is to skip it. // error. Our only option is to skip it.
errs.AddSkip(fault.FileSkip( errs.AddSkip(fault.FileSkip(
fault.SkipMalware, fault.SkipMalware,
"deduplication-namespace",
"file-id", "file-id",
"file-name", "file-name",
map[string]any{"foo": "bar"}, map[string]any{"foo": "bar"},

View File

@ -204,7 +204,7 @@ type Errors struct {
// Items are the reduction of all errors (both the failure and the // Items are the reduction of all errors (both the failure and the
// recovered values) in the Errors struct into a slice of items, // recovered values) in the Errors struct into a slice of items,
// deduplicated by their ID. // deduplicated by their Namespace + ID.
Items []Item `json:"items"` Items []Item `json:"items"`
// Skipped is the accumulation of skipped items. Skipped items // Skipped is the accumulation of skipped items. Skipped items
@ -218,7 +218,8 @@ type Errors struct {
} }
// itemsIn reduces all errors (both the failure and recovered values) // itemsIn reduces all errors (both the failure and recovered values)
// in the Errors struct into a slice of items, deduplicated by their ID. // in the Errors struct into a slice of items, deduplicated by their
// Namespace + ID.
// Any non-item error is serialized to a clues.ErrCore and returned in // Any non-item error is serialized to a clues.ErrCore and returned in
// the second list. // the second list.
func itemsIn(failure error, recovered []error) ([]Item, []*clues.ErrCore) { func itemsIn(failure error, recovered []error) ([]Item, []*clues.ErrCore) {
@ -234,12 +235,12 @@ func itemsIn(failure error, recovered []error) ([]Item, []*clues.ErrCore) {
continue continue
} }
is[ie.ID] = *ie is[ie.dedupeID()] = *ie
} }
var ie *Item var ie *Item
if errors.As(failure, &ie) { if errors.As(failure, &ie) {
is[ie.ID] = *ie is[ie.dedupeID()] = *ie
} }
return maps.Values(is), non return maps.Values(is), non

View File

@ -193,7 +193,7 @@ func (suite *FaultErrorsUnitSuite) TestAddSkip() {
n.AddRecoverable(assert.AnError) n.AddRecoverable(assert.AnError)
assert.Len(t, n.Skipped(), 0) assert.Len(t, n.Skipped(), 0)
n.AddSkip(fault.OwnerSkip(fault.SkipMalware, "id", "name", nil)) n.AddSkip(fault.OwnerSkip(fault.SkipMalware, "ns", "id", "name", nil))
assert.Len(t, n.Skipped(), 1) assert.Len(t, n.Skipped(), 1)
} }
@ -262,12 +262,12 @@ func (suite *FaultErrorsUnitSuite) TestErrors_Items() {
name: "failure item", name: "failure item",
errs: func() *fault.Errors { errs: func() *fault.Errors {
b := fault.New(false) b := fault.New(false)
b.Fail(fault.OwnerErr(ae, "id", "name", addtl)) b.Fail(fault.OwnerErr(ae, "ns", "id", "name", addtl))
b.AddRecoverable(ae) b.AddRecoverable(ae)
return b.Errors() return b.Errors()
}, },
expectItems: []fault.Item{*fault.OwnerErr(ae, "id", "name", addtl)}, expectItems: []fault.Item{*fault.OwnerErr(ae, "ns", "id", "name", addtl)},
expectRecoverable: noncore, expectRecoverable: noncore,
}, },
{ {
@ -275,25 +275,40 @@ func (suite *FaultErrorsUnitSuite) TestErrors_Items() {
errs: func() *fault.Errors { errs: func() *fault.Errors {
b := fault.New(false) b := fault.New(false)
b.Fail(ae) b.Fail(ae)
b.AddRecoverable(fault.OwnerErr(ae, "id", "name", addtl)) b.AddRecoverable(fault.OwnerErr(ae, "ns", "id", "name", addtl))
return b.Errors() return b.Errors()
}, },
expectItems: []fault.Item{*fault.OwnerErr(ae, "id", "name", addtl)}, expectItems: []fault.Item{*fault.OwnerErr(ae, "ns", "id", "name", addtl)},
expectRecoverable: []*clues.ErrCore{}, expectRecoverable: []*clues.ErrCore{},
}, },
{ {
name: "two items", name: "two items",
errs: func() *fault.Errors { errs: func() *fault.Errors {
b := fault.New(false) b := fault.New(false)
b.Fail(fault.OwnerErr(ae, "oid", "name", addtl)) b.Fail(fault.OwnerErr(ae, "ns", "oid", "name", addtl))
b.AddRecoverable(fault.FileErr(ae, "fid", "name", addtl)) b.AddRecoverable(fault.FileErr(ae, "ns", "fid", "name", addtl))
return b.Errors() return b.Errors()
}, },
expectItems: []fault.Item{ expectItems: []fault.Item{
*fault.OwnerErr(ae, "oid", "name", addtl), *fault.OwnerErr(ae, "ns", "oid", "name", addtl),
*fault.FileErr(ae, "fid", "name", addtl), *fault.FileErr(ae, "ns", "fid", "name", addtl),
},
expectRecoverable: []*clues.ErrCore{},
},
{
name: "two items - diff namespace same id",
errs: func() *fault.Errors {
b := fault.New(false)
b.Fail(fault.OwnerErr(ae, "ns", "id", "name", addtl))
b.AddRecoverable(fault.FileErr(ae, "ns2", "id", "name", addtl))
return b.Errors()
},
expectItems: []fault.Item{
*fault.OwnerErr(ae, "ns", "id", "name", addtl),
*fault.FileErr(ae, "ns2", "id", "name", addtl),
}, },
expectRecoverable: []*clues.ErrCore{}, expectRecoverable: []*clues.ErrCore{},
}, },
@ -301,13 +316,13 @@ func (suite *FaultErrorsUnitSuite) TestErrors_Items() {
name: "duplicate items - failure priority", name: "duplicate items - failure priority",
errs: func() *fault.Errors { errs: func() *fault.Errors {
b := fault.New(false) b := fault.New(false)
b.Fail(fault.OwnerErr(ae, "id", "name", addtl)) b.Fail(fault.OwnerErr(ae, "ns", "id", "name", addtl))
b.AddRecoverable(fault.FileErr(ae, "id", "name", addtl)) b.AddRecoverable(fault.FileErr(ae, "ns", "id", "name", addtl))
return b.Errors() return b.Errors()
}, },
expectItems: []fault.Item{ expectItems: []fault.Item{
*fault.OwnerErr(ae, "id", "name", addtl), *fault.OwnerErr(ae, "ns", "id", "name", addtl),
}, },
expectRecoverable: []*clues.ErrCore{}, expectRecoverable: []*clues.ErrCore{},
}, },
@ -316,13 +331,13 @@ func (suite *FaultErrorsUnitSuite) TestErrors_Items() {
errs: func() *fault.Errors { errs: func() *fault.Errors {
b := fault.New(false) b := fault.New(false)
b.Fail(ae) b.Fail(ae)
b.AddRecoverable(fault.FileErr(ae, "fid", "name", addtl)) b.AddRecoverable(fault.FileErr(ae, "ns", "fid", "name", addtl))
b.AddRecoverable(fault.FileErr(ae, "fid", "name2", addtl)) b.AddRecoverable(fault.FileErr(ae, "ns", "fid", "name2", addtl))
return b.Errors() return b.Errors()
}, },
expectItems: []fault.Item{ expectItems: []fault.Item{
*fault.FileErr(ae, "fid", "name2", addtl), *fault.FileErr(ae, "ns", "fid", "name2", addtl),
}, },
expectRecoverable: []*clues.ErrCore{}, expectRecoverable: []*clues.ErrCore{},
}, },
@ -331,13 +346,13 @@ func (suite *FaultErrorsUnitSuite) TestErrors_Items() {
errs: func() *fault.Errors { errs: func() *fault.Errors {
b := fault.New(false) b := fault.New(false)
b.Fail(ae) b.Fail(ae)
b.AddRecoverable(fault.FileErr(ae, "fid", "name", addtl)) b.AddRecoverable(fault.FileErr(ae, "ns", "fid", "name", addtl))
b.AddRecoverable(ae) b.AddRecoverable(ae)
return b.Errors() return b.Errors()
}, },
expectItems: []fault.Item{ expectItems: []fault.Item{
*fault.FileErr(ae, "fid", "name", addtl), *fault.FileErr(ae, "ns", "fid", "name", addtl),
}, },
expectRecoverable: noncore, expectRecoverable: noncore,
}, },

View File

@ -49,6 +49,12 @@ var (
// by the end user (cli or sdk) for surfacing human-readable and // by the end user (cli or sdk) for surfacing human-readable and
// identifiable points of failure. // identifiable points of failure.
type Item struct { type Item struct {
// deduplication namespace; the maximally-unique boundary of the
// item ID. The scope of this boundary depends on the service.
// ex: exchange items are unique within their category, drive items
// are only unique within a given drive.
Namespace string `json:"namespace"`
// deduplication identifier; the ID of the observed item. // deduplication identifier; the ID of the observed item.
ID string `json:"id"` ID string `json:"id"`
@ -72,6 +78,12 @@ type Item struct {
Additional map[string]any `json:"additional"` Additional map[string]any `json:"additional"`
} }
// dedupeID is the id used to deduplicate items when aggreagating
// errors in fault.Errors().
func (i *Item) dedupeID() string {
return i.Namespace + i.ID
}
// Error complies with the error interface. // Error complies with the error interface.
func (i *Item) Error() string { func (i *Item) Error() string {
if i == nil { if i == nil {
@ -111,23 +123,24 @@ func (i Item) Values() []string {
} }
// ContainerErr produces a Container-type Item for tracking erroneous items // ContainerErr produces a Container-type Item for tracking erroneous items
func ContainerErr(cause error, id, name string, addtl map[string]any) *Item { func ContainerErr(cause error, namespace, id, name string, addtl map[string]any) *Item {
return itemErr(ContainerType, cause, id, name, addtl) return itemErr(ContainerType, cause, namespace, id, name, addtl)
} }
// FileErr produces a File-type Item for tracking erroneous items. // FileErr produces a File-type Item for tracking erroneous items.
func FileErr(cause error, id, name string, addtl map[string]any) *Item { func FileErr(cause error, namespace, id, name string, addtl map[string]any) *Item {
return itemErr(FileType, cause, id, name, addtl) return itemErr(FileType, cause, namespace, id, name, addtl)
} }
// OnwerErr produces a ResourceOwner-type Item for tracking erroneous items. // OnwerErr produces a ResourceOwner-type Item for tracking erroneous items.
func OwnerErr(cause error, id, name string, addtl map[string]any) *Item { func OwnerErr(cause error, namespace, id, name string, addtl map[string]any) *Item {
return itemErr(ResourceOwnerType, cause, id, name, addtl) return itemErr(ResourceOwnerType, cause, namespace, id, name, addtl)
} }
// itemErr produces a Item of the provided type for tracking erroneous items. // itemErr produces a Item of the provided type for tracking erroneous items.
func itemErr(t itemType, cause error, id, name string, addtl map[string]any) *Item { func itemErr(t itemType, cause error, namespace, id, name string, addtl map[string]any) *Item {
return &Item{ return &Item{
Namespace: namespace,
ID: id, ID: id,
Name: name, Name: name,
Type: t, Type: t,
@ -228,24 +241,25 @@ func (s Skipped) Values() []string {
} }
// ContainerSkip produces a Container-kind Item for tracking skipped items. // ContainerSkip produces a Container-kind Item for tracking skipped items.
func ContainerSkip(cause skipCause, id, name string, addtl map[string]any) *Skipped { func ContainerSkip(cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped {
return itemSkip(ContainerType, cause, id, name, addtl) return itemSkip(ContainerType, cause, namespace, id, name, addtl)
} }
// FileSkip produces a File-kind Item for tracking skipped items. // FileSkip produces a File-kind Item for tracking skipped items.
func FileSkip(cause skipCause, id, name string, addtl map[string]any) *Skipped { func FileSkip(cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped {
return itemSkip(FileType, cause, id, name, addtl) return itemSkip(FileType, cause, namespace, id, name, addtl)
} }
// OnwerSkip produces a ResourceOwner-kind Item for tracking skipped items. // OnwerSkip produces a ResourceOwner-kind Item for tracking skipped items.
func OwnerSkip(cause skipCause, id, name string, addtl map[string]any) *Skipped { func OwnerSkip(cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped {
return itemSkip(ResourceOwnerType, cause, id, name, addtl) return itemSkip(ResourceOwnerType, cause, namespace, id, name, addtl)
} }
// itemSkip produces a Item of the provided type for tracking skipped items. // itemSkip produces a Item of the provided type for tracking skipped items.
func itemSkip(t itemType, cause skipCause, id, name string, addtl map[string]any) *Skipped { func itemSkip(t itemType, cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped {
return &Skipped{ return &Skipped{
Item: Item{ Item: Item{
Namespace: namespace,
ID: id, ID: id,
Name: name, Name: name,
Type: t, Type: t,

View File

@ -36,9 +36,10 @@ func (suite *ItemUnitSuite) TestItem_Error() {
func (suite *ItemUnitSuite) TestContainerErr() { func (suite *ItemUnitSuite) TestContainerErr() {
t := suite.T() t := suite.T()
addtl := map[string]any{"foo": "bar"} addtl := map[string]any{"foo": "bar"}
i := ContainerErr(clues.New("foo"), "id", "name", addtl) i := ContainerErr(clues.New("foo"), "ns", "id", "name", addtl)
expect := Item{ expect := Item{
Namespace: "ns",
ID: "id", ID: "id",
Name: "name", Name: "name",
Type: ContainerType, Type: ContainerType,
@ -52,9 +53,10 @@ func (suite *ItemUnitSuite) TestContainerErr() {
func (suite *ItemUnitSuite) TestFileErr() { func (suite *ItemUnitSuite) TestFileErr() {
t := suite.T() t := suite.T()
addtl := map[string]any{"foo": "bar"} addtl := map[string]any{"foo": "bar"}
i := FileErr(clues.New("foo"), "id", "name", addtl) i := FileErr(clues.New("foo"), "ns", "id", "name", addtl)
expect := Item{ expect := Item{
Namespace: "ns",
ID: "id", ID: "id",
Name: "name", Name: "name",
Type: FileType, Type: FileType,
@ -68,9 +70,10 @@ func (suite *ItemUnitSuite) TestFileErr() {
func (suite *ItemUnitSuite) TestOwnerErr() { func (suite *ItemUnitSuite) TestOwnerErr() {
t := suite.T() t := suite.T()
addtl := map[string]any{"foo": "bar"} addtl := map[string]any{"foo": "bar"}
i := OwnerErr(clues.New("foo"), "id", "name", addtl) i := OwnerErr(clues.New("foo"), "ns", "id", "name", addtl)
expect := Item{ expect := Item{
Namespace: "ns",
ID: "id", ID: "id",
Name: "name", Name: "name",
Type: ResourceOwnerType, Type: ResourceOwnerType,
@ -127,17 +130,17 @@ func (suite *ItemUnitSuite) TestItem_HeadersValues() {
}{ }{
{ {
name: "file", name: "file",
item: FileErr(assert.AnError, "id", "name", addtl), item: FileErr(assert.AnError, "ns", "id", "name", addtl),
expect: []string{"Error", FileType.Printable(), "name", "cname", cause}, expect: []string{"Error", FileType.Printable(), "name", "cname", cause},
}, },
{ {
name: "container", name: "container",
item: ContainerErr(assert.AnError, "id", "name", addtl), item: ContainerErr(assert.AnError, "ns", "id", "name", addtl),
expect: []string{"Error", ContainerType.Printable(), "name", "cname", cause}, expect: []string{"Error", ContainerType.Printable(), "name", "cname", cause},
}, },
{ {
name: "owner", name: "owner",
item: OwnerErr(assert.AnError, "id", "name", nil), item: OwnerErr(assert.AnError, "ns", "id", "name", nil),
expect: []string{"Error", ResourceOwnerType.Printable(), "name", "", cause}, expect: []string{"Error", ResourceOwnerType.Printable(), "name", "", cause},
}, },
} }
@ -169,9 +172,10 @@ func (suite *ItemUnitSuite) TestSkipped_String() {
func (suite *ItemUnitSuite) TestContainerSkip() { func (suite *ItemUnitSuite) TestContainerSkip() {
t := suite.T() t := suite.T()
addtl := map[string]any{"foo": "bar"} addtl := map[string]any{"foo": "bar"}
i := ContainerSkip(SkipMalware, "id", "name", addtl) i := ContainerSkip(SkipMalware, "ns", "id", "name", addtl)
expect := Item{ expect := Item{
Namespace: "ns",
ID: "id", ID: "id",
Name: "name", Name: "name",
Type: ContainerType, Type: ContainerType,
@ -185,9 +189,10 @@ func (suite *ItemUnitSuite) TestContainerSkip() {
func (suite *ItemUnitSuite) TestFileSkip() { func (suite *ItemUnitSuite) TestFileSkip() {
t := suite.T() t := suite.T()
addtl := map[string]any{"foo": "bar"} addtl := map[string]any{"foo": "bar"}
i := FileSkip(SkipMalware, "id", "name", addtl) i := FileSkip(SkipMalware, "ns", "id", "name", addtl)
expect := Item{ expect := Item{
Namespace: "ns",
ID: "id", ID: "id",
Name: "name", Name: "name",
Type: FileType, Type: FileType,
@ -201,9 +206,10 @@ func (suite *ItemUnitSuite) TestFileSkip() {
func (suite *ItemUnitSuite) TestOwnerSkip() { func (suite *ItemUnitSuite) TestOwnerSkip() {
t := suite.T() t := suite.T()
addtl := map[string]any{"foo": "bar"} addtl := map[string]any{"foo": "bar"}
i := OwnerSkip(SkipMalware, "id", "name", addtl) i := OwnerSkip(SkipMalware, "ns", "id", "name", addtl)
expect := Item{ expect := Item{
Namespace: "ns",
ID: "id", ID: "id",
Name: "name", Name: "name",
Type: ResourceOwnerType, Type: ResourceOwnerType,
@ -227,17 +233,17 @@ func (suite *ItemUnitSuite) TestSkipped_HeadersValues() {
}{ }{
{ {
name: "file", name: "file",
skip: FileSkip(SkipMalware, "id", "name", addtl), skip: FileSkip(SkipMalware, "ns", "id", "name", addtl),
expect: []string{"Skip", FileType.Printable(), "name", "cname", string(SkipMalware)}, expect: []string{"Skip", FileType.Printable(), "name", "cname", string(SkipMalware)},
}, },
{ {
name: "container", name: "container",
skip: ContainerSkip(SkipMalware, "id", "name", addtl), skip: ContainerSkip(SkipMalware, "ns", "id", "name", addtl),
expect: []string{"Skip", ContainerType.Printable(), "name", "cname", string(SkipMalware)}, expect: []string{"Skip", ContainerType.Printable(), "name", "cname", string(SkipMalware)},
}, },
{ {
name: "owner", name: "owner",
skip: OwnerSkip(SkipMalware, "id", "name", nil), skip: OwnerSkip(SkipMalware, "ns", "id", "name", nil),
expect: []string{"Skip", ResourceOwnerType.Printable(), "name", "", string(SkipMalware)}, expect: []string{"Skip", ResourceOwnerType.Printable(), "name", "", string(SkipMalware)},
}, },
} }

View File

@ -19,7 +19,7 @@ func MakeErrors(failure, recovered, skipped bool) fault.Errors {
} }
if skipped { if skipped {
fe.Skipped = []fault.Skipped{*fault.FileSkip(fault.SkipMalware, "id", "name", nil)} fe.Skipped = []fault.Skipped{*fault.FileSkip(fault.SkipMalware, "ns", "id", "name", nil)}
} }
return fe return fe

View File

@ -434,8 +434,8 @@ func (suite *RepositoryModelIntgSuite) TestGetBackupErrors() {
var ( var (
err = clues.Wrap(assert.AnError, "wrap") err = clues.Wrap(assert.AnError, "wrap")
cec = err.Core() cec = err.Core()
item = fault.FileErr(err, "file-id", "file-name", map[string]any{"foo": "bar"}) item = fault.FileErr(err, "ns", "file-id", "file-name", map[string]any{"foo": "bar"})
skip = fault.FileSkip(fault.SkipMalware, "s-file-id", "s-file-name", map[string]any{"foo": "bar"}) skip = fault.FileSkip(fault.SkipMalware, "ns", "s-file-id", "s-file-name", map[string]any{"foo": "bar"})
info = details.ItemInfo{ info = details.ItemInfo{
Exchange: &details.ExchangeInfo{ Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail, ItemType: details.ExchangeMail,