fixing up selectors

adds a set of fixes targeted at the selectors package.  Largely
updates testing code.  But also updates the resource check
at the beginning of the Reduce flow to check for a match on
any of the resources in the ServiceResources slice.
This commit is contained in:
ryanfkeepers 2023-08-11 11:50:11 -06:00
parent 7c242819bc
commit bf398f6c8d
10 changed files with 212 additions and 49 deletions

View File

@ -186,7 +186,7 @@ func FromDataLayerPath(p string, isItem bool) (Path, error) {
return nil, clues.New("path has too few segments").With("path_string", p)
}
srs, catIdx, err := ElementsToServiceResources(pb.elements[1:])
srs, catIdx, err := elementsToServiceResources(pb.elements[1:])
if err != nil {
return nil, clues.Stack(err)
}

View File

@ -31,7 +31,7 @@ func newDataLayerResourcePath(
cat CategoryType,
isItem bool,
) dataLayerResourcePath {
pfx := append([]string{tenant}, ServiceResourcesToElements(srs)...)
pfx := append([]string{tenant}, serviceResourcesToElements(srs)...)
pfx = append(pfx, cat.String())
return dataLayerResourcePath{

View File

@ -37,7 +37,7 @@ func (sr ServiceResource) validate() error {
}
// ---------------------------------------------------------------------------
// Collection
// Exported Helpers
// ---------------------------------------------------------------------------
// NewServiceResources is a lenient constructor for building a
@ -74,7 +74,21 @@ func NewServiceResources(elems ...any) ([]ServiceResource, error) {
return srs, nil
}
func ServiceResourcesToElements(srs []ServiceResource) Elements {
func ServiceResourcesToResources(srs []ServiceResource) []string {
prs := make([]string, len(srs))
for i := range srs {
prs[i] = srs[i].ProtectedResource
}
return prs
}
// ---------------------------------------------------------------------------
// Unexported Helpers
// ---------------------------------------------------------------------------
func serviceResourcesToElements(srs []ServiceResource) Elements {
es := make(Elements, 0, len(srs)*2)
for _, tuple := range srs {
@ -85,7 +99,7 @@ func ServiceResourcesToElements(srs []ServiceResource) Elements {
return es
}
// ElementsToServiceResources turns as many pairs of elems as possible
// elementsToServiceResources turns as many pairs of elems as possible
// into ServiceResource tuples. Elems must begin with a service, but
// may contain more entries than there are service/resource pairs.
// This transformer will continue consuming elements until it finds an
@ -93,7 +107,7 @@ func ServiceResourcesToElements(srs []ServiceResource) Elements {
// Returns the serviceResource pairs, the first index containing element
// that is not part of a service/resource pair, and an error if elems is
// len==0 or contains no services.
func ElementsToServiceResources(elems Elements) ([]ServiceResource, int, error) {
func elementsToServiceResources(elems Elements) ([]ServiceResource, int, error) {
if len(elems) == 0 {
return nil, -1, clues.Wrap(errMissingSegment, "service")
}

View File

@ -157,7 +157,7 @@ func (suite *ServiceResourceUnitSuite) TestServiceResourceToElements() {
suite.Run(test.name, func() {
t := suite.T()
result := ServiceResourcesToElements(test.srs)
result := serviceResourcesToElements(test.srs)
// not ElementsMatch, order matters
assert.Equal(t, test.expect, result)
@ -234,7 +234,7 @@ func (suite *ServiceResourceUnitSuite) TestElementsToServiceResource() {
suite.Run(test.name, func() {
t := suite.T()
srs, idx, err := ElementsToServiceResources(test.elems)
srs, idx, err := elementsToServiceResources(test.elems)
test.expectErr(t, err, clues.ToCore(err))
assert.Equal(t, test.expectIdx, idx)
assert.Equal(t, test.expectSRS, srs)

View File

@ -815,10 +815,12 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
}
joinedFldrs := strings.Join(newElems, "/")
sr0 := p.ServiceResources()[0]
return stubRepoRef(sr0.Service, p.Category(), sr0.ProtectedResource, joinedFldrs, p.Item())
return stubRepoRef(
suite.T(),
p.ServiceResources(),
p.Category(),
joinedFldrs,
p.Item())
}
makeDeets := func(refs ...path.Path) *details.Details {
@ -1060,12 +1062,36 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce_locationRef() {
var (
contact = stubRepoRef(path.ExchangeService, path.ContactsCategory, "uid", "id5/id6", "cid")
contactLocation = "conts/my_cont"
event = stubRepoRef(path.ExchangeService, path.EventsCategory, "uid", "id1/id2", "eid")
eventLocation = "cal/my_cal"
mail = stubRepoRef(path.ExchangeService, path.EmailCategory, "uid", "id3/id4", "mid")
mailLocation = "inbx/my_mail"
contact = stubRepoRef(
suite.T(),
[]path.ServiceResource{{
Service: path.ExchangeService,
ProtectedResource: "uid",
}},
path.ContactsCategory,
"id5/id6",
"cid")
event = stubRepoRef(
suite.T(),
[]path.ServiceResource{{
Service: path.ExchangeService,
ProtectedResource: "uid",
}},
path.EventsCategory,
"id1/id2",
"eid")
mail = stubRepoRef(
suite.T(),
[]path.ServiceResource{{
Service: path.ExchangeService,
ProtectedResource: "uid",
}},
path.EmailCategory,
"id3/id4",
"mid")
)
makeDeets := func(refs ...string) *details.Details {

View File

@ -2,7 +2,6 @@ package selectors
import (
"fmt"
"strings"
"testing"
"github.com/alcionai/clues"
@ -229,6 +228,22 @@ func stubPath(t *testing.T, user string, s []string, cat path.CategoryType) path
// stubRepoRef ensures test path production matches that of repoRef design,
// stubbing out static values where necessary.
func stubRepoRef(service path.ServiceType, data path.CategoryType, resourceOwner, folders, item string) string {
return strings.Join([]string{"tid", service.String(), resourceOwner, data.String(), folders, item}, "/")
func stubRepoRef(
t *testing.T,
srs []path.ServiceResource,
cat path.CategoryType,
folders, item string,
) string {
fs := path.Split(folders)
fs = append(fs, item)
pb, err := path.Build(
"tid",
srs,
cat,
true,
fs...)
require.NoError(t, err, clues.ToCore(err))
return pb.String()
}

View File

@ -161,23 +161,32 @@ func (suite *OneDriveSelectorSuite) TestToOneDriveRestore() {
func (suite *OneDriveSelectorSuite) TestOneDriveRestore_Reduce() {
var (
file = stubRepoRef(
path.OneDriveService,
suite.T(),
[]path.ServiceResource{{
Service: path.OneDriveService,
ProtectedResource: "uid",
}},
path.FilesCategory,
"uid",
"drive/driveID/root:/folderA.d/folderB.d",
"file")
fileParent = "folderA/folderB"
file2 = stubRepoRef(
path.OneDriveService,
suite.T(),
[]path.ServiceResource{{
Service: path.OneDriveService,
ProtectedResource: "uid",
}},
path.FilesCategory,
"uid",
"drive/driveID/root:/folderA.d/folderC.d",
"file2")
fileParent2 = "folderA/folderC"
file3 = stubRepoRef(
path.OneDriveService,
suite.T(),
[]path.ServiceResource{{
Service: path.OneDriveService,
ProtectedResource: "uid",
}},
path.FilesCategory,
"uid",
"drive/driveID/root:/folderD.d/folderE.d",
"file3")
fileParent3 = "folderD/folderE"

View File

@ -544,8 +544,11 @@ func reduce[T scopeT, C categoryT](
continue
}
// first check, every entry needs to match the selector's resource owners.
if !matchesResourceOwner.Compare(repoPath.ResourceOwner()) {
// first check, every entry needs to have at least one protected resource
// that matches the selector's protected resources.
if !matchesResourceOwner.CompareAny(
path.ServiceResourcesToResources(
repoPath.ServiceResources())...) {
continue
}

View File

@ -22,15 +22,15 @@ import (
// tests
// ---------------------------------------------------------------------------
type SelectorScopesSuite struct {
type ScopesUnitSuite struct {
tester.Suite
}
func TestSelectorScopesSuite(t *testing.T) {
suite.Run(t, &SelectorScopesSuite{Suite: tester.NewUnitSuite(t)})
func TestScopesUnitSuite(t *testing.T) {
suite.Run(t, &ScopesUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *SelectorScopesSuite) TestContains() {
func (suite *ScopesUnitSuite) TestContains() {
table := []struct {
name string
scope func() mockScope
@ -108,7 +108,7 @@ func (suite *SelectorScopesSuite) TestContains() {
}
}
func (suite *SelectorScopesSuite) TestGetCatValue() {
func (suite *ScopesUnitSuite) TestGetCatValue() {
t := suite.T()
stub := stubScope("")
@ -122,7 +122,7 @@ func (suite *SelectorScopesSuite) TestGetCatValue() {
getCatValue(stub, mockCategorizer("foo")))
}
func (suite *SelectorScopesSuite) TestIsAnyTarget() {
func (suite *ScopesUnitSuite) TestIsAnyTarget() {
t := suite.T()
stub := stubScope("")
assert.True(t, isAnyTarget(stub, rootCatStub))
@ -253,16 +253,19 @@ var reduceTestTable = []struct {
},
}
func (suite *SelectorScopesSuite) TestReduce() {
func (suite *ScopesUnitSuite) TestReduce() {
deets := func() details.Details {
return details.Details{
DetailsModel: details.DetailsModel{
Entries: []details.Entry{
{
RepoRef: stubRepoRef(
pathServiceStub,
suite.T(),
[]path.ServiceResource{{
Service: pathServiceStub,
ProtectedResource: rootCatStub.String(),
}},
pathCatStub,
rootCatStub.String(),
"stub",
leafCatStub.String(),
),
@ -298,16 +301,90 @@ func (suite *SelectorScopesSuite) TestReduce() {
}
}
func (suite *SelectorScopesSuite) TestReduce_locationRef() {
func (suite *ScopesUnitSuite) TestReduce_locationRef() {
deets := func() details.Details {
return details.Details{
DetailsModel: details.DetailsModel{
Entries: []details.Entry{{
RepoRef: stubRepoRef(
suite.T(),
[]path.ServiceResource{{
Service: pathServiceStub,
ProtectedResource: rootCatStub.String(),
}},
pathCatStub,
"stub",
leafCatStub.String(),
),
LocationRef: "a/b/c//defg",
}},
},
}
}
dataCats := map[path.CategoryType]mockCategorizer{
pathCatStub: rootCatStub,
}
for _, test := range reduceTestTable {
suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
ds := deets()
result := reduce[mockScope](
ctx,
&ds,
test.sel().Selector,
dataCats,
fault.New(true))
require.NotNil(t, result)
assert.Len(t, result.Entries, test.expectLen)
})
}
}
func (suite *ScopesUnitSuite) TestReduce_multipleServiceResources() {
deets := func() details.Details {
return details.Details{
DetailsModel: details.DetailsModel{
Entries: []details.Entry{
// tid/serv/"matching-id"/subserv/"non-matching-id"/...
{
RepoRef: stubRepoRef(
pathServiceStub,
suite.T(),
[]path.ServiceResource{
{
Service: pathServiceStub,
ProtectedResource: rootCatStub.String(),
},
{
Service: pathServiceStub,
ProtectedResource: "foo",
},
},
pathCatStub,
"stub",
leafCatStub.String(),
),
LocationRef: "a/b/c//defg",
},
// tid/serv/"non-matching-id"/subserv/"matching-id"/...
{
RepoRef: stubRepoRef(
suite.T(),
[]path.ServiceResource{
{
Service: pathServiceStub,
ProtectedResource: "foo",
},
{
Service: pathServiceStub,
ProtectedResource: rootCatStub.String(),
},
},
pathCatStub,
rootCatStub.String(),
"stub",
leafCatStub.String(),
),
@ -341,7 +418,7 @@ func (suite *SelectorScopesSuite) TestReduce_locationRef() {
}
}
func (suite *SelectorScopesSuite) TestScopesByCategory() {
func (suite *ScopesUnitSuite) TestScopesByCategory() {
t := suite.T()
s1 := stubScope("")
s2 := stubScope("")
@ -357,7 +434,7 @@ func (suite *SelectorScopesSuite) TestScopesByCategory() {
assert.Empty(t, result[leafCatStub])
}
func (suite *SelectorScopesSuite) TestPasses() {
func (suite *ScopesUnitSuite) TestPasses() {
var (
cat = rootCatStub
pth = stubPath(suite.T(), "uid", []string{"fld"}, path.EventsCategory)
@ -401,7 +478,7 @@ func toMockScope(sc []scope) []mockScope {
return ms
}
func (suite *SelectorScopesSuite) TestMatchesPathValues() {
func (suite *ScopesUnitSuite) TestMatchesPathValues() {
cat := rootCatStub
short := "brunheelda"
@ -460,7 +537,7 @@ func (suite *SelectorScopesSuite) TestMatchesPathValues() {
}
}
func (suite *SelectorScopesSuite) TestDefaultItemOptions() {
func (suite *ScopesUnitSuite) TestDefaultItemOptions() {
table := []struct {
name string
cfg Config
@ -521,7 +598,7 @@ func (suite *SelectorScopesSuite) TestDefaultItemOptions() {
}
}
func (suite *SelectorScopesSuite) TestClean() {
func (suite *ScopesUnitSuite) TestClean() {
table := []struct {
name string
input []string
@ -568,7 +645,7 @@ func (suite *SelectorScopesSuite) TestClean() {
}
}
func (suite *SelectorScopesSuite) TestScopeConfig() {
func (suite *ScopesUnitSuite) TestScopeConfig() {
input := "input"
table := []struct {
@ -608,7 +685,7 @@ func (ms mockFMTState) Width() (int, bool) { return 0, false }
func (ms mockFMTState) Precision() (int, bool) { return 0, false }
func (ms mockFMTState) Flag(int) bool { return false }
func (suite *SelectorScopesSuite) TestScopesPII() {
func (suite *ScopesUnitSuite) TestScopesPII() {
table := []struct {
name string
s mockScope

View File

@ -155,9 +155,12 @@ func (suite *SharePointSelectorSuite) TestSharePointRestore_Reduce() {
}
return stubRepoRef(
path.SharePointService,
suite.T(),
[]path.ServiceResource{{
Service: path.SharePointService,
ProtectedResource: siteID,
}},
cat,
siteID,
strings.Join(folderElems, "/"),
item)
}
@ -188,8 +191,24 @@ func (suite *SharePointSelectorSuite) TestSharePointRestore_Reduce() {
"sid",
append(slices.Clone(prefixElems), itemElems3...),
"item3")
item4 = stubRepoRef(path.SharePointService, path.PagesCategory, "sid", pairGH, "item4")
item5 = stubRepoRef(path.SharePointService, path.PagesCategory, "sid", pairGH, "item5")
item4 = stubRepoRef(
suite.T(),
[]path.ServiceResource{{
Service: path.SharePointService,
ProtectedResource: "sid",
}},
path.PagesCategory,
pairGH,
"item4")
item5 = stubRepoRef(
suite.T(),
[]path.ServiceResource{{
Service: path.SharePointService,
ProtectedResource: "sid",
}},
path.PagesCategory,
pairGH,
"item5")
)
deets := &details.Details{