GC: Backup: Events: Filter Calendars with Scope (#900)
## Description Feature branch enabling filtering based upon `selectors.ExchangeScope` <!-- Insert PR description--> ## Type of change <!--- Please check the type of change your PR introduces: ---> - [x] 🌻 Feature ## Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> *closes #810<issue> ## Test Plan - [x] ⚡ Unit test
This commit is contained in:
parent
679b32697b
commit
06d2a389d9
@ -58,12 +58,7 @@ func (suite *ExchangeIteratorSuite) TestDescendable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadService(t *testing.T) *exchangeService {
|
func loadService(t *testing.T) *exchangeService {
|
||||||
_, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
a := tester.NewM365Account(t)
|
a := tester.NewM365Account(t)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
m365, err := a.M365Config()
|
m365, err := a.M365Config()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -145,7 +140,7 @@ func (suite *ExchangeIteratorSuite) TestIterativeFunctions() {
|
|||||||
}, {
|
}, {
|
||||||
name: "Folder Iterative Check Mail",
|
name: "Folder Iterative Check Mail",
|
||||||
queryFunction: GetAllFolderNamesForUser,
|
queryFunction: GetAllFolderNamesForUser,
|
||||||
iterativeFunction: IterateFilterFolderDirectoriesForCollections,
|
iterativeFunction: IterateFilterContainersForCollections,
|
||||||
scope: mailScope,
|
scope: mailScope,
|
||||||
transformer: models.CreateMailFolderCollectionResponseFromDiscriminatorValue,
|
transformer: models.CreateMailFolderCollectionResponseFromDiscriminatorValue,
|
||||||
folderNames: map[string]struct{}{
|
folderNames: map[string]struct{}{
|
||||||
@ -156,7 +151,7 @@ func (suite *ExchangeIteratorSuite) TestIterativeFunctions() {
|
|||||||
}, {
|
}, {
|
||||||
name: "Folder Iterative Check Contacts",
|
name: "Folder Iterative Check Contacts",
|
||||||
queryFunction: GetAllContactFolderNamesForUser,
|
queryFunction: GetAllContactFolderNamesForUser,
|
||||||
iterativeFunction: IterateFilterFolderDirectoriesForCollections,
|
iterativeFunction: IterateFilterContainersForCollections,
|
||||||
scope: contactScope,
|
scope: contactScope,
|
||||||
transformer: models.CreateContactFolderCollectionResponseFromDiscriminatorValue,
|
transformer: models.CreateContactFolderCollectionResponseFromDiscriminatorValue,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,9 +5,12 @@ 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/connector/graph"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceFunctionsIntegrationSuite struct {
|
type ServiceFunctionsIntegrationSuite struct {
|
||||||
@ -77,6 +80,7 @@ func (suite *ServiceFunctionsIntegrationSuite) TestGetAllCalendars() {
|
|||||||
|
|
||||||
func (suite *ServiceFunctionsIntegrationSuite) TestGetAllContactFolders() {
|
func (suite *ServiceFunctionsIntegrationSuite) TestGetAllContactFolders() {
|
||||||
gs := loadService(suite.T())
|
gs := loadService(suite.T())
|
||||||
|
user := tester.M365UserID(suite.T())
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
@ -86,14 +90,14 @@ func (suite *ServiceFunctionsIntegrationSuite) TestGetAllContactFolders() {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "plain lookup",
|
name: "plain lookup",
|
||||||
user: suite.m365UserID,
|
user: user,
|
||||||
expectCount: assert.Greater,
|
expectCount: assert.Greater,
|
||||||
expectErr: assert.NoError,
|
expectErr: assert.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "root folder",
|
name: "root folder",
|
||||||
contains: "Contact",
|
contains: "Contact",
|
||||||
user: suite.m365UserID,
|
user: user,
|
||||||
expectCount: assert.Greater,
|
expectCount: assert.Greater,
|
||||||
expectErr: assert.NoError,
|
expectErr: assert.NoError,
|
||||||
},
|
},
|
||||||
@ -106,7 +110,7 @@ func (suite *ServiceFunctionsIntegrationSuite) TestGetAllContactFolders() {
|
|||||||
{
|
{
|
||||||
name: "nonsense matcher",
|
name: "nonsense matcher",
|
||||||
contains: "∂ç∂ç∂√≈∂ƒß∂ç√ßç√≈ç√ß∂ƒçß√ß≈∂ƒßç√",
|
contains: "∂ç∂ç∂√≈∂ƒß∂ç√ßç√≈ç√ß∂ƒçß√ß≈∂ƒßç√",
|
||||||
user: suite.m365UserID,
|
user: user,
|
||||||
expectCount: assert.Equal,
|
expectCount: assert.Equal,
|
||||||
expectErr: assert.NoError,
|
expectErr: assert.NoError,
|
||||||
},
|
},
|
||||||
@ -164,3 +168,83 @@ func (suite *ServiceFunctionsIntegrationSuite) TestGetAllMailFolders() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *ServiceFunctionsIntegrationSuite) TestCollectContainers() {
|
||||||
|
ctx := context.Background()
|
||||||
|
failFast := false
|
||||||
|
containerCount := 1
|
||||||
|
t := suite.T()
|
||||||
|
user := tester.M365UserID(t)
|
||||||
|
a := tester.NewM365Account(t)
|
||||||
|
credentials, err := a.M365Config()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name, contains string
|
||||||
|
getScope func() selectors.ExchangeScope
|
||||||
|
expectedCount assert.ComparisonAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "All Events",
|
||||||
|
contains: "Birthdays",
|
||||||
|
expectedCount: assert.Greater,
|
||||||
|
getScope: func() selectors.ExchangeScope {
|
||||||
|
sel := selectors.NewExchangeBackup()
|
||||||
|
sel.Include(sel.EventCalendars([]string{user}, selectors.Any()))
|
||||||
|
|
||||||
|
scopes := sel.Scopes()
|
||||||
|
assert.Equal(t, len(scopes), 1)
|
||||||
|
|
||||||
|
return scopes[0]
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "Default Calendar",
|
||||||
|
contains: DefaultCalendar,
|
||||||
|
expectedCount: assert.Equal,
|
||||||
|
getScope: func() selectors.ExchangeScope {
|
||||||
|
sel := selectors.NewExchangeBackup()
|
||||||
|
sel.Include(sel.EventCalendars([]string{user}, []string{DefaultCalendar}))
|
||||||
|
|
||||||
|
scopes := sel.Scopes()
|
||||||
|
assert.Equal(t, len(scopes), 1)
|
||||||
|
|
||||||
|
return scopes[0]
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "Default Mail",
|
||||||
|
contains: DefaultMailFolder,
|
||||||
|
expectedCount: assert.Equal,
|
||||||
|
getScope: func() selectors.ExchangeScope {
|
||||||
|
sel := selectors.NewExchangeBackup()
|
||||||
|
sel.Include(sel.MailFolders([]string{user}, []string{DefaultMailFolder}))
|
||||||
|
|
||||||
|
scopes := sel.Scopes()
|
||||||
|
assert.Equal(t, len(scopes), 1)
|
||||||
|
|
||||||
|
return scopes[0]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
qp := graph.QueryParams{
|
||||||
|
User: user,
|
||||||
|
Scope: test.getScope(),
|
||||||
|
FailFast: failFast,
|
||||||
|
Credentials: credentials,
|
||||||
|
}
|
||||||
|
collections := make(map[string]*Collection)
|
||||||
|
err := CollectFolders(ctx, qp, collections, nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
test.expectedCount(t, len(collections), containerCount)
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(collections))
|
||||||
|
for k := range collections {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
t.Logf("Collections Made: %v\n", keys)
|
||||||
|
assert.Contains(t, keys, test.contains)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package exchange
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
|
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
@ -117,6 +118,9 @@ func IterateSelectAllDescendablesForCollections(
|
|||||||
// utility function for iterating through events
|
// utility function for iterating through events
|
||||||
// and storing events in collections based on
|
// and storing events in collections based on
|
||||||
// the calendarID which originates from M365.
|
// the calendarID which originates from M365.
|
||||||
|
// @param pageItem is a CalendarCollectionResponse possessing two populated fields:
|
||||||
|
// - id - M365 ID
|
||||||
|
// - Name - Calendar Name
|
||||||
func IterateSelectAllEventsFromCalendars(
|
func IterateSelectAllEventsFromCalendars(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
qp graph.QueryParams,
|
qp graph.QueryParams,
|
||||||
@ -124,83 +128,62 @@ func IterateSelectAllEventsFromCalendars(
|
|||||||
collections map[string]*Collection,
|
collections map[string]*Collection,
|
||||||
statusUpdater support.StatusUpdater,
|
statusUpdater support.StatusUpdater,
|
||||||
) func(any) bool {
|
) func(any) bool {
|
||||||
|
var (
|
||||||
|
isEnabled bool
|
||||||
|
service graph.Service
|
||||||
|
)
|
||||||
|
|
||||||
return func(pageItem any) bool {
|
return func(pageItem any) bool {
|
||||||
if pageItem == nil {
|
if !isEnabled {
|
||||||
return true
|
// Create Collections based on qp.Scope
|
||||||
|
err := CollectFolders(ctx, qp, collections, statusUpdater)
|
||||||
|
if err != nil {
|
||||||
|
errUpdater(
|
||||||
|
qp.User,
|
||||||
|
errors.Wrap(err, support.ConnectorStackErrorTrace(err)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
service, err = createService(qp.Credentials, qp.FailFast)
|
||||||
|
if err != nil {
|
||||||
|
errUpdater(qp.User, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
isEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
shell, ok := pageItem.(models.Calendarable)
|
pageItem = CreateCalendarDisplayable(pageItem)
|
||||||
|
|
||||||
|
calendar, ok := pageItem.(displayable)
|
||||||
if !ok {
|
if !ok {
|
||||||
errUpdater(qp.User, errors.New("casting pageItem to models.Calendarable"))
|
errUpdater(
|
||||||
|
qp.User,
|
||||||
|
fmt.Errorf("unable to parse pageItem into CalendarDisplayable: %T", pageItem),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if calendar.GetDisplayName() == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
service, err := createService(qp.Credentials, qp.FailFast)
|
collection, ok := collections[*calendar.GetDisplayName()]
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
eventIDs, err := ReturnEventIDsFromCalendar(ctx, service, qp.User, *calendar.GetId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errUpdater(
|
errUpdater(
|
||||||
qp.User,
|
qp.User,
|
||||||
errors.Wrap(err, "creating service for IterateSelectAllEventsFromCalendars"))
|
errors.Wrap(err, support.ConnectorStackErrorTrace(err)))
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
eventResponseable, err := service.Client().
|
collection.jobs = append(collection.jobs, eventIDs...)
|
||||||
UsersById(qp.User).
|
|
||||||
CalendarsById(*shell.GetId()).
|
|
||||||
Events().Get(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
errUpdater(qp.User, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
directory := shell.GetName()
|
|
||||||
owner := shell.GetOwner()
|
|
||||||
|
|
||||||
// Conditional Guard Checks
|
|
||||||
if eventResponseable == nil ||
|
|
||||||
directory == nil ||
|
|
||||||
owner == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
eventables := eventResponseable.GetValue()
|
|
||||||
// Clause is true when Calendar has does not have any events
|
|
||||||
if eventables == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := collections[*directory]; !ok {
|
|
||||||
service, err := createService(qp.Credentials, qp.FailFast)
|
|
||||||
if err != nil {
|
|
||||||
errUpdater(qp.User, err)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
dirPath, err := path.Builder{}.Append(*directory).ToDataLayerExchangePathForCategory(
|
|
||||||
qp.Credentials.TenantID,
|
|
||||||
qp.User,
|
|
||||||
path.EventsCategory,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
// we should never hit this error
|
|
||||||
errUpdater("converting to resource path", err)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
edc := NewCollection(
|
|
||||||
qp.User,
|
|
||||||
dirPath,
|
|
||||||
events,
|
|
||||||
service,
|
|
||||||
statusUpdater,
|
|
||||||
)
|
|
||||||
collections[*directory] = &edc
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, event := range eventables {
|
|
||||||
collections[*directory].AddJob(*event.GetId())
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -216,7 +199,11 @@ func IterateAndFilterDescendablesForCollections(
|
|||||||
collections map[string]*Collection,
|
collections map[string]*Collection,
|
||||||
statusUpdater support.StatusUpdater,
|
statusUpdater support.StatusUpdater,
|
||||||
) func(any) bool {
|
) func(any) bool {
|
||||||
var isFilterSet bool
|
var (
|
||||||
|
isFilterSet bool
|
||||||
|
resolver graph.ContainerResolver
|
||||||
|
cache map[string]string
|
||||||
|
)
|
||||||
|
|
||||||
return func(descendItem any) bool {
|
return func(descendItem any) bool {
|
||||||
if !isFilterSet {
|
if !isFilterSet {
|
||||||
@ -230,6 +217,13 @@ func IterateAndFilterDescendablesForCollections(
|
|||||||
errUpdater(qp.User, err)
|
errUpdater(qp.User, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// Caches folder directories
|
||||||
|
cache = make(map[string]string, 0)
|
||||||
|
|
||||||
|
resolver, err = maybeGetAndPopulateFolderResolver(ctx, qp, path.EmailCategory)
|
||||||
|
if err != nil {
|
||||||
|
errUpdater("getting folder resolver for category "+path.EmailCategory.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
isFilterSet = true
|
isFilterSet = true
|
||||||
}
|
}
|
||||||
@ -240,7 +234,20 @@ func IterateAndFilterDescendablesForCollections(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Saving only messages for the created directories
|
// Saving only messages for the created directories
|
||||||
directory := *message.GetParentFolderId()
|
folderID := *message.GetParentFolderId()
|
||||||
|
|
||||||
|
directory, ok := cache[folderID]
|
||||||
|
if !ok {
|
||||||
|
result := translateIDToDirectory(ctx, qp, resolver, folderID)
|
||||||
|
if result == "" {
|
||||||
|
errUpdater(qp.User,
|
||||||
|
errors.New("getCollectionPath experienced error during translateID"))
|
||||||
|
}
|
||||||
|
|
||||||
|
cache[folderID] = result
|
||||||
|
directory = result
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok = collections[directory]; !ok {
|
if _, ok = collections[directory]; !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -251,7 +258,65 @@ func IterateAndFilterDescendablesForCollections(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func IterateFilterFolderDirectoriesForCollections(
|
func translateIDToDirectory(
|
||||||
|
ctx context.Context,
|
||||||
|
qp graph.QueryParams,
|
||||||
|
resolver graph.ContainerResolver,
|
||||||
|
directoryID string,
|
||||||
|
) string {
|
||||||
|
fullPath, err := getCollectionPath(ctx, qp, resolver, directoryID, path.EmailCategory)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullPath.Folder()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCategoryAndValidation(es selectors.ExchangeScope) (
|
||||||
|
optionIdentifier,
|
||||||
|
path.CategoryType,
|
||||||
|
func(namePtr *string) bool,
|
||||||
|
) {
|
||||||
|
var (
|
||||||
|
option = scopeToOptionIdentifier(es)
|
||||||
|
category path.CategoryType
|
||||||
|
validate func(namePtr *string) bool
|
||||||
|
)
|
||||||
|
|
||||||
|
switch option {
|
||||||
|
case messages:
|
||||||
|
category = path.EmailCategory
|
||||||
|
validate = func(namePtr *string) bool {
|
||||||
|
if namePtr == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return !es.Matches(selectors.ExchangeMailFolder, *namePtr)
|
||||||
|
}
|
||||||
|
case contacts:
|
||||||
|
category = path.ContactsCategory
|
||||||
|
validate = func(namePtr *string) bool {
|
||||||
|
if namePtr == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return !es.Matches(selectors.ExchangeContactFolder, *namePtr)
|
||||||
|
}
|
||||||
|
case events:
|
||||||
|
category = path.EventsCategory
|
||||||
|
validate = func(namePtr *string) bool {
|
||||||
|
if namePtr == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return !es.Matches(selectors.ExchangeEventCalendar, *namePtr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return option, category, validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func IterateFilterContainersForCollections(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
qp graph.QueryParams,
|
qp graph.QueryParams,
|
||||||
errUpdater func(string, error),
|
errUpdater func(string, error),
|
||||||
@ -265,30 +330,12 @@ func IterateFilterFolderDirectoriesForCollections(
|
|||||||
err error
|
err error
|
||||||
option optionIdentifier
|
option optionIdentifier
|
||||||
category path.CategoryType
|
category path.CategoryType
|
||||||
validate func(string) bool
|
validate func(*string) bool
|
||||||
)
|
)
|
||||||
|
|
||||||
return func(folderItem any) bool {
|
return func(folderItem any) bool {
|
||||||
folder, ok := folderItem.(displayable)
|
|
||||||
if !ok {
|
|
||||||
errUpdater(qp.User, errors.New("casting folderItem to displayable"))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isSet {
|
if !isSet {
|
||||||
option = scopeToOptionIdentifier(qp.Scope)
|
option, category, validate = getCategoryAndValidation(qp.Scope)
|
||||||
switch option {
|
|
||||||
case messages:
|
|
||||||
category = path.EmailCategory
|
|
||||||
validate = func(name string) bool {
|
|
||||||
return !qp.Scope.Matches(selectors.ExchangeMailFolder, name)
|
|
||||||
}
|
|
||||||
case contacts:
|
|
||||||
category = path.ContactsCategory
|
|
||||||
validate = func(name string) bool {
|
|
||||||
return !qp.Scope.Matches(selectors.ExchangeContactFolder, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolver, err = maybeGetAndPopulateFolderResolver(ctx, qp, category)
|
resolver, err = maybeGetAndPopulateFolderResolver(ctx, qp, category)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -298,19 +345,27 @@ func IterateFilterFolderDirectoriesForCollections(
|
|||||||
isSet = true
|
isSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue to iterate if folder name is empty
|
if option == events {
|
||||||
if folder.GetDisplayName() == nil {
|
folderItem = CreateCalendarDisplayable(folderItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
folder, ok := folderItem.(displayable)
|
||||||
|
if !ok {
|
||||||
|
errUpdater(qp.User,
|
||||||
|
fmt.Errorf("unable to convert input of %T for category: %s", folderItem, category.String()),
|
||||||
|
)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if validate(*folder.GetDisplayName()) {
|
if validate(folder.GetDisplayName()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if option == contacts {
|
if option == messages {
|
||||||
collectPath = *folder.GetDisplayName()
|
|
||||||
} else {
|
|
||||||
collectPath = *folder.GetId()
|
collectPath = *folder.GetId()
|
||||||
|
} else {
|
||||||
|
collectPath = *folder.GetDisplayName()
|
||||||
}
|
}
|
||||||
|
|
||||||
dirPath, err := getCollectionPath(
|
dirPath, err := getCollectionPath(
|
||||||
@ -345,7 +400,7 @@ func IterateFilterFolderDirectoriesForCollections(
|
|||||||
service,
|
service,
|
||||||
statusUpdater,
|
statusUpdater,
|
||||||
)
|
)
|
||||||
collections[*folder.GetId()] = &temp
|
collections[*folder.GetDisplayName()] = &temp
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -565,3 +620,53 @@ func ReturnContactIDsFromDirectory(ctx context.Context, gs graph.Service, user,
|
|||||||
|
|
||||||
return stringArray, nil
|
return stringArray, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReturnEventIDsFromCalendar returns a list of all M365IDs of events of the targeted Calendar.
|
||||||
|
func ReturnEventIDsFromCalendar(
|
||||||
|
ctx context.Context,
|
||||||
|
gs graph.Service,
|
||||||
|
user, calendarID string,
|
||||||
|
) ([]string, error) {
|
||||||
|
ids := []string{}
|
||||||
|
|
||||||
|
response, err := gs.Client().
|
||||||
|
UsersById(user).
|
||||||
|
CalendarsById(calendarID).
|
||||||
|
Events().Get(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pageIterator, err := msgraphgocore.NewPageIterator(
|
||||||
|
response,
|
||||||
|
gs.Adapter(),
|
||||||
|
models.CreateEventCollectionResponseFromDiscriminatorValue,
|
||||||
|
)
|
||||||
|
|
||||||
|
callbackFunc := func(pageItem any) bool {
|
||||||
|
entry, ok := pageItem.(models.Eventable)
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("casting pageItem to models.Eventable")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ids = append(ids, *entry.GetId())
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if iterateErr := pageIterator.Iterate(ctx, callbackFunc); iterateErr != nil {
|
||||||
|
return nil,
|
||||||
|
errors.Wrap(iterateErr, support.ConnectorStackErrorTrace(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -122,6 +122,8 @@ func RetrieveMessageDataForUser(ctx context.Context, gs graph.Service, user, m36
|
|||||||
return gs.Client().UsersById(user).MessagesById(m365ID).Get(ctx, nil)
|
return gs.Client().UsersById(user).MessagesById(m365ID).Get(ctx, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollectFolders is a utility function for creating Collections based off parameters found
|
||||||
|
// in the ExchangeScope found in the graph.QueryParams
|
||||||
func CollectFolders(
|
func CollectFolders(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
qp graph.QueryParams,
|
qp graph.QueryParams,
|
||||||
@ -149,6 +151,9 @@ func CollectFolders(
|
|||||||
case contacts:
|
case contacts:
|
||||||
query = GetAllContactFolderNamesForUser
|
query = GetAllContactFolderNamesForUser
|
||||||
transformer = models.CreateContactFolderCollectionResponseFromDiscriminatorValue
|
transformer = models.CreateContactFolderCollectionResponseFromDiscriminatorValue
|
||||||
|
case events:
|
||||||
|
query = GetAllCalendarNamesForUser
|
||||||
|
transformer = models.CreateCalendarCollectionResponseFromDiscriminatorValue
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported option %s used in CollectFolders", option)
|
return fmt.Errorf("unsupported option %s used in CollectFolders", option)
|
||||||
}
|
}
|
||||||
@ -176,7 +181,7 @@ func CollectFolders(
|
|||||||
err = support.WrapAndAppend(id, e, err)
|
err = support.WrapAndAppend(id, e, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
callbackFunc := IterateFilterFolderDirectoriesForCollections(
|
callbackFunc := IterateFilterContainersForCollections(
|
||||||
ctx,
|
ctx,
|
||||||
qp,
|
qp,
|
||||||
errUpdater,
|
errUpdater,
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package connector
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -14,7 +13,6 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common"
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
"github.com/alcionai/corso/src/internal/connector/exchange"
|
"github.com/alcionai/corso/src/internal/connector/exchange"
|
||||||
"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/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
@ -85,36 +83,54 @@ func (suite *GraphConnectorIntegrationSuite) TestSetTenantUsers() {
|
|||||||
// - events
|
// - events
|
||||||
func (suite *GraphConnectorIntegrationSuite) TestExchangeDataCollection() {
|
func (suite *GraphConnectorIntegrationSuite) TestExchangeDataCollection() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
t := suite.T()
|
connector := loadConnector(ctx, suite.T())
|
||||||
connector := loadConnector(ctx, t)
|
tests := []struct {
|
||||||
sel := selectors.NewExchangeBackup()
|
name string
|
||||||
sel.Include(sel.Users([]string{suite.user}))
|
getSelector func(t *testing.T) selectors.Selector
|
||||||
collectionList, err := connector.ExchangeDataCollection(context.Background(), sel.Selector)
|
}{
|
||||||
assert.NotNil(t, collectionList, "collection list")
|
{
|
||||||
assert.NoError(t, err)
|
name: suite.user + " Email",
|
||||||
assert.Zero(t, connector.status.ObjectCount)
|
getSelector: func(t *testing.T) selectors.Selector {
|
||||||
assert.Zero(t, connector.status.FolderCount)
|
sel := selectors.NewExchangeBackup()
|
||||||
assert.Zero(t, connector.status.Successful)
|
sel.Include(sel.MailFolders([]string{suite.user}, []string{exchange.DefaultMailFolder}))
|
||||||
|
|
||||||
streams := make(map[string]<-chan data.Stream)
|
return sel.Selector
|
||||||
// Verify Items() call returns an iterable channel(e.g. a channel that has been closed)
|
},
|
||||||
for _, collection := range collectionList {
|
},
|
||||||
temp := collection.Items()
|
{
|
||||||
testName := collection.FullPath().ResourceOwner()
|
name: suite.user + " Contacts",
|
||||||
streams[testName] = temp
|
getSelector: func(t *testing.T) selectors.Selector {
|
||||||
|
sel := selectors.NewExchangeBackup()
|
||||||
|
sel.Include(sel.ContactFolders([]string{suite.user}, []string{exchange.DefaultContactFolder}))
|
||||||
|
|
||||||
|
return sel.Selector
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: suite.user + " Events",
|
||||||
|
getSelector: func(t *testing.T) selectors.Selector {
|
||||||
|
sel := selectors.NewExchangeBackup()
|
||||||
|
sel.Include(sel.EventCalendars([]string{suite.user}, []string{exchange.DefaultCalendar}))
|
||||||
|
|
||||||
|
return sel.Selector
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
status := connector.AwaitStatus()
|
for _, test := range tests {
|
||||||
assert.NotZero(t, status.Successful)
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
collection, err := connector.ExchangeDataCollection(ctx, test.getSelector(t))
|
||||||
for name, channel := range streams {
|
require.NoError(t, err)
|
||||||
suite.T().Run(name, func(t *testing.T) {
|
assert.Equal(t, len(collection), 1)
|
||||||
t.Logf("Test: %s\t Items: %d", name, len(channel))
|
channel := collection[0].Items()
|
||||||
for object := range channel {
|
for object := range channel {
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
_, err := buf.ReadFrom(object.ToReader())
|
_, err := buf.ReadFrom(object.ToReader())
|
||||||
assert.NoError(t, err, "received a buf.Read error")
|
assert.NoError(t, err, "received a buf.Read error")
|
||||||
}
|
}
|
||||||
|
status := connector.AwaitStatus()
|
||||||
|
assert.NotZero(t, status.Successful)
|
||||||
|
t.Log(status.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,7 +143,7 @@ func (suite *GraphConnectorIntegrationSuite) TestMailSerializationRegression() {
|
|||||||
t := suite.T()
|
t := suite.T()
|
||||||
connector := loadConnector(ctx, t)
|
connector := loadConnector(ctx, t)
|
||||||
sel := selectors.NewExchangeBackup()
|
sel := selectors.NewExchangeBackup()
|
||||||
sel.Include(sel.MailFolders([]string{suite.user}, []string{"Inbox"}))
|
sel.Include(sel.MailFolders([]string{suite.user}, []string{exchange.DefaultMailFolder}))
|
||||||
eb, err := sel.ToExchangeBackup()
|
eb, err := sel.ToExchangeBackup()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -162,27 +178,40 @@ func (suite *GraphConnectorIntegrationSuite) TestMailSerializationRegression() {
|
|||||||
// and to store contact within Collection. Downloaded contacts are run through
|
// and to store contact within Collection. Downloaded contacts are run through
|
||||||
// a regression test to ensure that downloaded items can be uploaded.
|
// a regression test to ensure that downloaded items can be uploaded.
|
||||||
func (suite *GraphConnectorIntegrationSuite) TestContactSerializationRegression() {
|
func (suite *GraphConnectorIntegrationSuite) TestContactSerializationRegression() {
|
||||||
ctx := context.Background()
|
connector := loadConnector(context.Background(), suite.T())
|
||||||
t := suite.T()
|
|
||||||
sel := selectors.NewExchangeBackup()
|
|
||||||
sel.Include(sel.ContactFolders([]string{suite.user}, selectors.Any()))
|
|
||||||
eb, err := sel.ToExchangeBackup()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
scopes := eb.Scopes()
|
tests := []struct {
|
||||||
connector := loadConnector(ctx, t)
|
name string
|
||||||
|
getCollection func(t *testing.T) []*exchange.Collection
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Default Contact Folder",
|
||||||
|
getCollection: func(t *testing.T) []*exchange.Collection {
|
||||||
|
sel := selectors.NewExchangeBackup()
|
||||||
|
sel.Include(sel.ContactFolders([]string{suite.user}, []string{exchange.DefaultContactFolder}))
|
||||||
|
eb, err := sel.ToExchangeBackup()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
suite.Len(scopes, 1)
|
scopes := eb.Scopes()
|
||||||
contactsOnly := scopes[0]
|
|
||||||
collections, err := connector.createCollections(context.Background(), contactsOnly)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
number := 0
|
suite.Len(scopes, 1)
|
||||||
|
contactsOnly := scopes[0]
|
||||||
|
collections, err := connector.createCollections(context.Background(), contactsOnly)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
for _, edc := range collections {
|
return collections
|
||||||
testName := fmt.Sprintf("%s_ContactFolder_%d", edc.FullPath().ResourceOwner(), number)
|
},
|
||||||
suite.T().Run(testName, func(t *testing.T) {
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
edcs := test.getCollection(t)
|
||||||
|
assert.Equal(t, len(edcs), 1)
|
||||||
|
edc := edcs[0]
|
||||||
|
assert.Equal(t, edc.FullPath().Folder(), exchange.DefaultContactFolder)
|
||||||
streamChannel := edc.Items()
|
streamChannel := edc.Items()
|
||||||
|
count := 0
|
||||||
for stream := range streamChannel {
|
for stream := range streamChannel {
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
read, err := buf.ReadFrom(stream.ToReader())
|
read, err := buf.ReadFrom(stream.ToReader())
|
||||||
@ -190,52 +219,80 @@ func (suite *GraphConnectorIntegrationSuite) TestContactSerializationRegression(
|
|||||||
assert.NotZero(t, read)
|
assert.NotZero(t, read)
|
||||||
contact, err := support.CreateContactFromBytes(buf.Bytes())
|
contact, err := support.CreateContactFromBytes(buf.Bytes())
|
||||||
assert.NotNil(t, contact)
|
assert.NotNil(t, contact)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err, "error on converting contact bytes: "+string(buf.Bytes()))
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
number++
|
assert.NotZero(t, count)
|
||||||
|
|
||||||
|
status := connector.AwaitStatus()
|
||||||
|
suite.NotNil(status)
|
||||||
|
suite.Equal(status.ObjectCount, status.Successful)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
status := connector.AwaitStatus()
|
|
||||||
suite.NotNil(status)
|
|
||||||
suite.Equal(status.ObjectCount, status.Successful)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestEventsSerializationRegression ensures functionality of createCollections
|
// TestEventsSerializationRegression ensures functionality of createCollections
|
||||||
// to be able to successfully query, download and restore event objects
|
// to be able to successfully query, download and restore event objects
|
||||||
func (suite *GraphConnectorIntegrationSuite) TestEventsSerializationRegression() {
|
func (suite *GraphConnectorIntegrationSuite) TestEventsSerializationRegression() {
|
||||||
ctx := context.Background()
|
connector := loadConnector(context.Background(), suite.T())
|
||||||
t := suite.T()
|
|
||||||
connector := loadConnector(ctx, t)
|
|
||||||
sel := selectors.NewExchangeBackup()
|
|
||||||
sel.Include(sel.EventCalendars([]string{suite.user}, selectors.Any()))
|
|
||||||
scopes := sel.Scopes()
|
|
||||||
suite.Equal(len(scopes), 1)
|
|
||||||
collections, err := connector.createCollections(context.Background(), scopes[0])
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, edc := range collections {
|
tests := []struct {
|
||||||
streamChannel := edc.Items()
|
name, expected string
|
||||||
number := 0
|
getCollection func(t *testing.T) []*exchange.Collection
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Default Event Calendar",
|
||||||
|
expected: exchange.DefaultCalendar,
|
||||||
|
getCollection: func(t *testing.T) []*exchange.Collection {
|
||||||
|
sel := selectors.NewExchangeBackup()
|
||||||
|
sel.Include(sel.EventCalendars([]string{suite.user}, []string{exchange.DefaultCalendar}))
|
||||||
|
scopes := sel.Scopes()
|
||||||
|
suite.Equal(len(scopes), 1)
|
||||||
|
collections, err := connector.createCollections(context.Background(), scopes[0])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
for stream := range streamChannel {
|
return collections
|
||||||
testName := fmt.Sprintf("%s_Event_%d", edc.FullPath().ResourceOwner(), number)
|
},
|
||||||
suite.T().Run(testName, func(t *testing.T) {
|
},
|
||||||
|
{
|
||||||
|
name: "Birthday Calendar",
|
||||||
|
expected: "Birthdays",
|
||||||
|
getCollection: func(t *testing.T) []*exchange.Collection {
|
||||||
|
sel := selectors.NewExchangeBackup()
|
||||||
|
sel.Include(sel.EventCalendars([]string{suite.user}, []string{"Birthdays"}))
|
||||||
|
scopes := sel.Scopes()
|
||||||
|
suite.Equal(len(scopes), 1)
|
||||||
|
collections, err := connector.createCollections(context.Background(), scopes[0])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return collections
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
collections := test.getCollection(t)
|
||||||
|
require.Equal(t, len(collections), 1)
|
||||||
|
edc := collections[0]
|
||||||
|
assert.Equal(t, edc.FullPath().Folder(), test.expected)
|
||||||
|
streamChannel := edc.Items()
|
||||||
|
|
||||||
|
for stream := range streamChannel {
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
read, err := buf.ReadFrom(stream.ToReader())
|
read, err := buf.ReadFrom(stream.ToReader())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotZero(t, read)
|
assert.NotZero(t, read)
|
||||||
event, err := support.CreateEventFromBytes(buf.Bytes())
|
event, err := support.CreateEventFromBytes(buf.Bytes())
|
||||||
assert.NotNil(t, event)
|
assert.NotNil(t, event)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err, "experienced error parsing event bytes: "+string(buf.Bytes()))
|
||||||
})
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
status := connector.AwaitStatus()
|
status := connector.AwaitStatus()
|
||||||
suite.NotNil(status)
|
suite.NotNil(status)
|
||||||
suite.Equal(status.ObjectCount, status.Successful)
|
suite.Equal(status.ObjectCount, status.Successful)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAccessOfInboxAllUsers verifies that GraphConnector can
|
// TestAccessOfInboxAllUsers verifies that GraphConnector can
|
||||||
@ -249,7 +306,7 @@ func (suite *GraphConnectorIntegrationSuite) TestAccessOfInboxAllUsers() {
|
|||||||
t := suite.T()
|
t := suite.T()
|
||||||
connector := loadConnector(ctx, t)
|
connector := loadConnector(ctx, t)
|
||||||
sel := selectors.NewExchangeBackup()
|
sel := selectors.NewExchangeBackup()
|
||||||
sel.Include(sel.MailFolders(selectors.Any(), []string{"Inbox"}))
|
sel.Include(sel.MailFolders(selectors.Any(), []string{exchange.DefaultMailFolder}))
|
||||||
scopes := sel.DiscreteScopes(connector.GetUsers())
|
scopes := sel.DiscreteScopes(connector.GetUsers())
|
||||||
|
|
||||||
for _, scope := range scopes {
|
for _, scope := range scopes {
|
||||||
|
|||||||
@ -102,7 +102,6 @@ func (suite *ItemIntegrationSuite) TestItemReader() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Read data for the file
|
// Read data for the file
|
||||||
|
|
||||||
name, itemData, err := driveItemReader(ctx, suite, driveID, driveItemID)
|
name, itemData, err := driveItemReader(ctx, suite, driveID, driveItemID)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
require.NotEmpty(suite.T(), name)
|
require.NotEmpty(suite.T(), name)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user