GC Contacts requires additional tests placed in Exchange Services (#475)

Test coverage extended to GraphQuery and GraphIterateFuncs for exchange.Mail and exchange.Contact use cases.
This commit is contained in:
Danny 2022-08-15 13:50:06 -04:00 committed by GitHub
parent 9c62134cb2
commit be7b778769
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 299 additions and 67 deletions

View File

@ -3,43 +3,102 @@ package exchange
import ( import (
"testing" "testing"
absser "github.com/microsoft/kiota-abstractions-go/serialization"
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/internal/tester" "github.com/alcionai/corso/internal/tester"
"github.com/alcionai/corso/pkg/account"
"github.com/alcionai/corso/pkg/selectors" "github.com/alcionai/corso/pkg/selectors"
) )
type ExchangeServiceSuite struct { type ExchangeServiceSuite struct {
suite.Suite suite.Suite
es *exchangeService
} }
func TestExchangeServiceSuite(t *testing.T) { func TestExchangeServiceSuite(t *testing.T) {
if err := tester.RunOnAny(
tester.CorsoCITests,
tester.CorsoGraphConnectorTests,
); err != nil {
t.Skip(err)
}
suite.Run(t, new(ExchangeServiceSuite)) suite.Run(t, new(ExchangeServiceSuite))
} }
// TestExchangeService_optionsForMessages checks to ensure approved query func (suite *ExchangeServiceSuite) SetupSuite() {
t := suite.T()
_, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...)
require.NoError(t, err)
a := tester.NewM365Account(t)
require.NoError(t, err)
m365, err := a.M365Config()
require.NoError(t, err)
service, err := createService(m365, false)
require.NoError(t, err)
suite.es = service
}
// TestCreateService verifies that services are created
// when called with the correct range of params. NOTE:
// incorrect tenant or password information will NOT generate
// an error.
func (suite *ExchangeServiceSuite) TestCreateService() {
creds := suite.es.credentials
invalidCredentials := suite.es.credentials
invalidCredentials.ClientSecret = ""
tests := []struct {
name string
credentials account.M365Config
checkErr assert.ErrorAssertionFunc
}{
{
name: "Valid Service Creation",
credentials: creds,
checkErr: assert.NoError,
},
{
name: "Invalid Service Creation",
credentials: invalidCredentials,
checkErr: assert.Error,
},
}
for _, test := range tests {
suite.T().Run(test.name, func(t *testing.T) {
t.Log(test.credentials.ClientSecret)
_, err := createService(test.credentials, false)
test.checkErr(t, err)
})
}
}
// TestOptionsForMessages checks to ensure approved query
// options are added to the type specific RequestBuildConfiguration. Expected // options are added to the type specific RequestBuildConfiguration. Expected
// will be +1 on all select parameters // will be +1 on all select parameters
func (suite *ExchangeServiceSuite) TestExchangeService_optionsForMessages() { func (suite *ExchangeServiceSuite) TestOptionsForMessages() {
tests := []struct { tests := []struct {
name string name string
params []string params []string
checkError assert.ErrorAssertionFunc checkError assert.ErrorAssertionFunc
}{ }{
{ {
name: "Accepted", name: "Valid Message Option",
params: []string{"subject"}, params: []string{"subject"},
checkError: assert.NoError, checkError: assert.NoError,
}, },
{ {
name: "Multiple Accepted", name: "Multiple Message Options: Accepted",
params: []string{"webLink", "parentFolderId"}, params: []string{"webLink", "parentFolderId"},
checkError: assert.NoError, checkError: assert.NoError,
}, },
{ {
name: "Incorrect param", name: "Invalid Message Parameter",
params: []string{"status"}, params: []string{"status"},
checkError: assert.Error, checkError: assert.Error,
}, },
@ -55,10 +114,10 @@ func (suite *ExchangeServiceSuite) TestExchangeService_optionsForMessages() {
} }
} }
// TestExchangeService_optionsForFolders ensures that approved query options // TestOptionsForFolders ensures that approved query options
// are added to the RequestBuildConfiguration. Expected will always be +1 // are added to the RequestBuildConfiguration. Expected will always be +1
// on than the input as "id" are always included within the select parameters // on than the input as "id" are always included within the select parameters
func (suite *ExchangeServiceSuite) TestExchangeService_optionsForFolders() { func (suite *ExchangeServiceSuite) TestOptionsForFolders() {
tests := []struct { tests := []struct {
name string name string
params []string params []string
@ -66,19 +125,19 @@ func (suite *ExchangeServiceSuite) TestExchangeService_optionsForFolders() {
expected int expected int
}{ }{
{ {
name: "Accepted", name: "Valid Folder Option",
params: []string{"displayName"}, params: []string{"parentFolderId"},
checkError: assert.NoError, checkError: assert.NoError,
expected: 2, expected: 2,
}, },
{ {
name: "Multiple Accepted", name: "Multiple Folder Options: Valid",
params: []string{"displayName", "parentFolderId"}, params: []string{"displayName", "isHidden"},
checkError: assert.NoError, checkError: assert.NoError,
expected: 3, expected: 3,
}, },
{ {
name: "Incorrect param", name: "Invalid Folder option param",
params: []string{"status"}, params: []string{"status"},
checkError: assert.Error, checkError: assert.Error,
}, },
@ -94,8 +153,47 @@ func (suite *ExchangeServiceSuite) TestExchangeService_optionsForFolders() {
} }
} }
// NOTE the requirements are in PR 475 // TestOptionsForContacts similar to TestExchangeService_optionsForFolders
func (suite *ExchangeServiceSuite) TestExchangeService_SetupExchangeCollection() { func (suite *ExchangeServiceSuite) TestOptionsForContacts() {
tests := []struct {
name string
params []string
checkError assert.ErrorAssertionFunc
expected int
}{
{
name: "Valid Contact Option",
params: []string{"displayName"},
checkError: assert.NoError,
expected: 2,
},
{
name: "Multiple Contact Options: Valid",
params: []string{"displayName", "parentFolderId"},
checkError: assert.NoError,
expected: 3,
},
{
name: "Invalid Contact Option param",
params: []string{"status"},
checkError: assert.Error,
},
}
for _, test := range tests {
suite.T().Run(test.name, func(t *testing.T) {
options, err := optionsForContacts(test.params)
test.checkError(t, err)
if err == nil {
suite.Equal(test.expected, len(options.QueryParameters.Select))
}
})
}
}
// TestSetupExchangeCollection ensures that the helper
// function SetupExchangeCollectionVars returns a non-nil variable for returns
// in regards to the selector.ExchangeScope.
func (suite *ExchangeServiceSuite) TestSetupExchangeCollection() {
userID := tester.M365UserID(suite.T()) userID := tester.M365UserID(suite.T())
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
sel.Include(sel.Users([]string{userID})) sel.Include(sel.Users([]string{userID}))
@ -116,3 +214,107 @@ func (suite *ExchangeServiceSuite) TestExchangeService_SetupExchangeCollection()
}) })
} }
} }
// TestGraphQueryFunctions verifies if Query functions APIs
// through Microsoft Graph are functional
func (suite *ExchangeServiceSuite) TestGraphQueryFunctions() {
userID := tester.M365UserID(suite.T())
tests := []struct {
name string
function GraphQuery
}{
{
name: "GraphQuery: Get All Messages For User",
function: GetAllMessagesForUser,
},
{
name: "GraphQuery: Get All Contacts For User",
function: GetAllContactsForUser,
},
{
name: "GraphQuery: Get All Folders",
function: GetAllFolderNamesForUser,
},
}
for _, test := range tests {
suite.T().Run(test.name, func(t *testing.T) {
response, err := test.function(suite.es, userID)
assert.NoError(t, err)
assert.NotNil(t, response)
})
}
}
// TestIterativeFunctions verifies that GraphQuery to Iterate
// functions are valid for current versioning of msgraph-go-sdk
func (suite *ExchangeServiceSuite) TestIterativeFunctions() {
userID := tester.M365UserID(suite.T())
sel := selectors.NewExchangeBackup()
sel.Include(sel.Users([]string{userID}))
eb, err := sel.ToExchangeBackup()
require.NoError(suite.T(), err)
scopes := eb.Scopes()
var mailScope, contactScope selectors.ExchangeScope
for _, scope := range scopes {
if scope.IncludesCategory(selectors.ExchangeContactFolder) {
contactScope = scope
}
if scope.IncludesCategory(selectors.ExchangeMail) {
mailScope = scope
}
}
tests := []struct {
name string
queryFunction GraphQuery
iterativeFunction GraphIterateFunc
scope selectors.ExchangeScope
transformer absser.ParsableFactory
}{
{
name: "Mail Iterative Check",
queryFunction: GetAllMessagesForUser,
iterativeFunction: IterateSelectAllMessagesForCollections,
scope: mailScope,
transformer: models.CreateMessageCollectionResponseFromDiscriminatorValue,
}, {
name: "Contacts Iterative Check",
queryFunction: GetAllContactsForUser,
iterativeFunction: IterateAllContactsForCollection,
scope: contactScope,
transformer: models.CreateContactFromDiscriminatorValue,
}, {
name: "Folder Iterative Check",
queryFunction: GetAllFolderNamesForUser,
iterativeFunction: IterateFilterFolderDirectoriesForCollections,
scope: mailScope,
transformer: models.CreateMailFolderCollectionResponseFromDiscriminatorValue,
},
}
for _, test := range tests {
suite.T().Run(test.name, func(t *testing.T) {
response, err := test.queryFunction(suite.es, userID)
require.NoError(t, err)
// Create Iterator
pageIterator, err := msgraphgocore.NewPageIterator(response,
&suite.es.adapter,
test.transformer)
require.NoError(t, err)
// Create collection for iterate test
collections := make(map[string]*Collection)
var errs error
// callbackFunc iterates through all models.Messageable and fills exchange.Collection.jobs[]
// with corresponding item IDs. New collections are created for each directory
callbackFunc := test.iterativeFunction(
"testingTenant",
test.scope,
errs, false,
suite.es.credentials,
collections,
nil)
iterateError := pageIterator.Iterate(callbackFunc)
require.NoError(t, iterateError)
})
}
}

