corso/src/pkg/selectors/scopes_test.go
Keepers 41bb3ee6f9
prevent cross-contamination on filter reduce (#898)
## Description

Over-restrictive scope correlation caused the
reduce processor to only include filters which
matched the data type of the scope.  Ironically,
this allowed a superset of information to match,
by evading the _all-match_ expectations of
filter scopes.

Also replaces the data category consts inside /details
with the path category types, since those are acting as
better canonical owners of data type identification
throughout the app.

## Type of change

- [x] 🐛 Bugfix

## Issue(s)

* #890

## Test Plan

- [x] 💪 Manual
- [x]  Unit test
2022-09-20 00:34:51 +00:00

486 lines
10 KiB
Go

package selectors
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/path"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/filters"
)
// ---------------------------------------------------------------------------
// tests
// ---------------------------------------------------------------------------
type SelectorScopesSuite struct {
suite.Suite
}
func TestSelectorScopesSuite(t *testing.T) {
suite.Run(t, new(SelectorScopesSuite))
}
func (suite *SelectorScopesSuite) TestContains() {
table := []struct {
name string
scope func() mockScope
check string
expect assert.BoolAssertionFunc
}{
{
name: "any",
scope: func() mockScope {
stub := stubScope("")
return stub
},
check: rootCatStub.String(),
expect: assert.True,
},
{
name: "none",
scope: func() mockScope {
stub := stubScope("")
stub[rootCatStub.String()] = failAny
return stub
},
check: rootCatStub.String(),
expect: assert.False,
},
{
name: "blank value",
scope: func() mockScope {
stub := stubScope("")
stub[rootCatStub.String()] = filters.Equal("")
return stub
},
check: rootCatStub.String(),
expect: assert.False,
},
{
name: "blank target",
scope: func() mockScope {
stub := stubScope("")
stub[rootCatStub.String()] = filterize("fnords")
return stub
},
check: "",
expect: assert.False,
},
{
name: "matching target",
scope: func() mockScope {
stub := stubScope("")
stub[rootCatStub.String()] = filterize(rootCatStub.String())
return stub
},
check: rootCatStub.String(),
expect: assert.True,
},
{
name: "non-matching target",
scope: func() mockScope {
stub := stubScope("")
stub[rootCatStub.String()] = filterize(rootCatStub.String())
return stub
},
check: "smarf",
expect: assert.False,
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
test.expect(
t,
matches(test.scope(), rootCatStub, test.check))
})
}
}
func (suite *SelectorScopesSuite) TestGetCatValue() {
t := suite.T()
stub := stubScope("")
stub[rootCatStub.String()] = filterize(rootCatStub.String())
assert.Equal(t,
[]string{rootCatStub.String()},
getCatValue(stub, rootCatStub))
assert.Equal(t,
None(),
getCatValue(stub, mockCategorizer("foo")))
}
func (suite *SelectorScopesSuite) TestIsAnyTarget() {
t := suite.T()
stub := stubScope("")
assert.True(t, isAnyTarget(stub, rootCatStub))
assert.True(t, isAnyTarget(stub, leafCatStub))
assert.False(t, isAnyTarget(stub, mockCategorizer("smarf")))
stub = stubScope("none")
assert.False(t, isAnyTarget(stub, rootCatStub))
assert.False(t, isAnyTarget(stub, leafCatStub))
assert.False(t, isAnyTarget(stub, mockCategorizer("smarf")))
}
var reduceTestTable = []struct {
name string
sel func() mockSel
expectLen int
expectPasses assert.BoolAssertionFunc
}{
{
name: "include all",
sel: func() mockSel {
sel := stubSelector()
sel.Filters = nil
sel.Excludes = nil
return sel
},
expectLen: 1,
expectPasses: assert.True,
},
{
name: "include none",
sel: func() mockSel {
sel := stubSelector()
sel.Includes[0] = scope(stubScope("none"))
sel.Filters = nil
sel.Excludes = nil
return sel
},
expectLen: 0,
expectPasses: assert.False,
},
{
name: "filter and include all",
sel: func() mockSel {
sel := stubSelector()
sel.Excludes = nil
return sel
},
expectLen: 1,
expectPasses: assert.True,
},
{
name: "include all filter none",
sel: func() mockSel {
sel := stubSelector()
sel.Filters[0] = scope(stubInfoScope("none"))
sel.Excludes = nil
return sel
},
expectLen: 0,
expectPasses: assert.False,
},
{
name: "include all exclude all",
sel: func() mockSel {
sel := stubSelector()
sel.Filters = nil
return sel
},
expectLen: 0,
expectPasses: assert.False,
},
{
name: "include all exclude none",
sel: func() mockSel {
sel := stubSelector()
sel.Filters = nil
sel.Excludes[0] = scope(stubScope("none"))
return sel
},
expectLen: 1,
expectPasses: assert.True,
},
{
name: "filter all exclude all",
sel: func() mockSel {
sel := stubSelector()
sel.Includes = nil
return sel
},
expectLen: 0,
expectPasses: assert.False,
},
{
name: "filter all exclude none",
sel: func() mockSel {
sel := stubSelector()
sel.Includes = nil
sel.Excludes[0] = scope(stubScope("none"))
return sel
},
expectLen: 1,
expectPasses: assert.True,
},
}
func (suite *SelectorScopesSuite) TestReduce() {
deets := func() details.Details {
return details.Details{
DetailsModel: details.DetailsModel{
Entries: []details.DetailsEntry{
{
RepoRef: stubRepoRef(
pathServiceStub,
pathCatStub,
rootCatStub.String(),
"stub",
leafCatStub.String(),
),
},
},
},
}
}
dataCats := map[path.CategoryType]mockCategorizer{
pathCatStub: rootCatStub,
}
for _, test := range reduceTestTable {
suite.T().Run(test.name, func(t *testing.T) {
ds := deets()
result := reduce[mockScope](
context.Background(),
&ds,
test.sel().Selector,
dataCats)
require.NotNil(t, result)
assert.Len(t, result.Entries, test.expectLen)
})
}
}
func (suite *SelectorScopesSuite) TestScopesByCategory() {
t := suite.T()
s1 := stubScope("")
s2 := stubScope("")
s2[scopeKeyCategory] = filterize(unknownCatStub.String())
result := scopesByCategory[mockScope](
[]scope{scope(s1), scope(s2)},
map[path.CategoryType]mockCategorizer{
path.UnknownCategory: rootCatStub,
},
false)
assert.Len(t, result, 1)
assert.Len(t, result[rootCatStub], 1)
assert.Empty(t, result[leafCatStub])
}
func (suite *SelectorScopesSuite) TestPasses() {
cat := rootCatStub
pth := stubPath(suite.T(), "uid", []string{"fld"}, path.EventsCategory)
pathVals := cat.pathValues(pth)
entry := details.DetailsEntry{}
for _, test := range reduceTestTable {
suite.T().Run(test.name, func(t *testing.T) {
sel := test.sel()
excl := toMockScope(sel.Excludes)
filt := toMockScope(sel.Filters)
incl := toMockScope(sel.Includes)
result := passes(
cat,
pathVals,
entry,
excl, filt, incl)
test.expectPasses(t, result)
})
}
}
func toMockScope(sc []scope) []mockScope {
if len(sc) == 0 {
return nil
}
ms := []mockScope{}
for _, s := range sc {
ms = append(ms, mockScope(s))
}
return ms
}
func (suite *SelectorScopesSuite) TestMatchesPathValues() {
cat := rootCatStub
pvs := stubPathValues()
short := "brunheelda"
table := []struct {
name string
cat mockCategorizer
rootVal string
leafVal string
shortRef string
expect assert.BoolAssertionFunc
}{
{
name: "matching values",
rootVal: rootCatStub.String(),
leafVal: leafCatStub.String(),
expect: assert.True,
},
{
name: "any",
rootVal: AnyTgt,
leafVal: AnyTgt,
expect: assert.True,
},
{
name: "none",
rootVal: NoneTgt,
leafVal: NoneTgt,
expect: assert.False,
},
{
name: "mismatched values",
rootVal: "fnords",
leafVal: "smarf",
expect: assert.False,
},
{
name: "leaf matches shortRef",
rootVal: rootCatStub.String(),
leafVal: short,
shortRef: short,
expect: assert.True,
},
{
name: "root matches shortRef",
rootVal: short,
leafVal: leafCatStub.String(),
shortRef: short,
expect: assert.False,
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
sc := stubScope("")
sc[rootCatStub.String()] = filterize(test.rootVal)
sc[leafCatStub.String()] = filterize(test.leafVal)
test.expect(t, matchesPathValues(sc, cat, pvs, test.shortRef))
})
}
}
func (suite *SelectorScopesSuite) TestAddToSet() {
t := suite.T()
set := []string{}
set = addToSet(set, []string{})
assert.Len(t, set, 0)
set = addToSet(set, []string{"a"})
assert.Len(t, set, 1)
assert.Equal(t, set[0], "a")
set = addToSet(set, []string{"a"})
assert.Len(t, set, 1)
set = addToSet(set, []string{"a", "b"})
assert.Len(t, set, 2)
assert.Equal(t, set[0], "a")
assert.Equal(t, set[1], "b")
set = addToSet(set, []string{"c", "d"})
assert.Len(t, set, 4)
assert.Equal(t, set[0], "a")
assert.Equal(t, set[1], "b")
assert.Equal(t, set[2], "c")
assert.Equal(t, set[3], "d")
}
func (suite *SelectorScopesSuite) TestClean() {
table := []struct {
name string
input []string
expect []string
}{
{
name: "nil",
input: nil,
expect: None(),
},
{
name: "has anyTgt",
input: []string{"a", AnyTgt},
expect: Any(),
},
{
name: "has noneTgt",
input: []string{"a", NoneTgt},
expect: None(),
},
{
name: "has anyTgt and noneTgt, any first",
input: []string{"a", AnyTgt, NoneTgt},
expect: Any(),
},
{
name: "has noneTgt and anyTgt, none first",
input: []string{"a", NoneTgt, AnyTgt},
expect: None(),
},
{
name: "already clean",
input: []string{"a", "b"},
expect: []string{"a", "b"},
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
result := clean(test.input)
assert.Equal(t, result, test.expect)
})
}
}
func (suite *SelectorScopesSuite) TestWrapFilter() {
table := []struct {
name string
filter filterFunc
input []string
comparator int
target string
}{
{
name: "any",
filter: filters.Contains,
input: Any(),
comparator: int(filters.Passes),
target: AnyTgt,
},
{
name: "none",
filter: filters.In,
input: None(),
comparator: int(filters.Fails),
target: NoneTgt,
},
{
name: "something",
filter: filters.Equal,
input: []string{"userid"},
comparator: int(filters.EqualTo),
target: "userid",
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
ff := wrapFilter(test.filter)(test.input)
assert.Equal(t, int(ff.Comparator), test.comparator)
assert.Equal(t, ff.Target, test.target)
})
}
}