introduce exchange info selector support (#379)

* introduce exchange info selector support

Adds support in selectors/exchange for queries based on
backup.ExchangeInfo entries.  This allows the declaration
of selectors based on non-identifier details such as sender,
subject, or receivedAt time.

Changes Exclude scope matching from being an Any-
match comparator to an All-match.  This keeps exclude
and include behavior identical, hopefully making less
confusion for users.
This commit is contained in:
Keepers 2022-07-21 15:08:03 -06:00 committed by GitHub
parent dfcba3bf0a
commit 532922f662
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 407 additions and 234 deletions

View File

@ -156,22 +156,22 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Selector { func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Selector {
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
if all { if all {
sel.Include(sel.Users(selectors.All())) sel.Include(sel.Users(selectors.Any()))
return sel.Selector return sel.Selector
} }
if len(data) == 0 { if len(data) == 0 {
sel.Include(sel.ContactFolders(user, selectors.All())) sel.Include(sel.ContactFolders(user, selectors.Any()))
sel.Include(sel.MailFolders(user, selectors.All())) sel.Include(sel.MailFolders(user, selectors.Any()))
sel.Include(sel.Events(user, selectors.All())) sel.Include(sel.Events(user, selectors.Any()))
} }
for _, d := range data { for _, d := range data {
switch d { switch d {
case dataContacts: case dataContacts:
sel.Include(sel.ContactFolders(users, selectors.All())) sel.Include(sel.ContactFolders(users, selectors.Any()))
case dataEmail: case dataEmail:
sel.Include(sel.MailFolders(users, selectors.All())) sel.Include(sel.MailFolders(users, selectors.Any()))
case dataEvents: case dataEvents:
sel.Include(sel.Events(users, selectors.All())) sel.Include(sel.Events(users, selectors.Any()))
} }
} }
return sel.Selector return sel.Selector
@ -318,7 +318,7 @@ func exchangeBackupDetailSelectors(
// if only the backupID is provided, treat that as an --all query // if only the backupID is provided, treat that as an --all query
if lc+lcf+le+lef+lev+lu == 0 { if lc+lcf+le+lef+lev+lu == 0 {
sel.Include(sel.Users(selectors.All())) sel.Include(sel.Users(selectors.Any()))
return sel.Selector return sel.Selector
} }

View File

@ -0,0 +1,25 @@
package common
import (
"errors"
"time"
)
// FormatTime produces the standard format for corso time values.
// Always formats into the UTC timezone.
func FormatTime(t time.Time) string {
return t.UTC().Format(time.RFC3339Nano)
}
// ParseTime makes a best attempt to produce a time value from
// the provided string. Always returns a UTC timezone value.
func ParseTime(s string) (time.Time, error) {
if len(s) == 0 {
return time.Time{}, errors.New("cannot interpret an empty string as time.Time")
}
t, err := time.Parse(time.RFC3339Nano, s)
if err != nil {
return time.Time{}, err
}
return t.UTC(), nil
}

View File

@ -0,0 +1,42 @@
package common_test
import (
"testing"
"time"
"github.com/alcionai/corso/internal/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type CommonTimeUnitSuite struct {
suite.Suite
}
func TestCommonTimeUnitSuite(t *testing.T) {
suite.Run(t, new(CommonTimeUnitSuite))
}
func (suite *CommonTimeUnitSuite) TestFormatTime() {
t := suite.T()
now := time.Now()
result := common.FormatTime(now)
assert.Equal(t, now.UTC().Format(time.RFC3339Nano), result)
}
func (suite *CommonTimeUnitSuite) TestParseTime() {
t := suite.T()
now := time.Now()
nowStr := now.Format(time.RFC3339Nano)
result, err := common.ParseTime(nowStr)
require.NoError(t, err)
assert.Equal(t, now.UTC(), result)
_, err = common.ParseTime("")
require.Error(t, err)
_, err = common.ParseTime("flablabls")
require.Error(t, err)
}

View File

@ -208,7 +208,7 @@ func (gc *GraphConnector) ExchangeDataCollection(ctx context.Context, selector s
// TODO: handle "get mail for all users" // TODO: handle "get mail for all users"
// this would probably no-op without this check, // this would probably no-op without this check,
// but we want it made obvious that we're punting. // but we want it made obvious that we're punting.
if user == selectors.AllTgt { if user == selectors.AnyTgt {
errs = support.WrapAndAppend( errs = support.WrapAndAppend(
"all-users", "all-users",
errors.New("all users selector currently not handled"), errors.New("all users selector currently not handled"),

View File

@ -3,6 +3,7 @@ package selectors
import ( import (
"strings" "strings"
"github.com/alcionai/corso/internal/common"
"github.com/alcionai/corso/pkg/backup" "github.com/alcionai/corso/pkg/backup"
) )
@ -76,13 +77,22 @@ func (s Selector) ToExchangeRestore() (*ExchangeRestore, error) {
// Exclude/Includes // Exclude/Includes
// Include appends the provided scopes to the selector's inclusion set. // Include appends the provided scopes to the selector's inclusion set.
//
// All parts of the scope must match for data to be included.
// Ex: Mail(u1, f1, m1) => only includes mail if it is owned by user u1,
// located in folder f1, and ID'd as m1. Use selectors.Any() to wildcard
// a scope value. No value will match if selectors.None() is provided.
//
// Group-level scopes will automatically apply the Any() wildcard to child
// properties.
// ex: User(u1) is the same as Mail(u1, Any(), Any()).
func (s *exchange) Include(scopes ...[]exchangeScope) { func (s *exchange) Include(scopes ...[]exchangeScope) {
if s.Includes == nil { if s.Includes == nil {
s.Includes = []map[string]string{} s.Includes = []map[string]string{}
} }
concat := []exchangeScope{} concat := []exchangeScope{}
for _, scopeSl := range scopes { for _, scopeSl := range scopes {
concat = append(concat, extendExchangeScopeValues(All(), scopeSl)...) concat = append(concat, extendExchangeScopeValues(scopeSl)...)
} }
for _, sc := range concat { for _, sc := range concat {
s.Includes = append(s.Includes, map[string]string(sc)) s.Includes = append(s.Includes, map[string]string(sc))
@ -91,13 +101,24 @@ func (s *exchange) Include(scopes ...[]exchangeScope) {
// Exclude appends the provided scopes to the selector's exclusion set. // Exclude appends the provided scopes to the selector's exclusion set.
// Every Exclusion scope applies globally, affecting all inclusion scopes. // Every Exclusion scope applies globally, affecting all inclusion scopes.
//
// All parts of the scope must match for data to be excluded.
// Ex: Mail(u1, f1, m1) => only excludes mail that is owned by user u1,
// located in folder f1, and ID'd as m1. Use selectors.Any() to wildcard
// a scope value. No value will match if selectors.None() is provided.
//
// Group-level scopes will automatically apply the Any() wildcard to
// child properties.
// ex: User(u1) automatically includes all mail, events, and contacts,
// therefore it is the same as selecting all of the following:
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
func (s *exchange) Exclude(scopes ...[]exchangeScope) { func (s *exchange) Exclude(scopes ...[]exchangeScope) {
if s.Excludes == nil { if s.Excludes == nil {
s.Excludes = []map[string]string{} s.Excludes = []map[string]string{}
} }
concat := []exchangeScope{} concat := []exchangeScope{}
for _, scopeSl := range scopes { for _, scopeSl := range scopes {
concat = append(concat, extendExchangeScopeValues(None(), scopeSl)...) concat = append(concat, extendExchangeScopeValues(scopeSl)...)
} }
for _, sc := range concat { for _, sc := range concat {
s.Excludes = append(s.Excludes, map[string]string(sc)) s.Excludes = append(s.Excludes, map[string]string(sc))
@ -106,20 +127,20 @@ func (s *exchange) Exclude(scopes ...[]exchangeScope) {
// completes population for certain scope properties, according to the // completes population for certain scope properties, according to the
// expecations of Include and Exclude behavior. // expecations of Include and Exclude behavior.
func extendExchangeScopeValues(v []string, es []exchangeScope) []exchangeScope { func extendExchangeScopeValues(es []exchangeScope) []exchangeScope {
vv := join(v...) v := join(Any()...)
for i := range es { for i := range es {
switch es[i].Category() { switch es[i].Category() {
case ExchangeContactFolder: case ExchangeContactFolder:
es[i][ExchangeContact.String()] = vv es[i][ExchangeContact.String()] = v
case ExchangeMailFolder: case ExchangeMailFolder:
es[i][ExchangeMail.String()] = vv es[i][ExchangeMail.String()] = v
case ExchangeUser: case ExchangeUser:
es[i][ExchangeContactFolder.String()] = vv es[i][ExchangeContactFolder.String()] = v
es[i][ExchangeContact.String()] = vv es[i][ExchangeContact.String()] = v
es[i][ExchangeEvent.String()] = vv es[i][ExchangeEvent.String()] = v
es[i][ExchangeMailFolder.String()] = vv es[i][ExchangeMailFolder.String()] = v
es[i][ExchangeMail.String()] = vv es[i][ExchangeMail.String()] = v
} }
} }
return es return es
@ -130,7 +151,7 @@ func extendExchangeScopeValues(v []string, es []exchangeScope) []exchangeScope {
// Produces one or more exchange contact scopes. // Produces one or more exchange contact scopes.
// One scope is created per combination of users,folders,contacts. // One scope is created per combination of users,folders,contacts.
// If any slice contains selectors.All, that slice is reduced to [selectors.All] // If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None] // If any slice is empty, it defaults to [selectors.None]
func (s *exchange) Contacts(users, folders, contacts []string) []exchangeScope { func (s *exchange) Contacts(users, folders, contacts []string) []exchangeScope {
@ -154,7 +175,7 @@ func (s *exchange) Contacts(users, folders, contacts []string) []exchangeScope {
// Produces one or more exchange contact folder scopes. // Produces one or more exchange contact folder scopes.
// One scope is created per combination of users,folders. // One scope is created per combination of users,folders.
// If any slice contains selectors.All, that slice is reduced to [selectors.All] // If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None] // If any slice is empty, it defaults to [selectors.None]
func (s *exchange) ContactFolders(users, folders []string) []exchangeScope { func (s *exchange) ContactFolders(users, folders []string) []exchangeScope {
@ -174,7 +195,7 @@ func (s *exchange) ContactFolders(users, folders []string) []exchangeScope {
// Produces one or more exchange event scopes. // Produces one or more exchange event scopes.
// One scope is created per combination of users,events. // One scope is created per combination of users,events.
// If any slice contains selectors.All, that slice is reduced to [selectors.All] // If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None] // If any slice is empty, it defaults to [selectors.None]
func (s *exchange) Events(users, events []string) []exchangeScope { func (s *exchange) Events(users, events []string) []exchangeScope {
@ -194,7 +215,7 @@ func (s *exchange) Events(users, events []string) []exchangeScope {
// Produces one or more mail scopes. // Produces one or more mail scopes.
// One scope is created per combination of users,folders,mails. // One scope is created per combination of users,folders,mails.
// If any slice contains selectors.All, that slice is reduced to [selectors.All] // If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None] // If any slice is empty, it defaults to [selectors.None]
func (s *exchange) Mails(users, folders, mails []string) []exchangeScope { func (s *exchange) Mails(users, folders, mails []string) []exchangeScope {
@ -218,7 +239,7 @@ func (s *exchange) Mails(users, folders, mails []string) []exchangeScope {
// Produces one or more exchange mail folder scopes. // Produces one or more exchange mail folder scopes.
// One scope is created per combination of users,folders. // One scope is created per combination of users,folders.
// If any slice contains selectors.All, that slice is reduced to [selectors.All] // If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None] // If any slice is empty, it defaults to [selectors.None]
func (s *exchange) MailFolders(users, folders []string) []exchangeScope { func (s *exchange) MailFolders(users, folders []string) []exchangeScope {
@ -238,7 +259,7 @@ func (s *exchange) MailFolders(users, folders []string) []exchangeScope {
// Produces one or more exchange contact user scopes. // Produces one or more exchange contact user scopes.
// One scope is created per user entry. // One scope is created per user entry.
// If any slice contains selectors.All, that slice is reduced to [selectors.All] // If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None] // If any slice is empty, it defaults to [selectors.None]
func (s *exchange) Users(users []string) []exchangeScope { func (s *exchange) Users(users []string) []exchangeScope {
@ -252,6 +273,73 @@ func (s *exchange) Users(users []string) []exchangeScope {
return scopes return scopes
} }
// Produces one or more exchange mail sender filter scopes.
// Matches any mail whose sender is equal to one of the provided strings.
// One scope is created per senderID entry.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *ExchangeRestore) MailSender(senderID []string) []exchangeScope {
scopes := []exchangeScope{}
scopes = append(scopes, exchangeScope{
scopeKeyGranularity: Item,
scopeKeyCategory: ExchangeMail.String(),
scopeKeyInfoFilter: ExchangeInfoMailSender.String(),
ExchangeInfoMailSender.String(): join(senderID...),
})
return scopes
}
// Produces one or more exchange mail subject filter scopes.
// Matches any mail whose mail subject contains one of the provided strings.
// One scope is created per subject entry.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *ExchangeRestore) MailSubject(subjectSubstring []string) []exchangeScope {
scopes := []exchangeScope{}
scopes = append(scopes, exchangeScope{
scopeKeyGranularity: Item,
scopeKeyCategory: ExchangeMail.String(),
scopeKeyInfoFilter: ExchangeInfoMailSubject.String(),
ExchangeInfoMailSubject.String(): join(subjectSubstring...),
})
return scopes
}
// Produces one or more exchange mail received-after filter scopes.
// Matches any mail which was received after the timestring.
// One scope is created per timeString entry.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *ExchangeRestore) MailReceivedAfter(timeString []string) []exchangeScope {
scopes := []exchangeScope{}
scopes = append(scopes, exchangeScope{
scopeKeyGranularity: Item,
scopeKeyCategory: ExchangeMail.String(),
scopeKeyInfoFilter: ExchangeInfoMailReceivedAfter.String(),
ExchangeInfoMailReceivedAfter.String(): join(timeString...),
})
return scopes
}
// Produces one or more exchange mail received-before filter scopes.
// Matches any mail which was received before the timestring.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *ExchangeRestore) MailReceivedBefore(timeString []string) []exchangeScope {
scopes := []exchangeScope{}
scopes = append(scopes, exchangeScope{
scopeKeyGranularity: Item,
scopeKeyCategory: ExchangeMail.String(),
scopeKeyInfoFilter: ExchangeInfoMailReceivedBefore.String(),
ExchangeInfoMailReceivedBefore.String(): join(timeString...),
})
return scopes
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Destination // Destination
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -311,16 +399,23 @@ func (s *exchange) Scopes() []exchangeScope {
//go:generate stringer -type=exchangeCategory //go:generate stringer -type=exchangeCategory
const ( const (
ExchangeCategoryUnknown exchangeCategory = iota ExchangeCategoryUnknown exchangeCategory = iota
// types of data identified by exchange
ExchangeContact ExchangeContact
ExchangeContactFolder ExchangeContactFolder
ExchangeEvent ExchangeEvent
ExchangeMail ExchangeMail
ExchangeMailFolder ExchangeMailFolder
ExchangeUser ExchangeUser
// filterable topics identified by exchange
ExchangeInfoMailSender exchangeCategory = iota + 100 // offset to pad out future data additions
ExchangeInfoMailSubject
ExchangeInfoMailReceivedAfter
ExchangeInfoMailReceivedBefore
) )
func exchangeCatAtoI(s string) exchangeCategory { func exchangeCatAtoI(s string) exchangeCategory {
switch s { switch s {
// data types
case ExchangeContact.String(): case ExchangeContact.String():
return ExchangeContact return ExchangeContact
case ExchangeContactFolder.String(): case ExchangeContactFolder.String():
@ -333,6 +428,15 @@ func exchangeCatAtoI(s string) exchangeCategory {
return ExchangeMailFolder return ExchangeMailFolder
case ExchangeUser.String(): case ExchangeUser.String():
return ExchangeUser return ExchangeUser
// filters
case ExchangeInfoMailSender.String():
return ExchangeInfoMailSender
case ExchangeInfoMailSubject.String():
return ExchangeInfoMailSubject
case ExchangeInfoMailReceivedAfter.String():
return ExchangeInfoMailReceivedAfter
case ExchangeInfoMailReceivedBefore.String():
return ExchangeInfoMailReceivedBefore
default: default:
return ExchangeCategoryUnknown return ExchangeCategoryUnknown
} }
@ -349,6 +453,11 @@ func (s exchangeScope) Category() exchangeCategory {
return exchangeCatAtoI(s[scopeKeyCategory]) return exchangeCatAtoI(s[scopeKeyCategory])
} }
// Filer describes the specific filter, and its target values.
func (s exchangeScope) Filter() exchangeCategory {
return exchangeCatAtoI(s[scopeKeyInfoFilter])
}
// IncludeCategory checks whether the scope includes a // IncludeCategory checks whether the scope includes a
// certain category of data. // certain category of data.
// Ex: to check if the scope includes mail data: // Ex: to check if the scope includes mail data:
@ -387,64 +496,87 @@ var categoryPathSet = map[exchangeCategory][]exchangeCategory{
ExchangeContact: {ExchangeUser, ExchangeContactFolder, ExchangeContact}, ExchangeContact: {ExchangeUser, ExchangeContactFolder, ExchangeContact},
ExchangeEvent: {ExchangeUser, ExchangeEvent}, ExchangeEvent: {ExchangeUser, ExchangeEvent},
ExchangeMail: {ExchangeUser, ExchangeMailFolder, ExchangeMail}, ExchangeMail: {ExchangeUser, ExchangeMailFolder, ExchangeMail},
ExchangeUser: {ExchangeUser},
} }
// includesPath returns true if all filters in the scope match the path. // matches returns true if either the path or the info matches the scope details.
func (s exchangeScope) includesPath(cat exchangeCategory, path []string) bool { func (s exchangeScope) matches(cat exchangeCategory, path []string, info *backup.ExchangeInfo) bool {
ids := exchangeIDPath(cat, path) return s.matchesPath(cat, path) || s.matchesInfo(cat, info)
}
// matchesInfo handles the standard behavior when comparing a scope and an exchangeInfo
// returns true if the scope and info match for the provided category.
func (s exchangeScope) matchesInfo(cat exchangeCategory, info *backup.ExchangeInfo) bool {
// we need values to match against
if info == nil {
return false
}
// the scope must define targets to match on
filterCat := s.Filter()
targets := s.Get(filterCat)
if len(targets) == 0 {
return false
}
if targets[0] == AnyTgt {
return true
}
if targets[0] == NoneTgt {
return false
}
// any of the targets for a given info filter may succeed.
for _, target := range targets {
switch filterCat {
case ExchangeInfoMailSender:
if target == info.Sender {
return true
}
case ExchangeInfoMailSubject:
if strings.Contains(info.Subject, target) {
return true
}
case ExchangeInfoMailReceivedAfter:
if target < common.FormatTime(info.Received) {
return true
}
case ExchangeInfoMailReceivedBefore:
if target > common.FormatTime(info.Received) {
return true
}
}
}
return false
}
// matchesPath handles the standard behavior when comparing a scope and a path
// returns true if the scope and path match for the provided category.
func (s exchangeScope) matchesPath(cat exchangeCategory, path []string) bool {
pathIDs := exchangeIDPath(cat, path)
for _, c := range categoryPathSet[cat] { for _, c := range categoryPathSet[cat] {
target := s.Get(c) target := s.Get(c)
// the scope must define the targets to match on
if len(target) == 0 { if len(target) == 0 {
return false return false
} }
id, ok := ids[c] // None() fails all matches
if target[0] == NoneTgt {
return false
}
// the path must contain a value to match against
id, ok := pathIDs[c]
if !ok { if !ok {
return false return false
} }
if target[0] != AllTgt && !contains(target, id) { // all parts of the scope must match
return false isAny := target[0] == AnyTgt
if !isAny {
if !contains(target, id) {
return false
}
} }
} }
return true return true
} }
// includesInfo returns true if all filters in the scope match the info.
func (s exchangeScope) includesInfo(cat exchangeCategory, info *backup.ExchangeInfo) bool {
// todo: implement once filters used in scopes
if info == nil {
return false
}
return false
}
// excludesPath returns true if all filters in the scope match the path.
func (s exchangeScope) excludesPath(cat exchangeCategory, path []string) bool {
ids := exchangeIDPath(cat, path)
for _, c := range categoryPathSet[cat] {
target := s.Get(c)
if len(target) == 0 {
return true
}
id, ok := ids[c]
if !ok {
return true
}
if target[0] == AllTgt || contains(target, id) {
return true
}
}
return false
}
// excludesInfo returns true if all filters in the scope matche the info.
func (s exchangeScope) excludesInfo(cat exchangeCategory, info *backup.ExchangeInfo) bool {
// todo: implement once filters used in scopes
if info == nil {
return false
}
return false
}
// temporary helper until filters replace string values for scopes. // temporary helper until filters replace string values for scopes.
func contains(super []string, sub string) bool { func contains(super []string, sub string) bool {
for _, s := range super { for _, s := range super {
@ -577,7 +709,7 @@ func matchExchangeEntry(
) bool { ) bool {
var included bool var included bool
for _, inc := range incs { for _, inc := range incs {
if inc.includesPath(cat, path) || inc.includesInfo(cat, info) { if inc.matches(cat, path, info) {
included = true included = true
break break
} }
@ -588,7 +720,7 @@ func matchExchangeEntry(
var excluded bool var excluded bool
for _, exc := range excs { for _, exc := range excs {
if exc.excludesPath(cat, path) || exc.excludesInfo(cat, info) { if exc.matches(cat, path, info) {
excluded = true excluded = true
break break
} }

View File

@ -2,7 +2,9 @@ package selectors
import ( import (
"testing" "testing"
"time"
"github.com/alcionai/corso/internal/common"
"github.com/alcionai/corso/pkg/backup" "github.com/alcionai/corso/pkg/backup"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -57,7 +59,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Contacts() {
const ( const (
user = "user" user = "user"
folder = AllTgt folder = AnyTgt
c1 = "c1" c1 = "c1"
c2 = "c2" c2 = "c2"
) )
@ -78,7 +80,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Contacts() {
const ( const (
user = "user" user = "user"
folder = AllTgt folder = AnyTgt
c1 = "c1" c1 = "c1"
c2 = "c2" c2 = "c2"
) )
@ -112,7 +114,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_ContactFolders()
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeUser.String()], user)
assert.Equal(t, scope[ExchangeContactFolder.String()], join(f1, f2)) assert.Equal(t, scope[ExchangeContactFolder.String()], join(f1, f2))
assert.Equal(t, scope[ExchangeContact.String()], NoneTgt) assert.Equal(t, scope[ExchangeContact.String()], AnyTgt)
} }
func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_ContactFolders() { func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_ContactFolders() {
@ -132,7 +134,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_ContactFolders()
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeUser.String()], user)
assert.Equal(t, scope[ExchangeContactFolder.String()], join(f1, f2)) assert.Equal(t, scope[ExchangeContactFolder.String()], join(f1, f2))
assert.Equal(t, scope[ExchangeContact.String()], AllTgt) assert.Equal(t, scope[ExchangeContact.String()], AnyTgt)
assert.Equal(t, sel.Scopes()[0].Category(), ExchangeContactFolder) assert.Equal(t, sel.Scopes()[0].Category(), ExchangeContactFolder)
} }
@ -183,7 +185,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Mails() {
const ( const (
user = "user" user = "user"
folder = AllTgt folder = AnyTgt
m1 = "m1" m1 = "m1"
m2 = "m2" m2 = "m2"
) )
@ -204,7 +206,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Mails() {
const ( const (
user = "user" user = "user"
folder = AllTgt folder = AnyTgt
m1 = "m1" m1 = "m1"
m2 = "m2" m2 = "m2"
) )
@ -238,7 +240,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_MailFolders() {
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeUser.String()], user)
assert.Equal(t, scope[ExchangeMailFolder.String()], join(f1, f2)) assert.Equal(t, scope[ExchangeMailFolder.String()], join(f1, f2))
assert.Equal(t, scope[ExchangeMail.String()], NoneTgt) assert.Equal(t, scope[ExchangeMail.String()], AnyTgt)
} }
func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_MailFolders() { func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_MailFolders() {
@ -258,7 +260,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_MailFolders() {
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeUser.String()], user)
assert.Equal(t, scope[ExchangeMailFolder.String()], join(f1, f2)) assert.Equal(t, scope[ExchangeMailFolder.String()], join(f1, f2))
assert.Equal(t, scope[ExchangeMail.String()], AllTgt) assert.Equal(t, scope[ExchangeMail.String()], AnyTgt)
assert.Equal(t, sel.Scopes()[0].Category(), ExchangeMailFolder) assert.Equal(t, sel.Scopes()[0].Category(), ExchangeMailFolder)
} }
@ -278,11 +280,11 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Users() {
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], join(u1, u2)) assert.Equal(t, scope[ExchangeUser.String()], join(u1, u2))
assert.Equal(t, scope[ExchangeContact.String()], NoneTgt) assert.Equal(t, scope[ExchangeContact.String()], AnyTgt)
assert.Equal(t, scope[ExchangeContactFolder.String()], NoneTgt) assert.Equal(t, scope[ExchangeContactFolder.String()], AnyTgt)
assert.Equal(t, scope[ExchangeEvent.String()], NoneTgt) assert.Equal(t, scope[ExchangeEvent.String()], AnyTgt)
assert.Equal(t, scope[ExchangeMail.String()], NoneTgt) assert.Equal(t, scope[ExchangeMail.String()], AnyTgt)
assert.Equal(t, scope[ExchangeMailFolder.String()], NoneTgt) assert.Equal(t, scope[ExchangeMailFolder.String()], AnyTgt)
} }
func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Users() { func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Users() {
@ -300,11 +302,11 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Users() {
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], join(u1, u2)) assert.Equal(t, scope[ExchangeUser.String()], join(u1, u2))
assert.Equal(t, scope[ExchangeContact.String()], AllTgt) assert.Equal(t, scope[ExchangeContact.String()], AnyTgt)
assert.Equal(t, scope[ExchangeContactFolder.String()], AllTgt) assert.Equal(t, scope[ExchangeContactFolder.String()], AnyTgt)
assert.Equal(t, scope[ExchangeEvent.String()], AllTgt) assert.Equal(t, scope[ExchangeEvent.String()], AnyTgt)
assert.Equal(t, scope[ExchangeMail.String()], AllTgt) assert.Equal(t, scope[ExchangeMail.String()], AnyTgt)
assert.Equal(t, scope[ExchangeMailFolder.String()], AllTgt) assert.Equal(t, scope[ExchangeMailFolder.String()], AnyTgt)
assert.Equal(t, sel.Scopes()[0].Category(), ExchangeUser) assert.Equal(t, sel.Scopes()[0].Category(), ExchangeUser)
} }
@ -359,19 +361,19 @@ func (suite *ExchangeSourceSuite) TestExchangeDestination_GetOrDefault() {
} }
var allScopesExceptUnknown = map[string]string{ var allScopesExceptUnknown = map[string]string{
ExchangeContact.String(): AllTgt, ExchangeContact.String(): AnyTgt,
ExchangeContactFolder.String(): AllTgt, ExchangeContactFolder.String(): AnyTgt,
ExchangeEvent.String(): AllTgt, ExchangeEvent.String(): AnyTgt,
ExchangeMail.String(): AllTgt, ExchangeMail.String(): AnyTgt,
ExchangeMailFolder.String(): AllTgt, ExchangeMailFolder.String(): AnyTgt,
ExchangeUser.String(): AllTgt, ExchangeUser.String(): AnyTgt,
} }
func (suite *ExchangeSourceSuite) TestExchangeBackup_Scopes() { func (suite *ExchangeSourceSuite) TestExchangeBackup_Scopes() {
eb := NewExchangeBackup() eb := NewExchangeBackup()
eb.Includes = []map[string]string{allScopesExceptUnknown} eb.Includes = []map[string]string{allScopesExceptUnknown}
// todo: swap the above for this // todo: swap the above for this
// eb := NewExchangeBackup().IncludeUsers(AllTgt) // eb := NewExchangeBackup().IncludeUsers(AnyTgt)
scopes := eb.Scopes() scopes := eb.Scopes()
assert.Len(suite.T(), scopes, 1) assert.Len(suite.T(), scopes, 1)
@ -448,7 +450,7 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_Get() {
eb := NewExchangeBackup() eb := NewExchangeBackup()
eb.Includes = []map[string]string{allScopesExceptUnknown} eb.Includes = []map[string]string{allScopesExceptUnknown}
// todo: swap the above for this // todo: swap the above for this
// eb := NewExchangeBackup().IncludeUsers(AllTgt) // eb := NewExchangeBackup().IncludeUsers(AnyTgt)
scope := eb.Scopes()[0] scope := eb.Scopes()[0]
@ -466,7 +468,7 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_Get() {
None(), None(),
scope.Get(ExchangeCategoryUnknown)) scope.Get(ExchangeCategoryUnknown))
expect := All() expect := Any()
for _, test := range table { for _, test := range table {
suite.T().Run(test.String(), func(t *testing.T) { suite.T().Run(test.String(), func(t *testing.T) {
assert.Equal(t, expect, scope.Get(test)) assert.Equal(t, expect, scope.Get(test))
@ -474,33 +476,55 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_Get() {
} }
} }
func (suite *ExchangeSourceSuite) TestExchangeScope_IncludesInfo() { func (suite *ExchangeSourceSuite) TestExchangeScope_Include_MatchesInfo() {
es := NewExchangeRestore()
const ( const (
TODO = "this is a placeholder, awaiting implemenation of filters" sender = "smarf@2many.cooks"
subject = "I have seen the fnords!"
) )
var ( var (
es = NewExchangeRestore() epoch = time.Time{}
now = time.Now()
then = now.Add(1 * time.Minute)
info = &backup.ExchangeInfo{
Sender: sender,
Subject: subject,
Received: now,
}
) )
table := []struct { table := []struct {
name string name string
scope []exchangeScope scope []exchangeScope
info *backup.ExchangeInfo
expect assert.BoolAssertionFunc expect assert.BoolAssertionFunc
}{ }{
{"all user's items", es.Users(All()), nil, assert.False}, // false while a todo {"any mail with a sender", es.MailSender(Any()), assert.True},
{"no mail, regardless of sender", es.MailSender(None()), assert.False},
{"mail from a different sender", es.MailSender([]string{"magoo@ma.goo"}), assert.False},
{"mail from the matching sender", es.MailSender([]string{sender}), assert.True},
{"mail with any subject", es.MailSubject(Any()), assert.True},
{"no mail, regardless of subject", es.MailSubject(None()), assert.False},
{"mail with a different subject", es.MailSubject([]string{"fancy"}), assert.False},
{"mail with the matching subject", es.MailSubject([]string{subject}), assert.True},
{"mail with a substring subject match", es.MailSubject([]string{subject[5:9]}), assert.True},
{"mail received after the epoch", es.MailReceivedAfter([]string{common.FormatTime(epoch)}), assert.True},
{"mail received after now", es.MailReceivedAfter([]string{common.FormatTime(now)}), assert.False},
{"mail received after sometime later", es.MailReceivedAfter([]string{common.FormatTime(then)}), assert.False},
{"mail received before the epoch", es.MailReceivedBefore([]string{common.FormatTime(epoch)}), assert.False},
{"mail received before now", es.MailReceivedBefore([]string{common.FormatTime(now)}), assert.False},
{"mail received before sometime later", es.MailReceivedBefore([]string{common.FormatTime(then)}), assert.True},
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
scopes := extendExchangeScopeValues(All(), test.scope) scopes := extendExchangeScopeValues(test.scope)
for _, scope := range scopes { for _, scope := range scopes {
test.expect(t, scope.includesInfo(ExchangeMail, test.info)) test.expect(t, scope.matchesInfo(scope.Category(), info))
} }
}) })
} }
} }
func (suite *ExchangeSourceSuite) TestExchangeScope_IncludesPath() { func (suite *ExchangeSourceSuite) TestExchangeScope_MatchesPath() {
const ( const (
usr = "userID" usr = "userID"
fld = "mailFolder" fld = "mailFolder"
@ -516,95 +540,27 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_IncludesPath() {
scope []exchangeScope scope []exchangeScope
expect assert.BoolAssertionFunc expect assert.BoolAssertionFunc
}{ }{
{"all user's items", es.Users(All()), assert.True}, {"all user's items", es.Users(Any()), assert.True},
{"no user's items", es.Users(None()), assert.False}, {"no user's items", es.Users(None()), assert.False},
{"matching user", es.Users([]string{usr}), assert.True}, {"matching user", es.Users([]string{usr}), assert.True},
{"non-maching user", es.Users([]string{"smarf"}), assert.False}, {"non-maching user", es.Users([]string{"smarf"}), assert.False},
{"one of multiple users", es.Users([]string{"smarf", usr}), assert.True}, {"one of multiple users", es.Users([]string{"smarf", usr}), assert.True},
{"all folders", es.MailFolders(All(), All()), assert.True}, {"all folders", es.MailFolders(Any(), Any()), assert.True},
{"no folders", es.MailFolders(All(), None()), assert.False}, {"no folders", es.MailFolders(Any(), None()), assert.False},
{"matching folder", es.MailFolders(All(), []string{fld}), assert.True}, {"matching folder", es.MailFolders(Any(), []string{fld}), assert.True},
{"non-matching folder", es.MailFolders(All(), []string{"smarf"}), assert.False}, {"non-matching folder", es.MailFolders(Any(), []string{"smarf"}), assert.False},
{"one of multiple folders", es.MailFolders(All(), []string{"smarf", fld}), assert.True}, {"one of multiple folders", es.MailFolders(Any(), []string{"smarf", fld}), assert.True},
{"all mail", es.Mails(All(), All(), All()), assert.True}, {"all mail", es.Mails(Any(), Any(), Any()), assert.True},
{"no mail", es.Mails(All(), All(), None()), assert.False}, {"no mail", es.Mails(Any(), Any(), None()), assert.False},
{"matching mail", es.Mails(All(), All(), []string{mail}), assert.True}, {"matching mail", es.Mails(Any(), Any(), []string{mail}), assert.True},
{"non-matching mail", es.Mails(All(), All(), []string{"smarf"}), assert.False}, {"non-matching mail", es.Mails(Any(), Any(), []string{"smarf"}), assert.False},
{"one of multiple mails", es.Mails(All(), All(), []string{"smarf", mail}), assert.True}, {"one of multiple mails", es.Mails(Any(), Any(), []string{"smarf", mail}), assert.True},
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
scopes := extendExchangeScopeValues(All(), test.scope) scopes := extendExchangeScopeValues(test.scope)
for _, scope := range scopes { for _, scope := range scopes {
test.expect(t, scope.includesPath(ExchangeMail, path)) test.expect(t, scope.matchesPath(ExchangeMail, path))
}
})
}
}
func (suite *ExchangeSourceSuite) TestExchangeScope_ExcludesInfo() {
const (
TODO = "this is a placeholder, awaiting implemenation of filters"
)
var (
es = NewExchangeRestore()
)
table := []struct {
name string
scope []exchangeScope
info *backup.ExchangeInfo
expect assert.BoolAssertionFunc
}{
{"all user's items", es.Users(All()), nil, assert.False}, // false while a todo
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
scopes := extendExchangeScopeValues(None(), test.scope)
for _, scope := range scopes {
test.expect(t, scope.excludesInfo(ExchangeMail, test.info))
}
})
}
}
func (suite *ExchangeSourceSuite) TestExchangeScope_ExcludesPath() {
const (
usr = "userID"
fld = "mailFolder"
mail = "mailID"
)
var (
path = []string{"tid", usr, "mail", fld, mail}
es = NewExchangeRestore()
)
table := []struct {
name string
scope []exchangeScope
expect assert.BoolAssertionFunc
}{
{"all user's items", es.Users(All()), assert.True},
{"no user's items", es.Users(None()), assert.False},
{"matching user", es.Users([]string{usr}), assert.True},
{"non-maching user", es.Users([]string{"smarf"}), assert.False},
{"one of multiple users", es.Users([]string{"smarf", usr}), assert.True},
{"all folders", es.MailFolders(None(), All()), assert.True},
{"no folders", es.MailFolders(None(), None()), assert.False},
{"matching folder", es.MailFolders(None(), []string{fld}), assert.True},
{"non-matching folder", es.MailFolders(None(), []string{"smarf"}), assert.False},
{"one of multiple folders", es.MailFolders(None(), []string{"smarf", fld}), assert.True},
{"all mail", es.Mails(None(), None(), All()), assert.True},
{"no mail", es.Mails(None(), None(), None()), assert.False},
{"matching mail", es.Mails(None(), None(), []string{mail}), assert.True},
{"non-matching mail", es.Mails(None(), None(), []string{"smarf"}), assert.False},
{"one of multiple mails", es.Mails(None(), None(), []string{"smarf", mail}), assert.True},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
scopes := extendExchangeScopeValues(None(), test.scope)
for _, scope := range scopes {
test.expect(t, scope.excludesPath(ExchangeMail, path))
} }
}) })
} }
@ -688,7 +644,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(), makeDeets(),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All())) er.Include(er.Users(Any()))
return er return er
}, },
[]string{}, []string{},
@ -698,7 +654,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact), makeDeets(contact),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All())) er.Include(er.Users(Any()))
return er return er
}, },
arr(contact), arr(contact),
@ -708,7 +664,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(event), makeDeets(event),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All())) er.Include(er.Users(Any()))
return er return er
}, },
arr(event), arr(event),
@ -718,7 +674,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(mail), makeDeets(mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All())) er.Include(er.Users(Any()))
return er return er
}, },
arr(mail), arr(mail),
@ -728,7 +684,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All())) er.Include(er.Users(Any()))
return er return er
}, },
arr(contact, event, mail), arr(contact, event, mail),
@ -768,7 +724,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All())) er.Include(er.Users(Any()))
er.Exclude(er.Contacts([]string{"uid"}, []string{"cfld"}, []string{"cid"})) er.Exclude(er.Contacts([]string{"uid"}, []string{"cfld"}, []string{"cid"}))
return er return er
}, },
@ -779,7 +735,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All())) er.Include(er.Users(Any()))
er.Exclude(er.Events([]string{"uid"}, []string{"eid"})) er.Exclude(er.Events([]string{"uid"}, []string{"eid"}))
return er return er
}, },
@ -790,7 +746,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All())) er.Include(er.Users(Any()))
er.Exclude(er.Mails([]string{"uid"}, []string{"mfld"}, []string{"mid"})) er.Exclude(er.Mails([]string{"uid"}, []string{"mfld"}, []string{"mid"}))
return er return er
}, },
@ -810,10 +766,10 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() { func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() {
var ( var (
es = NewExchangeRestore() es = NewExchangeRestore()
users = es.Users(All()) users = es.Users(Any())
contacts = es.ContactFolders(All(), All()) contacts = es.ContactFolders(Any(), Any())
events = es.Events(All(), All()) events = es.Events(Any(), Any())
mail = es.MailFolders(All(), All()) mail = es.MailFolders(Any(), Any())
) )
type expect struct { type expect struct {
contact int contact int
@ -857,22 +813,16 @@ func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() {
mail = "mailID" mail = "mailID"
cat = ExchangeMail cat = ExchangeMail
) )
include := func(s []exchangeScope) []exchangeScope {
return extendExchangeScopeValues(All(), s)
}
exclude := func(s []exchangeScope) []exchangeScope {
return extendExchangeScopeValues(None(), s)
}
var ( var (
es = NewExchangeRestore() es = NewExchangeRestore()
inAll = include(es.Users(All())) inAny = extendExchangeScopeValues(es.Users(Any()))
inNone = include(es.Users(None())) inNone = extendExchangeScopeValues(es.Users(None()))
inMail = include(es.Mails(All(), All(), []string{mail})) inMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{mail}))
inOtherMail = include(es.Mails(All(), All(), []string{"smarf"})) inOtherMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{"smarf"}))
exAll = exclude(es.Users(All())) exAny = extendExchangeScopeValues(es.Users(Any()))
exNone = exclude(es.Users(None())) exNone = extendExchangeScopeValues(es.Users(None()))
exMail = exclude(es.Mails(None(), None(), []string{mail})) exMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{mail}))
exOtherMail = exclude(es.Mails(None(), None(), []string{"smarf"})) exOtherMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{"smarf"}))
path = []string{"tid", "user", "mail", "folder", mail} path = []string{"tid", "user", "mail", "folder", mail}
) )
@ -883,15 +833,15 @@ func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() {
expect assert.BoolAssertionFunc expect assert.BoolAssertionFunc
}{ }{
{"empty", nil, nil, assert.False}, {"empty", nil, nil, assert.False},
{"in all", inAll, nil, assert.True}, {"in all", inAny, nil, assert.True},
{"in None", inNone, nil, assert.False}, {"in None", inNone, nil, assert.False},
{"in Mail", inMail, nil, assert.True}, {"in Mail", inMail, nil, assert.True},
{"in Other", inOtherMail, nil, assert.False}, {"in Other", inOtherMail, nil, assert.False},
{"ex all", inAll, exAll, assert.False}, {"ex all", inAny, exAny, assert.False},
{"ex None", inAll, exNone, assert.True}, {"ex None", inAny, exNone, assert.True},
{"in Mail", inAll, exMail, assert.False}, {"in Mail", inAny, exMail, assert.False},
{"in Other", inAll, exOtherMail, assert.True}, {"in Other", inAny, exOtherMail, assert.True},
{"in and ex mail", inMail, exMail, assert.False}, {"in and ex Mail", inMail, exMail, assert.False},
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {

View File

@ -15,15 +15,30 @@ func _() {
_ = x[ExchangeMail-4] _ = x[ExchangeMail-4]
_ = x[ExchangeMailFolder-5] _ = x[ExchangeMailFolder-5]
_ = x[ExchangeUser-6] _ = x[ExchangeUser-6]
_ = x[ExchangeInfoMailSender-107]
_ = x[ExchangeInfoMailSubject-108]
_ = x[ExchangeInfoMailReceivedAfter-109]
_ = x[ExchangeInfoMailReceivedBefore-110]
} }
const _exchangeCategory_name = "ExchangeCategoryUnknownExchangeContactExchangeContactFolderExchangeEventExchangeMailExchangeMailFolderExchangeUser" const (
_exchangeCategory_name_0 = "ExchangeCategoryUnknownExchangeContactExchangeContactFolderExchangeEventExchangeMailExchangeMailFolderExchangeUser"
_exchangeCategory_name_1 = "ExchangeInfoMailSenderExchangeInfoMailSubjectExchangeInfoMailReceivedAfterExchangeInfoMailReceivedBefore"
)
var _exchangeCategory_index = [...]uint8{0, 23, 38, 59, 72, 84, 102, 114} var (
_exchangeCategory_index_0 = [...]uint8{0, 23, 38, 59, 72, 84, 102, 114}
_exchangeCategory_index_1 = [...]uint8{0, 22, 45, 74, 104}
)
func (i exchangeCategory) String() string { func (i exchangeCategory) String() string {
if i < 0 || i >= exchangeCategory(len(_exchangeCategory_index)-1) { switch {
case 0 <= i && i <= 6:
return _exchangeCategory_name_0[_exchangeCategory_index_0[i]:_exchangeCategory_index_0[i+1]]
case 107 <= i && i <= 110:
i -= 107
return _exchangeCategory_name_1[_exchangeCategory_index_1[i]:_exchangeCategory_index_1[i+1]]
default:
return "exchangeCategory(" + strconv.FormatInt(int64(i), 10) + ")" return "exchangeCategory(" + strconv.FormatInt(int64(i), 10) + ")"
} }
return _exchangeCategory_name[_exchangeCategory_index[i]:_exchangeCategory_index[i+1]]
} }

View File

@ -19,21 +19,27 @@ var ErrorBadSelectorCast = errors.New("wrong selector service type")
const ( const (
scopeKeyCategory = "category" scopeKeyCategory = "category"
scopeKeyGranularity = "granularity" scopeKeyGranularity = "granularity"
scopeKeyInfoFilter = "info_filter"
) )
// The granularity exprerssed by the scope. Groups imply non-item granularity,
// such as a directory. Items are individual files or objects.
const ( const (
Group = "group" Group = "group"
Item = "item" Item = "item"
) )
const ( const (
// AllTgt is the target value used to select "all data of <type>" // AnyTgt is the target value used to select "any data of <type>"
// Ex: {user: u1, events: AllTgt) => all events for user u1. // Ex: {user: u1, events: AnyTgt) => all events for user u1.
// In the event that "*" conflicts with a user value, such as a // In the event that "*" conflicts with a user value, such as a
// folder named "*", calls to corso should escape the value with "\*" // folder named "*", calls to corso should escape the value with "\*"
AllTgt = "*" AnyTgt = "*"
// NoneTgt is the target value used to select "no data of <type>" // NoneTgt is the target value used to select "no data of <type>"
// Ex: {user: u1, events: NoneTgt} => no events for user u1. // This is primarily a fallback for empty values. Adding NoneTgt or
// None() to any selector will force all matches() checks on that
// selector to fail.
// Ex: {user: u1, events: NoneTgt} => matches nothing.
NoneTgt = "" NoneTgt = ""
delimiter = "," delimiter = ","
@ -60,12 +66,15 @@ func newSelector(s service) Selector {
} }
} }
// All returns the set matching All values. // Any returns the set matching any value.
func All() []string { func Any() []string {
return []string{AllTgt} return []string{AnyTgt}
} }
// None returns the set matching None of the values. // None returns the set matching None of the values.
// This is primarily a fallback for empty values. Adding None()
// to any selector will force all matches() checks on that selector
// to fail.
func None() []string { func None() []string {
return []string{NoneTgt} return []string{NoneTgt}
} }
@ -98,9 +107,9 @@ func split(s string) []string {
return strings.Split(s, delimiter) return strings.Split(s, delimiter)
} }
// if the provided slice contains All, returns [All] // if the provided slice contains Any, returns [Any]
// if the slice contains None, returns [None] // if the slice contains None, returns [None]
// if the slice contains All and None, returns the first // if the slice contains Any and None, returns the first
// if the slice is empty, returns [None] // if the slice is empty, returns [None]
// otherwise returns the input unchanged // otherwise returns the input unchanged
func normalize(s []string) []string { func normalize(s []string) []string {
@ -108,8 +117,8 @@ func normalize(s []string) []string {
return None() return None()
} }
for _, e := range s { for _, e := range s {
if e == AllTgt { if e == AnyTgt {
return All() return Any()
} }
if e == NoneTgt { if e == NoneTgt {
return None() return None()