View File

@ -29,6 +29,9 @@ type exchangeService struct {
credentials account.M365Config credentials account.M365Config
} }
///------------------------------------------------------------
// Functions to comply with graph.Service Interface
//-------------------------------------------------------
func (es *exchangeService) Client() *msgraphsdk.GraphServiceClient { func (es *exchangeService) Client() *msgraphsdk.GraphServiceClient {
return &es.client return &es.client
} }
@ -41,6 +44,9 @@ func (es *exchangeService) ErrPolicy() bool {
return es.failFast return es.failFast
} }
// createService internal constructor for exchangeService struct returns an error
// iff the params for the entry are incorrect (e.g. len(TenantID) == 0, etc.)
// NOTE: Incorrect account information will result in errors on subsequent queries.
func createService(credentials account.M365Config, shouldFailFast bool) (*exchangeService, error) { func createService(credentials account.M365Config, shouldFailFast bool) (*exchangeService, error) {
adapter, err := graph.CreateAdapter( adapter, err := graph.CreateAdapter(
credentials.TenantID, credentials.TenantID,
@ -89,13 +95,7 @@ func GetAllMailFolders(gs graph.Service, user, nameContains string) ([]MailFolde
mfs = []MailFolder{} mfs = []MailFolder{}
err error err error
) )
resp, err := GetAllFolderNamesForUser(gs, user)
opts, err := optionsForMailFolders([]string{"id", "displayName"})
if err != nil {
return nil, err
}
resp, err := gs.Client().UsersById(user).MailFolders().GetWithRequestConfigurationAndResponseHandler(opts, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -137,21 +137,12 @@ func GetAllMailFolders(gs graph.Service, user, nameContains string) ([]MailFolde
func GetMailFolderID(service graph.Service, folderName, user string) (*string, error) { func GetMailFolderID(service graph.Service, folderName, user string) (*string, error) {
var errs error var errs error
var folderID *string var folderID *string
options, err := optionsForMailFolders([]string{"displayName"})
response, err := GetAllFolderNamesForUser(service, user)
if err != nil { if err != nil {
return nil, err return nil, err
} }
response, err := service.
Client().
UsersById(user).
MailFolders().
GetWithRequestConfigurationAndResponseHandler(options, nil)
if err != nil {
return nil, err
}
if response == nil {
return nil, errors.New("mail folder query to m365 back store returned nil")
}
pageIterator, err := msgraphgocore.NewPageIterator( pageIterator, err := msgraphgocore.NewPageIterator(
response, response,
service.Adapter(), service.Adapter(),

View File

@ -64,13 +64,13 @@ func GetAllContactsForUser(gs graph.Service, user string) (absser.Parsable, erro
// GetAllFolderDisplayNamesForUser is a GraphQuery function for getting FolderId and display // GetAllFolderDisplayNamesForUser is a GraphQuery function for getting FolderId and display
// names for Mail Folder. All other information for the MailFolder object is omitted. // names for Mail Folder. All other information for the MailFolder object is omitted.
func GetAllFolderNamesForUser(gs graph.Service, identities []string) (absser.Parsable, error) { func GetAllFolderNamesForUser(gs graph.Service, user string) (absser.Parsable, error) {
options, err := optionsForMailFolders([]string{"id", "displayName"}) options, err := optionsForMailFolders([]string{"id", "displayName"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
return gs.Client().UsersById(identities[0]).MailFolders().GetWithRequestConfigurationAndResponseHandler(options, nil) return gs.Client().UsersById(user).MailFolders().GetWithRequestConfigurationAndResponseHandler(options, nil)
} }
// GraphIterateFuncs are iterate functions to be used with the M365 iterators (e.g. msgraphgocore.NewPageIterator) // GraphIterateFuncs are iterate functions to be used with the M365 iterators (e.g. msgraphgocore.NewPageIterator)
@ -216,6 +216,60 @@ func IterateAndFilterMessagesForCollections(
} }
} }
func IterateFilterFolderDirectoriesForCollections(
tenant string,
scope selectors.ExchangeScope,
errs error,
failFast bool,
credentials account.M365Config,
collections map[string]*Collection,
statusCh chan<- *support.ConnectorOperationStatus,
) func(any) bool {
var (
service graph.Service
err error
)
return func(folderItem any) bool {
user := scope.Get(selectors.ExchangeUser)[0]
folder, ok := folderItem.(models.MailFolderable)
if !ok {
errs = support.WrapAndAppend(
user,
errors.New("unable to transform folderable item"),
errs,
)
return true
}
if !scope.Contains(selectors.ExchangeMailFolder, *folder.GetDisplayName()) {
return true
}
directory := *folder.GetId()
service, err = createService(credentials, failFast)
if err != nil {
errs = support.WrapAndAppend(
*folder.GetDisplayName(),
errors.Wrap(
err,
"unable to create service a folder query service for "+user,
),
errs,
)
return true
}
temp := NewCollection(
user,
[]string{tenant, user, mailCategory, directory},
messages,
service,
statusCh,
)
collections[directory] = &temp
return true
}
}
func CollectMailFolders( func CollectMailFolders(
scope selectors.ExchangeScope, scope selectors.ExchangeScope,
tenant string, tenant string,
@ -230,7 +284,7 @@ func CollectMailFolders(
return errors.New("unable to create a mail folder query service for " + user) return errors.New("unable to create a mail folder query service for " + user)
} }
query, err := GetAllFolderNamesForUser(queryService, []string{user}) query, err := GetAllFolderNamesForUser(queryService, user)
if err != nil { if err != nil {
return fmt.Errorf( return fmt.Errorf(
"unable to query mail folder for %s: details: %s", "unable to query mail folder for %s: details: %s",
@ -247,37 +301,16 @@ func CollectMailFolders(
if err != nil { if err != nil {
return errors.Wrap(err, "unable to create iterator during mail folder query service") return errors.Wrap(err, "unable to create iterator during mail folder query service")
} }
var service graph.Service
callbackFunc := func(pageItem any) bool {
folder, ok := pageItem.(models.MailFolderable)
if !ok {
err = support.WrapAndAppend(user, errors.New("unable to transform folderable item"), err)
return true
}
if !scope.Contains(selectors.ExchangeMailFolder, *folder.GetDisplayName()) {
return true
}
directory := *folder.GetId()
service, err = createService(credentials, failFast)
if err != nil {
err = support.WrapAndAppend(
*folder.GetDisplayName(),
errors.New("unable to create service a folder query service for "+user),
err,
)
return true
}
temp := NewCollection(
user,
[]string{tenant, user, mailCategory, directory},
messages,
service,
statusCh,
)
collections[directory] = &temp
return true callbackFunc := IterateFilterFolderDirectoriesForCollections(
} tenant,
scope,
err,
failFast,
credentials,
collections,
statusCh,
)
iterateFailure := pageIterator.Iterate(callbackFunc) iterateFailure := pageIterator.Iterate(callbackFunc)
if iterateFailure != nil { if iterateFailure != nil {
@ -393,7 +426,13 @@ func buildOptions(options []string, optID optionIdentifier) ([]string, error) {
fieldsForContacts := map[string]int{ fieldsForContacts := map[string]int{
"id": 1, "id": 1,
"parentFolderId": 2, "companyName": 2,
"department": 3,
"displayName": 4,
"fileAs": 5,
"givenName": 6,
"manager": 7,
"parentFolderId": 8,
} }
returnedOptions := []string{"id"} returnedOptions := []string{"id"}