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:
parent
9c62134cb2
commit
be7b778769
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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"}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user