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 (
"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/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/internal/tester"
"github.com/alcionai/corso/pkg/account"
"github.com/alcionai/corso/pkg/selectors"
)
type ExchangeServiceSuite struct {
suite.Suite
es *exchangeService
}
func TestExchangeServiceSuite(t *testing.T) {
if err := tester.RunOnAny(
tester.CorsoCITests,
tester.CorsoGraphConnectorTests,
); err != nil {
t.Skip(err)
}
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
// will be +1 on all select parameters
func (suite *ExchangeServiceSuite) TestExchangeService_optionsForMessages() {
func (suite *ExchangeServiceSuite) TestOptionsForMessages() {
tests := []struct {
name string
params []string
checkError assert.ErrorAssertionFunc
}{
{
name: "Accepted",
name: "Valid Message Option",
params: []string{"subject"},
checkError: assert.NoError,
},
{
name: "Multiple Accepted",
name: "Multiple Message Options: Accepted",
params: []string{"webLink", "parentFolderId"},
checkError: assert.NoError,
},
{
name: "Incorrect param",
name: "Invalid Message Parameter",
params: []string{"status"},
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
// on than the input as "id" are always included within the select parameters
func (suite *ExchangeServiceSuite) TestExchangeService_optionsForFolders() {
func (suite *ExchangeServiceSuite) TestOptionsForFolders() {
tests := []struct {
name string
params []string
@ -66,19 +125,19 @@ func (suite *ExchangeServiceSuite) TestExchangeService_optionsForFolders() {
expected int
}{
{
name: "Accepted",
params: []string{"displayName"},
name: "Valid Folder Option",
params: []string{"parentFolderId"},
checkError: assert.NoError,
expected: 2,
},
{
name: "Multiple Accepted",
params: []string{"displayName", "parentFolderId"},
name: "Multiple Folder Options: Valid",
params: []string{"displayName", "isHidden"},
checkError: assert.NoError,
expected: 3,
},
{
name: "Incorrect param",
name: "Invalid Folder option param",
params: []string{"status"},
checkError: assert.Error,
},
@ -94,8 +153,47 @@ func (suite *ExchangeServiceSuite) TestExchangeService_optionsForFolders() {
}
}
// NOTE the requirements are in PR 475
func (suite *ExchangeServiceSuite) TestExchangeService_SetupExchangeCollection() {
// TestOptionsForContacts similar to TestExchangeService_optionsForFolders
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())
sel := selectors.NewExchangeBackup()
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
}
///------------------------------------------------------------
// Functions to comply with graph.Service Interface
//-------------------------------------------------------
func (es *exchangeService) Client() *msgraphsdk.GraphServiceClient {
return &es.client
}
@ -41,6 +44,9 @@ func (es *exchangeService) ErrPolicy() bool {
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) {
adapter, err := graph.CreateAdapter(
credentials.TenantID,
@ -89,13 +95,7 @@ func GetAllMailFolders(gs graph.Service, user, nameContains string) ([]MailFolde
mfs = []MailFolder{}
err error
)
opts, err := optionsForMailFolders([]string{"id", "displayName"})
if err != nil {
return nil, err
}
resp, err := gs.Client().UsersById(user).MailFolders().GetWithRequestConfigurationAndResponseHandler(opts, nil)
resp, err := GetAllFolderNamesForUser(gs, user)
if err != nil {
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) {
var errs error
var folderID *string
options, err := optionsForMailFolders([]string{"displayName"})
response, err := GetAllFolderNamesForUser(service, user)
if err != nil {
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(
response,
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
// 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"})
if err != nil {
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)
@ -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(
scope selectors.ExchangeScope,
tenant string,
@ -230,7 +284,7 @@ func CollectMailFolders(
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 {
return fmt.Errorf(
"unable to query mail folder for %s: details: %s",
@ -247,37 +301,16 @@ func CollectMailFolders(
if err != nil {
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),
callbackFunc := IterateFilterFolderDirectoriesForCollections(
tenant,
scope,
err,
)
return true
}
temp := NewCollection(
user,
[]string{tenant, user, mailCategory, directory},
messages,
service,
failFast,
credentials,
collections,
statusCh,
)
collections[directory] = &temp
return true
}
iterateFailure := pageIterator.Iterate(callbackFunc)
if iterateFailure != nil {
@ -393,7 +426,13 @@ func buildOptions(options []string, optID optionIdentifier) ([]string, error) {
fieldsForContacts := map[string]int{
"id": 1,
"parentFolderId": 2,
"companyName": 2,
"department": 3,
"displayName": 4,
"fileAs": 5,
"givenName": 6,
"manager": 7,
"parentFolderId": 8,
}
returnedOptions := []string{"id"}