Make ExchangeDataCollection use path.Path internally (#820)
* Have exchange data collection store path.Path Still complies with the old FullPath() string interface until we update that. * Pass path.Path to NewCollection for exchange Basically fixes up errors introduced by previous commit. * Fixup exchange recovery path indices All exchange paths now use the path struct, meaning the service, category, and user elements are in the standard positions. * use path package in selector reduction (#822) Currently, during a reduction process, scopes compare their values to the raw split on repoRef. This causes some brittle indexing to retrieve values from the rr, carrying assumptions that are difficult to track across changes. This PR trades the string split for the paths package to better integrate identification of the path values. Adds some mocks and amends some error behaviors in order to fit paths into the current testing schema. Co-authored-by: Keepers <ryanfkeepers@gmail.com>
This commit is contained in:
parent
a17d00c65f
commit
573f55686f
@ -385,7 +385,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
sel.Include(sel.Users(selectors.Any()))
|
sel.Include(sel.Users(selectors.Any()))
|
||||||
}
|
}
|
||||||
|
|
||||||
ds := sel.Reduce(d)
|
ds := sel.Reduce(ctx, d)
|
||||||
if len(ds.Entries) == 0 {
|
if len(ds.Entries) == 0 {
|
||||||
Info(ctx, "nothing to display: no items in the backup match the provided selectors")
|
Info(ctx, "nothing to display: no items in the backup match the provided selectors")
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
absser "github.com/microsoft/kiota-abstractions-go/serialization"
|
absser "github.com/microsoft/kiota-abstractions-go/serialization"
|
||||||
kw "github.com/microsoft/kiota-serialization-json-go"
|
kw "github.com/microsoft/kiota-serialization-json-go"
|
||||||
@ -18,6 +19,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
|
"github.com/alcionai/corso/src/internal/path"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
)
|
)
|
||||||
@ -53,13 +55,13 @@ type Collection struct {
|
|||||||
statusUpdater support.StatusUpdater
|
statusUpdater support.StatusUpdater
|
||||||
// FullPath is the slice representation of the action context passed down through the hierarchy.
|
// FullPath is the slice representation of the action context passed down through the hierarchy.
|
||||||
// The original request can be gleaned from the slice. (e.g. {<tenant ID>, <user ID>, "emails"})
|
// The original request can be gleaned from the slice. (e.g. {<tenant ID>, <user ID>, "emails"})
|
||||||
fullPath []string
|
fullPath path.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExchangeDataCollection creates an ExchangeDataCollection with fullPath is annotated
|
// NewExchangeDataCollection creates an ExchangeDataCollection with fullPath is annotated
|
||||||
func NewCollection(
|
func NewCollection(
|
||||||
user string,
|
user string,
|
||||||
fullPath []string,
|
fullPath path.Path,
|
||||||
collectionType optionIdentifier,
|
collectionType optionIdentifier,
|
||||||
service graph.Service,
|
service graph.Service,
|
||||||
statusUpdater support.StatusUpdater,
|
statusUpdater support.StatusUpdater,
|
||||||
@ -107,7 +109,11 @@ func GetQueryAndSerializeFunc(optID optionIdentifier) (GraphRetrievalFunc, Graph
|
|||||||
|
|
||||||
// FullPath returns the Collection's fullPath []string
|
// FullPath returns the Collection's fullPath []string
|
||||||
func (col *Collection) FullPath() []string {
|
func (col *Collection) FullPath() []string {
|
||||||
return append([]string{}, col.fullPath...)
|
// TODO(ashmrtn): Remove this when data.Collection.FullPath returns a
|
||||||
|
// path.Path. This assumes we don't have adversarial users that use '/' in
|
||||||
|
// their folder names.
|
||||||
|
r := col.fullPath.String()
|
||||||
|
return strings.Split(r, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
// populateByOptionIdentifier is a utility function that uses col.collectionType to be able to serialize
|
// populateByOptionIdentifier is a utility function that uses col.collectionType to be able to serialize
|
||||||
|
|||||||
@ -5,7 +5,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExchangeDataCollectionSuite struct {
|
type ExchangeDataCollectionSuite struct {
|
||||||
@ -44,31 +47,65 @@ func (suite *ExchangeDataCollectionSuite) TestExchangeDataReader_Empty() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeDataCollectionSuite) TestExchangeData_FullPath() {
|
func (suite *ExchangeDataCollectionSuite) TestExchangeData_FullPath() {
|
||||||
|
t := suite.T()
|
||||||
|
tenant := "a-tenant"
|
||||||
user := "a-user"
|
user := "a-user"
|
||||||
fullPath := []string{"a-tenant", user, "emails"}
|
folder := "a-folder"
|
||||||
|
expected := []string{
|
||||||
|
tenant,
|
||||||
|
path.ExchangeService.String(),
|
||||||
|
user,
|
||||||
|
path.EmailCategory.String(),
|
||||||
|
folder,
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath, err := path.Builder{}.Append(folder).ToDataLayerExchangePathForCategory(
|
||||||
|
tenant,
|
||||||
|
user,
|
||||||
|
path.EmailCategory,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
edc := Collection{
|
edc := Collection{
|
||||||
user: user,
|
user: user,
|
||||||
fullPath: fullPath,
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
assert.Equal(suite.T(), edc.FullPath(), fullPath)
|
|
||||||
|
// TODO(ashmrtn): Update when data.Collection.FullPath returns path.Path.
|
||||||
|
assert.Equal(t, expected, edc.FullPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeDataCollectionSuite) TestExchangeDataCollection_NewExchangeDataCollection() {
|
func (suite *ExchangeDataCollectionSuite) TestExchangeDataCollection_NewExchangeDataCollection() {
|
||||||
|
tenant := "a-tenant"
|
||||||
|
user := "a-user"
|
||||||
|
folder := "a-folder"
|
||||||
name := "User"
|
name := "User"
|
||||||
|
|
||||||
|
fullPath, err := path.Builder{}.Append(folder).ToDataLayerExchangePathForCategory(
|
||||||
|
tenant,
|
||||||
|
user,
|
||||||
|
path.EmailCategory,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
edc := Collection{
|
edc := Collection{
|
||||||
user: name,
|
user: name,
|
||||||
fullPath: []string{"Directory", "File", "task"},
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
suite.Equal(name, edc.user)
|
suite.Equal(name, edc.user)
|
||||||
suite.Contains(edc.FullPath(), "Directory")
|
suite.Contains(edc.FullPath(), fullPath.Tenant())
|
||||||
suite.Contains(edc.FullPath(), "File")
|
suite.Contains(edc.FullPath(), fullPath.Service().String())
|
||||||
suite.Contains(edc.FullPath(), "task")
|
suite.Contains(edc.FullPath(), fullPath.Category().String())
|
||||||
|
suite.Contains(edc.FullPath(), fullPath.ResourceOwner())
|
||||||
|
suite.Contains(edc.FullPath(), fullPath.Folder())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeDataCollectionSuite) TestExchangeCollection_AddJob() {
|
func (suite *ExchangeDataCollectionSuite) TestExchangeCollection_AddJob() {
|
||||||
eoc := Collection{
|
eoc := Collection{
|
||||||
user: "Dexter",
|
user: "Dexter",
|
||||||
fullPath: []string{"Today", "is", "currently", "different"},
|
fullPath: nil,
|
||||||
}
|
}
|
||||||
suite.Zero(len(eoc.jobs))
|
suite.Zero(len(eoc.jobs))
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package exchange
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -78,7 +77,7 @@ func resolveCollectionPath(
|
|||||||
resolver graph.ContainerResolver,
|
resolver graph.ContainerResolver,
|
||||||
tenantID, user, folderID string,
|
tenantID, user, folderID string,
|
||||||
category path.CategoryType,
|
category path.CategoryType,
|
||||||
) ([]string, error) {
|
) (path.Path, error) {
|
||||||
if resolver == nil {
|
if resolver == nil {
|
||||||
// Allows caller to default to old-style path.
|
// Allows caller to default to old-style path.
|
||||||
return nil, errors.WithStack(errNilResolver)
|
return nil, errors.WithStack(errNilResolver)
|
||||||
@ -89,19 +88,12 @@ func resolveCollectionPath(
|
|||||||
return nil, errors.Wrap(err, "resolving folder ID")
|
return nil, errors.Wrap(err, "resolving folder ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath, err := p.ToDataLayerExchangePathForCategory(
|
return p.ToDataLayerExchangePathForCategory(
|
||||||
tenantID,
|
tenantID,
|
||||||
user,
|
user,
|
||||||
category,
|
category,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "converting to canonical path")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(ashmrtn): This can return the path directly when Collections take
|
|
||||||
// path.Path.
|
|
||||||
return strings.Split(fullPath.String(), "/"), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterateSelectAllDescendablesForCollection utility function for
|
// IterateSelectAllDescendablesForCollection utility function for
|
||||||
@ -156,7 +148,18 @@ func IterateSelectAllDescendablesForCollections(
|
|||||||
|
|
||||||
// Saving to messages to list. Indexed by folder
|
// Saving to messages to list. Indexed by folder
|
||||||
directory := *entry.GetParentFolderId()
|
directory := *entry.GetParentFolderId()
|
||||||
dirPath := []string{qp.Credentials.TenantID, qp.User, category.String(), directory}
|
|
||||||
|
dirPath, err := path.Builder{}.Append(directory).ToDataLayerExchangePathForCategory(
|
||||||
|
qp.Credentials.TenantID,
|
||||||
|
qp.User,
|
||||||
|
category,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
errs = support.WrapAndAppend("converting to resource path", err, errs)
|
||||||
|
// This really shouldn't be happening unless we have a bad category.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok = collections[directory]; !ok {
|
if _, ok = collections[directory]; !ok {
|
||||||
newPath, err := resolveCollectionPath(
|
newPath, err := resolveCollectionPath(
|
||||||
@ -271,9 +274,21 @@ func IterateSelectAllEventsFromCalendars(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dirPath, err := path.Builder{}.Append(*directory).ToDataLayerExchangePathForCategory(
|
||||||
|
qp.Credentials.TenantID,
|
||||||
|
qp.User,
|
||||||
|
path.EventsCategory,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
// This really shouldn't be happening.
|
||||||
|
errs = support.WrapAndAppend("converting to resource path", err, errs)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
edc := NewCollection(
|
edc := NewCollection(
|
||||||
qp.User,
|
qp.User,
|
||||||
[]string{qp.Credentials.TenantID, qp.User, path.EventsCategory.String(), *directory},
|
dirPath,
|
||||||
events,
|
events,
|
||||||
service,
|
service,
|
||||||
statusUpdater,
|
statusUpdater,
|
||||||
@ -376,11 +391,17 @@ func IterateFilterFolderDirectoriesForCollections(
|
|||||||
}
|
}
|
||||||
|
|
||||||
directory := *folder.GetId()
|
directory := *folder.GetId()
|
||||||
dirPath := []string{
|
|
||||||
|
dirPath, err := path.Builder{}.Append(directory).ToDataLayerExchangePathForCategory(
|
||||||
qp.Credentials.TenantID,
|
qp.Credentials.TenantID,
|
||||||
qp.User,
|
qp.User,
|
||||||
path.EmailCategory.String(),
|
path.EmailCategory,
|
||||||
directory,
|
false,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
// This really shouldn't be happening.
|
||||||
|
errs = support.WrapAndAppend("converting to resource path", err, errs)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := resolveCollectionPath(
|
p, err := resolveCollectionPath(
|
||||||
|
|||||||
@ -268,20 +268,8 @@ func (gc *GraphConnector) RestoreExchangeDataCollection(
|
|||||||
exit bool
|
exit bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// email uses the new path format
|
|
||||||
category = path.ToCategoryType(dc.FullPath()[3])
|
category = path.ToCategoryType(dc.FullPath()[3])
|
||||||
if category == path.UnknownCategory {
|
user = dc.FullPath()[2]
|
||||||
// events and calendar use the old path format
|
|
||||||
category = path.ToCategoryType(dc.FullPath()[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the user from the path index based on the path pattern.
|
|
||||||
switch category {
|
|
||||||
case path.EmailCategory:
|
|
||||||
user = dc.FullPath()[2]
|
|
||||||
case path.ContactsCategory, path.EventsCategory:
|
|
||||||
user = dc.FullPath()[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := pathCounter[directory]; !ok {
|
if _, ok := pathCounter[directory]; !ok {
|
||||||
pathCounter[directory] = true
|
pathCounter[directory] = true
|
||||||
|
|||||||
@ -107,7 +107,7 @@ func (op *RestoreOperation) Run(ctx context.Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// format the details and retrieve the items from kopia
|
// format the details and retrieve the items from kopia
|
||||||
fds := er.Reduce(d)
|
fds := er.Reduce(ctx, d)
|
||||||
if len(fds.Entries) == 0 {
|
if len(fds.Entries) == 0 {
|
||||||
return errors.New("nothing to restore: no items in the backup match the provided selectors")
|
return errors.New("nothing to restore: no items in the backup match the provided selectors")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrorUnknownService = errors.New("unknown service string")
|
||||||
|
|
||||||
type ServiceType int
|
type ServiceType int
|
||||||
|
|
||||||
//go:generate stringer -type=ServiceType -linecomment
|
//go:generate stringer -type=ServiceType -linecomment
|
||||||
@ -21,6 +23,8 @@ func toServiceType(service string) ServiceType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrorUnknownCategory = errors.New("unknown category string")
|
||||||
|
|
||||||
type CategoryType int
|
type CategoryType int
|
||||||
|
|
||||||
//go:generate stringer -type=CategoryType -linecomment
|
//go:generate stringer -type=CategoryType -linecomment
|
||||||
@ -56,12 +60,12 @@ var serviceCategories = map[ServiceType]map[CategoryType]struct{}{
|
|||||||
func validateServiceAndCategoryStrings(s, c string) (ServiceType, CategoryType, error) {
|
func validateServiceAndCategoryStrings(s, c string) (ServiceType, CategoryType, error) {
|
||||||
service := toServiceType(s)
|
service := toServiceType(s)
|
||||||
if service == UnknownService {
|
if service == UnknownService {
|
||||||
return UnknownService, UnknownCategory, errors.Errorf("unknown service string %q", s)
|
return UnknownService, UnknownCategory, errors.Wrapf(ErrorUnknownService, "%q", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
category := ToCategoryType(c)
|
category := ToCategoryType(c)
|
||||||
if category == UnknownCategory {
|
if category == UnknownCategory {
|
||||||
return UnknownService, UnknownCategory, errors.Errorf("unknown category string %q", c)
|
return UnknownService, UnknownCategory, errors.Wrapf(ErrorUnknownCategory, "%q", c)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateServiceAndCategory(service, category); err != nil {
|
if err := validateServiceAndCategory(service, category); err != nil {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package selectors
|
package selectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common"
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
@ -550,39 +551,33 @@ func (ec exchangeCategory) unknownCat() categorizer {
|
|||||||
return ExchangeCategoryUnknown
|
return ExchangeCategoryUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
// transforms a path to a map of identified properties.
|
// pathValues transforms a path to a map of identified properties.
|
||||||
// TODO: this should use service-specific funcs in the Paths pkg. Instead of
|
|
||||||
// peeking at the path directly, the caller should compare against values like
|
|
||||||
// path.UserPN() and path.Folders().
|
|
||||||
//
|
//
|
||||||
// Malformed (ie, short len) paths will return incomplete results.
|
|
||||||
// Example:
|
// Example:
|
||||||
// [tenantID, userPN, "mail", mailFolder, mailID]
|
// [tenantID, service, userPN, category, mailFolder, mailID]
|
||||||
// => {exchUser: userPN, exchMailFolder: mailFolder, exchMail: mailID}
|
// => {exchUser: userPN, exchMailFolder: mailFolder, exchMail: mailID}
|
||||||
func (ec exchangeCategory) pathValues(p []string) map[categorizer]string {
|
func (ec exchangeCategory) pathValues(p path.Path) map[categorizer]string {
|
||||||
m := map[categorizer]string{}
|
var folderCat, itemCat categorizer
|
||||||
if len(p) < 5 {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ec {
|
switch ec {
|
||||||
case ExchangeContact:
|
case ExchangeContact:
|
||||||
m[ExchangeUser] = p[1]
|
folderCat, itemCat = ExchangeContactFolder, ExchangeContact
|
||||||
m[ExchangeContactFolder] = p[3]
|
|
||||||
m[ExchangeContact] = p[4]
|
|
||||||
|
|
||||||
case ExchangeEvent:
|
case ExchangeEvent:
|
||||||
m[ExchangeUser] = p[1]
|
folderCat, itemCat = ExchangeEventCalendar, ExchangeEvent
|
||||||
m[ExchangeEventCalendar] = p[3]
|
|
||||||
m[ExchangeEvent] = p[4]
|
|
||||||
|
|
||||||
case ExchangeMail:
|
case ExchangeMail:
|
||||||
m[ExchangeUser] = p[2]
|
folderCat, itemCat = ExchangeMailFolder, ExchangeMail
|
||||||
m[ExchangeMailFolder] = p[4]
|
|
||||||
m[ExchangeMail] = p[5]
|
default:
|
||||||
|
return map[categorizer]string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m
|
return map[categorizer]string{
|
||||||
|
ExchangeUser: p.ResourceOwner(),
|
||||||
|
folderCat: p.Folder(),
|
||||||
|
itemCat: p.Item(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pathKeys returns the path keys recognized by the receiver's leaf type.
|
// pathKeys returns the path keys recognized by the receiver's leaf type.
|
||||||
@ -677,8 +672,9 @@ func (s ExchangeScope) setDefaults() {
|
|||||||
|
|
||||||
// Reduce filters the entries in a details struct to only those that match the
|
// Reduce filters the entries in a details struct to only those that match the
|
||||||
// inclusions, filters, and exclusions in the selector.
|
// inclusions, filters, and exclusions in the selector.
|
||||||
func (s exchange) Reduce(deets *details.Details) *details.Details {
|
func (s exchange) Reduce(ctx context.Context, deets *details.Details) *details.Details {
|
||||||
return reduce[ExchangeScope](
|
return reduce[ExchangeScope](
|
||||||
|
ctx,
|
||||||
deets,
|
deets,
|
||||||
s.Selector,
|
s.Selector,
|
||||||
map[path.CategoryType]exchangeCategory{
|
map[path.CategoryType]exchangeCategory{
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package selectors
|
package selectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -779,7 +779,7 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesPath() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pth = stubPath(path.ExchangeService, path.EmailCategory, usr, fld, mail)
|
pth = stubPath(suite.T(), usr, []string{fld, mail}, path.EmailCategory)
|
||||||
es = NewExchangeRestore()
|
es = NewExchangeRestore()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -823,12 +823,12 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesPath() {
|
|||||||
func (suite *ExchangeSelectorSuite) TestIdPath() {
|
func (suite *ExchangeSelectorSuite) TestIdPath() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
cat exchangeCategory
|
cat exchangeCategory
|
||||||
pth []string
|
pth path.Path
|
||||||
expect map[exchangeCategory]string
|
expect map[exchangeCategory]string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
ExchangeContact,
|
ExchangeContact,
|
||||||
stubPath(path.ExchangeService, path.ContactsCategory, "uid", "cFld", "cid"),
|
stubPath(suite.T(), "uid", []string{"cFld", "cid"}, path.ContactsCategory),
|
||||||
map[exchangeCategory]string{
|
map[exchangeCategory]string{
|
||||||
ExchangeUser: "uid",
|
ExchangeUser: "uid",
|
||||||
ExchangeContactFolder: "cFld",
|
ExchangeContactFolder: "cFld",
|
||||||
@ -837,7 +837,7 @@ func (suite *ExchangeSelectorSuite) TestIdPath() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
ExchangeEvent,
|
ExchangeEvent,
|
||||||
stubPath(path.ExchangeService, path.EventsCategory, "uid", "eCld", "eid"),
|
stubPath(suite.T(), "uid", []string{"eCld", "eid"}, path.EventsCategory),
|
||||||
map[exchangeCategory]string{
|
map[exchangeCategory]string{
|
||||||
ExchangeUser: "uid",
|
ExchangeUser: "uid",
|
||||||
ExchangeEventCalendar: "eCld",
|
ExchangeEventCalendar: "eCld",
|
||||||
@ -846,20 +846,13 @@ func (suite *ExchangeSelectorSuite) TestIdPath() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
ExchangeMail,
|
ExchangeMail,
|
||||||
stubPath(path.ExchangeService, path.EmailCategory, "uid", "mFld", "mid"),
|
stubPath(suite.T(), "uid", []string{"mFld", "mid"}, path.EmailCategory),
|
||||||
map[exchangeCategory]string{
|
map[exchangeCategory]string{
|
||||||
ExchangeUser: "uid",
|
ExchangeUser: "uid",
|
||||||
ExchangeMailFolder: "mFld",
|
ExchangeMailFolder: "mFld",
|
||||||
ExchangeMail: "mid",
|
ExchangeMail: "mid",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
ExchangeCategoryUnknown,
|
|
||||||
stubPath(path.ExchangeService, path.UnknownCategory, "u", "f", "i"),
|
|
||||||
map[exchangeCategory]string{
|
|
||||||
ExchangeUser: "uid",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.T().Run(test.cat.String(), func(t *testing.T) {})
|
suite.T().Run(test.cat.String(), func(t *testing.T) {})
|
||||||
@ -884,11 +877,8 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// TODO: contacts and events currently do not comply with the mail path pattern
|
contact = stubRepoRef(path.ExchangeService, path.ContactsCategory, "uid", "cfld", "cid")
|
||||||
// contact = stubRepoRef(path.ExchangeService, path.ContactsCategory, "uid", "cfld", "cid")
|
event = stubRepoRef(path.ExchangeService, path.EventsCategory, "uid", "ecld", "eid")
|
||||||
// event = stubRepoRef(path.ExchangeService, path.EventsCategory, "uid", "ecld", "eid")
|
|
||||||
contact = strings.Join([]string{"tid", "uid", path.ContactsCategory.String(), "cfld", "cid"}, "/")
|
|
||||||
event = strings.Join([]string{"tid", "uid", path.EventsCategory.String(), "ecld", "eid"}, "/")
|
|
||||||
mail = stubRepoRef(path.ExchangeService, path.EmailCategory, "uid", "mfld", "mid")
|
mail = stubRepoRef(path.ExchangeService, path.EmailCategory, "uid", "mfld", "mid")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1019,7 +1009,7 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
|
|||||||
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) {
|
||||||
sel := test.makeSelector()
|
sel := test.makeSelector()
|
||||||
results := sel.Reduce(test.deets)
|
results := sel.Reduce(context.Background(), test.deets)
|
||||||
paths := results.Paths()
|
paths := results.Paths()
|
||||||
assert.Equal(t, test.expect, paths)
|
assert.Equal(t, test.expect, paths)
|
||||||
})
|
})
|
||||||
@ -1096,7 +1086,7 @@ func (suite *ExchangeSelectorSuite) TestPasses() {
|
|||||||
mail = setScopesToDefault(es.Mails(Any(), Any(), []string{mid}))
|
mail = setScopesToDefault(es.Mails(Any(), Any(), []string{mid}))
|
||||||
otherMail = setScopesToDefault(es.Mails(Any(), Any(), []string{"smarf"}))
|
otherMail = setScopesToDefault(es.Mails(Any(), Any(), []string{"smarf"}))
|
||||||
noMail = setScopesToDefault(es.Mails(Any(), Any(), None()))
|
noMail = setScopesToDefault(es.Mails(Any(), Any(), None()))
|
||||||
pth = stubPath(path.ExchangeService, path.EmailCategory, "user", "folder", mid)
|
pth = stubPath(suite.T(), "user", []string{"folder", mid}, path.EmailCategory)
|
||||||
)
|
)
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
@ -1232,51 +1222,38 @@ func (suite *ExchangeSelectorSuite) TestExchangeCategory_leafCat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathValues() {
|
func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathValues() {
|
||||||
// TODO: currently events and contacts are non-compliant with email path patterns
|
t := suite.T()
|
||||||
// contactPath := stubPath(path.ExchangeService, path.ContactsCategory, "user", "cfolder", "contactitem")
|
|
||||||
// contactMap := map[categorizer]string{
|
contactPath := stubPath(t, "user", []string{"cfolder", "contactitem"}, path.ContactsCategory)
|
||||||
// ExchangeUser: contactPath[2],
|
|
||||||
// ExchangeContactFolder: contactPath[4],
|
|
||||||
// ExchangeContact: contactPath[5],
|
|
||||||
// }
|
|
||||||
// eventPath := stubPath(path.ExchangeService, path.EventsCategory, "user", "ecalendar", "eventitem")
|
|
||||||
// eventMap := map[categorizer]string{
|
|
||||||
// ExchangeUser: eventPath[2],
|
|
||||||
// ExchangeEventCalendar: eventPath[4],
|
|
||||||
// ExchangeEvent: eventPath[5],
|
|
||||||
// }
|
|
||||||
mailPath := stubPath(path.ExchangeService, path.EmailCategory, "user", "mfolder", "mailitem")
|
|
||||||
mailMap := map[categorizer]string{
|
|
||||||
ExchangeUser: mailPath[2],
|
|
||||||
ExchangeMailFolder: mailPath[4],
|
|
||||||
ExchangeMail: mailPath[5],
|
|
||||||
}
|
|
||||||
contactPath := []string{"tid", "user", path.ContactsCategory.String(), "cfolder", "contactitem"}
|
|
||||||
contactMap := map[categorizer]string{
|
contactMap := map[categorizer]string{
|
||||||
ExchangeUser: contactPath[1],
|
ExchangeUser: contactPath.ResourceOwner(),
|
||||||
ExchangeContactFolder: contactPath[3],
|
ExchangeContactFolder: contactPath.Folder(),
|
||||||
ExchangeContact: contactPath[4],
|
ExchangeContact: contactPath.Item(),
|
||||||
}
|
}
|
||||||
eventPath := []string{"tid", "user", path.EventsCategory.String(), "ecalendar", "eventitem"}
|
eventPath := stubPath(t, "user", []string{"ecalendar", "eventitem"}, path.EventsCategory)
|
||||||
eventMap := map[categorizer]string{
|
eventMap := map[categorizer]string{
|
||||||
ExchangeUser: eventPath[1],
|
ExchangeUser: eventPath.ResourceOwner(),
|
||||||
ExchangeEventCalendar: eventPath[3],
|
ExchangeEventCalendar: eventPath.Folder(),
|
||||||
ExchangeEvent: eventPath[4],
|
ExchangeEvent: eventPath.Item(),
|
||||||
|
}
|
||||||
|
mailPath := stubPath(t, "user", []string{"mfolder", "mailitem"}, path.EmailCategory)
|
||||||
|
mailMap := map[categorizer]string{
|
||||||
|
ExchangeUser: mailPath.ResourceOwner(),
|
||||||
|
ExchangeMailFolder: mailPath.Folder(),
|
||||||
|
ExchangeMail: mailPath.Item(),
|
||||||
}
|
}
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
cat exchangeCategory
|
cat exchangeCategory
|
||||||
path []string
|
path path.Path
|
||||||
expect map[categorizer]string
|
expect map[categorizer]string
|
||||||
}{
|
}{
|
||||||
{ExchangeCategoryUnknown, nil, map[categorizer]string{}},
|
|
||||||
{ExchangeCategoryUnknown, []string{"a"}, map[categorizer]string{}},
|
|
||||||
{ExchangeContact, contactPath, contactMap},
|
{ExchangeContact, contactPath, contactMap},
|
||||||
{ExchangeEvent, eventPath, eventMap},
|
{ExchangeEvent, eventPath, eventMap},
|
||||||
{ExchangeMail, mailPath, mailMap},
|
{ExchangeMail, mailPath, mailMap},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.T().Run(test.cat.String(), func(t *testing.T) {
|
suite.T().Run(string(test.cat), func(t *testing.T) {
|
||||||
assert.Equal(t, test.cat.pathValues(test.path), test.expect)
|
assert.Equal(t, test.cat.pathValues(test.path), test.expect)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1301,7 +1278,7 @@ func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathKeys() {
|
|||||||
{ExchangeUser, user},
|
{ExchangeUser, user},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.T().Run(test.cat.String(), func(t *testing.T) {
|
suite.T().Run(string(test.cat), func(t *testing.T) {
|
||||||
assert.Equal(t, test.cat.pathKeys(), test.expect)
|
assert.Equal(t, test.cat.pathKeys(), test.expect)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/path"
|
"github.com/alcionai/corso/src/internal/path"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
@ -20,8 +21,12 @@ type mockCategorizer string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
unknownCatStub mockCategorizer = ""
|
unknownCatStub mockCategorizer = ""
|
||||||
rootCatStub mockCategorizer = "rootCatStub"
|
// wrap Exchange data here to get around path pkg assertions about path content.
|
||||||
leafCatStub mockCategorizer = "leafCatStub"
|
rootCatStub mockCategorizer = mockCategorizer(ExchangeUser)
|
||||||
|
leafCatStub mockCategorizer = mockCategorizer(ExchangeEvent)
|
||||||
|
|
||||||
|
pathServiceStub = path.ExchangeService
|
||||||
|
pathCatStub = path.EmailCategory
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ categorizer = unknownCatStub
|
var _ categorizer = unknownCatStub
|
||||||
@ -42,7 +47,7 @@ func (mc mockCategorizer) unknownCat() categorizer {
|
|||||||
return unknownCatStub
|
return unknownCatStub
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc mockCategorizer) pathValues(pth []string) map[categorizer]string {
|
func (mc mockCategorizer) pathValues(pth path.Path) map[categorizer]string {
|
||||||
return map[categorizer]string{rootCatStub: "stub"}
|
return map[categorizer]string{rootCatStub: "stub"}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,8 +157,13 @@ func scopeMustHave[T scopeT](t *testing.T, sc T, m map[categorizer]string) {
|
|||||||
|
|
||||||
// stubPath ensures test path production matches that of fullPath design,
|
// stubPath ensures test path production matches that of fullPath design,
|
||||||
// stubbing out static values where necessary.
|
// stubbing out static values where necessary.
|
||||||
func stubPath(service path.ServiceType, data path.CategoryType, resourceOwner, folders, item string) []string {
|
func stubPath(t *testing.T, user string, s []string, cat path.CategoryType) path.Path {
|
||||||
return []string{"tid", service.String(), resourceOwner, data.String(), folders, item}
|
pth, err := path.Builder{}.
|
||||||
|
Append(s...).
|
||||||
|
ToDataLayerExchangePathForCategory("tid", user, cat, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return pth
|
||||||
}
|
}
|
||||||
|
|
||||||
// stubRepoRef ensures test path production matches that of repoRef design,
|
// stubRepoRef ensures test path production matches that of repoRef design,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package selectors
|
package selectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/alcionai/corso/src/internal/path"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -183,31 +184,14 @@ func (c oneDriveCategory) unknownCat() categorizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pathValues transforms a path to a map of identified properties.
|
// pathValues transforms a path to a map of identified properties.
|
||||||
// TODO: this should use service-specific funcs in the Paths pkg. Instead of
|
|
||||||
// peeking at the path directly, the caller should compare against values like
|
|
||||||
// path.UserPN() and path.Folders().
|
|
||||||
//
|
//
|
||||||
// Malformed (ie, short len) paths will return incomplete results.
|
|
||||||
// Example:
|
// Example:
|
||||||
// [tenantID, userPN, "files", folder, fileID]
|
// [tenantID, service, userPN, category, folder, fileID]
|
||||||
// => {odUser: userPN, odFolder: folder, odFileID: fileID}
|
// => {odUser: userPN, odFolder: folder, odFileID: fileID}
|
||||||
func (c oneDriveCategory) pathValues(path []string) map[categorizer]string {
|
func (c oneDriveCategory) pathValues(p path.Path) map[categorizer]string {
|
||||||
m := map[categorizer]string{}
|
return map[categorizer]string{
|
||||||
if len(path) < 3 {
|
OneDriveUser: p.ResourceOwner(),
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m[OneDriveUser] = path[2]
|
|
||||||
/*
|
|
||||||
TODO/Notice:
|
|
||||||
Files contain folder structures, identified
|
|
||||||
in this code as being at index 4. This assumes a single
|
|
||||||
folder, while in reality users can express subfolder
|
|
||||||
hierarchies of arbirary depth. Subfolder handling is coming
|
|
||||||
at a later time.
|
|
||||||
*/
|
|
||||||
// TODO: populate path values when known.
|
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pathKeys returns the path keys recognized by the receiver's leaf type.
|
// pathKeys returns the path keys recognized by the receiver's leaf type.
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
package selectors
|
package selectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"context"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/path"
|
"github.com/alcionai/corso/src/internal/path"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/filters"
|
"github.com/alcionai/corso/src/pkg/filters"
|
||||||
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -32,17 +33,16 @@ type (
|
|||||||
unknownCat() categorizer
|
unknownCat() categorizer
|
||||||
|
|
||||||
// pathValues should produce a map of category:string pairs populated by extracting
|
// pathValues should produce a map of category:string pairs populated by extracting
|
||||||
// values out of the path that match the given categorizer.
|
// values out of the path.Path struct.
|
||||||
//
|
//
|
||||||
// Ex: given a path like "tenant/service/root/dataType/folder/itemID", the func should
|
// Ex: given a path builder like ["tenant", "service", "resource", "dataType", "folder", "itemID"],
|
||||||
// autodetect the data type using 'service' and 'dataType', and use the remaining
|
// the func should use the path to construct a map similar to this:
|
||||||
// details to construct a map similar to this:
|
|
||||||
// {
|
// {
|
||||||
// rootCat: root,
|
// rootCat: resource,
|
||||||
// folderCat: folder,
|
// folderCat: folder,
|
||||||
// itemCat: itemID,
|
// itemCat: itemID,
|
||||||
// }
|
// }
|
||||||
pathValues([]string) map[categorizer]string
|
pathValues(path.Path) map[categorizer]string
|
||||||
|
|
||||||
// pathKeys produces a list of categorizers that can be used as keys in the pathValues
|
// pathKeys produces a list of categorizers that can be used as keys in the pathValues
|
||||||
// map. The combination of the two funcs generically interprets the context of the
|
// map. The combination of the two funcs generically interprets the context of the
|
||||||
@ -207,6 +207,7 @@ func isAnyTarget[T scopeT, C categoryT](s T, cat C) bool {
|
|||||||
// inclusions, filters, and exclusions in the selector.
|
// inclusions, filters, and exclusions in the selector.
|
||||||
//
|
//
|
||||||
func reduce[T scopeT, C categoryT](
|
func reduce[T scopeT, C categoryT](
|
||||||
|
ctx context.Context,
|
||||||
deets *details.Details,
|
deets *details.Details,
|
||||||
s Selector,
|
s Selector,
|
||||||
dataCategories map[path.CategoryType]C,
|
dataCategories map[path.CategoryType]C,
|
||||||
@ -224,10 +225,13 @@ func reduce[T scopeT, C categoryT](
|
|||||||
|
|
||||||
// for each entry, compare that entry against the scopes of the same data type
|
// for each entry, compare that entry against the scopes of the same data type
|
||||||
for _, ent := range deets.Entries {
|
for _, ent := range deets.Entries {
|
||||||
// todo: use Path pkg for this
|
repoPath, err := path.FromDataLayerPath(ent.RepoRef, true)
|
||||||
repoPath := strings.Split(ent.RepoRef, "/")
|
if err != nil {
|
||||||
|
logger.Ctx(ctx).Debugw("transforming repoRef to path", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
dc, ok := dataCategories[pathTypeIn(repoPath)]
|
dc, ok := dataCategories[repoPath.Category()]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -251,34 +255,6 @@ func reduce[T scopeT, C categoryT](
|
|||||||
return reduced
|
return reduced
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the service data type of the path.
|
|
||||||
// TODO: this is a hack. We don't want this identification to occur in this
|
|
||||||
// package. It should get handled in paths, since that's where service- and
|
|
||||||
// data-type-specific assertions are owned.
|
|
||||||
// Ideally, we'd use something like path.DataType() instead of this func.
|
|
||||||
func pathTypeIn(p []string) path.CategoryType {
|
|
||||||
// not all paths will be len=3. Most should be longer.
|
|
||||||
// This just protects us from panicing below.
|
|
||||||
if len(p) < 4 {
|
|
||||||
return path.UnknownCategory
|
|
||||||
}
|
|
||||||
|
|
||||||
if c := path.ToCategoryType(p[3]); c != path.UnknownCategory {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
switch p[2] {
|
|
||||||
case path.EmailCategory.String():
|
|
||||||
return path.EmailCategory
|
|
||||||
case path.ContactsCategory.String():
|
|
||||||
return path.ContactsCategory
|
|
||||||
case path.EventsCategory.String():
|
|
||||||
return path.EventsCategory
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.UnknownCategory
|
|
||||||
}
|
|
||||||
|
|
||||||
// groups each scope by its category of data (specified by the service-selector).
|
// groups each scope by its category of data (specified by the service-selector).
|
||||||
// ex: a slice containing the scopes [mail1, mail2, event1]
|
// ex: a slice containing the scopes [mail1, mail2, event1]
|
||||||
// would produce a map like { mail: [1, 2], event: [1] }
|
// would produce a map like { mail: [1, 2], event: [1] }
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package selectors
|
package selectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -216,65 +217,37 @@ func (suite *SelectorScopesSuite) TestReduce() {
|
|||||||
return details.Details{
|
return details.Details{
|
||||||
DetailsModel: details.DetailsModel{
|
DetailsModel: details.DetailsModel{
|
||||||
Entries: []details.DetailsEntry{
|
Entries: []details.DetailsEntry{
|
||||||
{RepoRef: rootCatStub.String() + "/stub/" + leafCatStub.String()},
|
{
|
||||||
|
RepoRef: stubRepoRef(
|
||||||
|
pathServiceStub,
|
||||||
|
pathCatStub,
|
||||||
|
rootCatStub.String(),
|
||||||
|
"stub",
|
||||||
|
leafCatStub.String(),
|
||||||
|
),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataCats := map[path.CategoryType]mockCategorizer{
|
dataCats := map[path.CategoryType]mockCategorizer{
|
||||||
path.UnknownCategory: rootCatStub,
|
pathCatStub: rootCatStub,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range reduceTestTable {
|
for _, test := range reduceTestTable {
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
ds := deets()
|
ds := deets()
|
||||||
result := reduce[mockScope](&ds, test.sel().Selector, dataCats)
|
result := reduce[mockScope](
|
||||||
|
context.Background(),
|
||||||
|
&ds,
|
||||||
|
test.sel().Selector,
|
||||||
|
dataCats)
|
||||||
require.NotNil(t, result)
|
require.NotNil(t, result)
|
||||||
assert.Len(t, result.Entries, test.expectLen)
|
assert.Len(t, result.Entries, test.expectLen)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SelectorScopesSuite) TestPathTypeIn() {
|
|
||||||
table := []struct {
|
|
||||||
name string
|
|
||||||
pathType path.CategoryType
|
|
||||||
pth []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
pathType: path.UnknownCategory,
|
|
||||||
pth: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "email",
|
|
||||||
pathType: path.EmailCategory,
|
|
||||||
pth: stubPath(path.ExchangeService, path.EmailCategory, "", "", ""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "contact",
|
|
||||||
pathType: path.ContactsCategory,
|
|
||||||
pth: stubPath(path.ExchangeService, path.ContactsCategory, "", "", ""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "event",
|
|
||||||
pathType: path.EventsCategory,
|
|
||||||
pth: stubPath(path.ExchangeService, path.EventsCategory, "", "", ""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bogus",
|
|
||||||
pathType: path.UnknownCategory,
|
|
||||||
pth: []string{"", "", "", "fnords", "", ""},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range table {
|
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
|
||||||
result := pathTypeIn(test.pth)
|
|
||||||
assert.Equal(t, test.pathType, result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *SelectorScopesSuite) TestScopesByCategory() {
|
func (suite *SelectorScopesSuite) TestScopesByCategory() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
s1 := stubScope("")
|
s1 := stubScope("")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user