add cli integration tests for event/contact (#760)

## Description

Adds foundational cli integration tests for backup/restore
of events and calendars

## Type of change

- [x] 🤖 Test

## Issue(s)

#501 

## Test Plan

- [ ] 💪 Manual
- [ ]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2022-09-09 08:15:36 -06:00 committed by GitHub
parent 2e3ee15fd4
commit 6f7cf00188
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 265 additions and 126 deletions

View File

@ -444,19 +444,19 @@ func includeExchangeBackupDetailDataSelectors(
lc, lcf := len(contacts), len(contactFolders)
le, lef := len(emails), len(emailFolders)
lev, lec := len(events), len(eventCalendars)
lu := len(users)
if lc+lcf+le+lef+lev+lec+lu == 0 {
return
// either scope the request to a set of users
if lc+lcf+le+lef+lev+lec == 0 {
if len(users) == 0 {
users = selectors.Any()
}
// if only users are provided, we only get one selector
if lu > 0 && lc+lcf+le+lef+lev+lec == 0 {
sel.Include(sel.Users(users))
return
}
// otherwise, add selectors for each type of data
// or add selectors for each type of data
includeExchangeContacts(sel, users, contactFolders, contacts)
includeExchangeEmails(sel, users, emailFolders, email)
includeExchangeEvents(sel, users, eventCalendars, events)

View File

@ -16,6 +16,7 @@ import (
"github.com/alcionai/corso/src/cli/config"
"github.com/alcionai/corso/src/cli/print"
"github.com/alcionai/corso/src/internal/operations"
"github.com/alcionai/corso/src/internal/path"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/control"
@ -24,6 +25,14 @@ import (
"github.com/alcionai/corso/src/pkg/storage"
)
var (
email = path.EmailCategory
contacts = path.ContactsCategory
events = path.EventsCategory
)
var backupDataSets = []path.CategoryType{email, contacts, events}
// ---------------------------------------------------------------------------
// tests with no prior backup
// ---------------------------------------------------------------------------
@ -69,6 +78,7 @@ func (suite *BackupExchangeIntegrationSuite) SetupSuite() {
tester.TestCfgStorageProvider: "S3",
tester.TestCfgPrefix: cfg.Prefix,
}
suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force)
require.NoError(t, err)
@ -81,17 +91,21 @@ func (suite *BackupExchangeIntegrationSuite) SetupSuite() {
}
func (suite *BackupExchangeIntegrationSuite) TestExchangeBackupCmd() {
recorder := strings.Builder{}
for _, set := range backupDataSets {
recorder.Reset()
suite.T().Run(set.String(), func(t *testing.T) {
ctx := config.SetViper(tester.NewContext(), suite.vpr)
t := suite.T()
cmd := tester.StubRootCmd(
"backup", "create", "exchange",
"--config-file", suite.cfgFP,
"--user", suite.m365UserID,
"--data", "email")
"--data", set.String())
cli.BuildCommandTree(cmd)
recorder := strings.Builder{}
cmd.SetOut(&recorder)
ctx = print.SetRootCmd(ctx, cmd)
@ -99,11 +113,15 @@ func (suite *BackupExchangeIntegrationSuite) TestExchangeBackupCmd() {
// run the command
require.NoError(t, cmd.ExecuteContext(ctx))
// as an offhand check: the result should contain a string with the current hour
result := recorder.String()
t.Log("backup results", result)
// as an offhand check: the result should contain a string with the current hour
assert.Contains(t, result, time.Now().UTC().Format("2006-01-02T15"))
// and the m365 user id
assert.Contains(t, result, suite.m365UserID)
})
}
}
// ---------------------------------------------------------------------------
@ -118,7 +136,7 @@ type PreparedBackupExchangeIntegrationSuite struct {
cfgFP string
repo *repository.Repository
m365UserID string
backupOp operations.BackupOperation
backupOps map[path.CategoryType]operations.BackupOperation
}
func TestPreparedBackupExchangeIntegrationSuite(t *testing.T) {
@ -162,28 +180,58 @@ func (suite *PreparedBackupExchangeIntegrationSuite) SetupSuite() {
suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st)
require.NoError(t, err)
// some tests require an existing backup
sel := selectors.NewExchangeBackup()
sel.Include(sel.MailFolders([]string{suite.m365UserID}, []string{"Inbox"}))
suite.backupOps = make(map[path.CategoryType]operations.BackupOperation)
suite.backupOp, err = suite.repo.NewBackup(
for _, set := range backupDataSets {
var (
sel = selectors.NewExchangeBackup()
scopes []selectors.ExchangeScope
)
switch set {
case email:
scopes = sel.MailFolders([]string{suite.m365UserID}, []string{"Inbox"})
case contacts:
scopes = sel.ContactFolders([]string{suite.m365UserID}, selectors.Any())
case events:
scopes = sel.EventCalendars([]string{suite.m365UserID}, selectors.Any())
}
sel.Include(scopes)
bop, err := suite.repo.NewBackup(
ctx,
sel.Selector,
control.NewOptions(false))
require.NoError(t, suite.backupOp.Run(ctx))
require.NoError(t, bop.Run(ctx))
require.NoError(t, err)
suite.backupOps[set] = bop
// sanity check, ensure we can find the backup and its details immediately
_, err = suite.repo.Backup(ctx, bop.Results.BackupID)
require.NoError(t, err, "retrieving recent backup by ID")
_, _, err = suite.repo.BackupDetails(ctx, string(bop.Results.BackupID))
require.NoError(t, err, "retrieving recent backup details by ID")
}
}
func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeListCmd() {
recorder := strings.Builder{}
for _, set := range backupDataSets {
recorder.Reset()
suite.T().Run(set.String(), func(t *testing.T) {
ctx := config.SetViper(tester.NewContext(), suite.vpr)
t := suite.T()
cmd := tester.StubRootCmd(
"backup", "list", "exchange",
"--config-file", suite.cfgFP)
cli.BuildCommandTree(cmd)
recorder := strings.Builder{}
cmd.SetOut(&recorder)
ctx = print.SetRootCmd(ctx, cmd)
@ -193,24 +241,31 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeListCmd() {
// compare the output
result := recorder.String()
assert.Contains(t, result, suite.backupOp.Results.BackupID)
assert.Contains(t, result, suite.backupOps[set].Results.BackupID)
})
}
}
func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() {
recorder := strings.Builder{}
for _, set := range backupDataSets {
recorder.Reset()
suite.T().Run(set.String(), func(t *testing.T) {
ctx := config.SetViper(tester.NewContext(), suite.vpr)
t := suite.T()
bID := suite.backupOps[set].Results.BackupID
// fetch the details from the repo first
deets, _, err := suite.repo.BackupDetails(ctx, string(suite.backupOp.Results.BackupID))
deets, _, err := suite.repo.BackupDetails(ctx, string(bID))
require.NoError(t, err)
cmd := tester.StubRootCmd(
"backup", "details", "exchange",
"--config-file", suite.cfgFP,
"--backup", string(suite.backupOp.Results.BackupID))
"--backup", string(bID))
cli.BuildCommandTree(cmd)
recorder := strings.Builder{}
cmd.SetOut(&recorder)
ctx = print.SetRootCmd(ctx, cmd)
@ -226,6 +281,8 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() {
assert.Contains(t, result, ent.RepoRef)
})
}
})
}
}
// ---------------------------------------------------------------------------

View File

@ -328,7 +328,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() {
}{
{
name: "no selectors",
expectIncludeLen: 0,
expectIncludeLen: 3,
},
{
name: "any users",

View File

@ -242,19 +242,19 @@ func includeExchangeRestoreDataSelectors(
lc, lcf := len(contacts), len(contactFolders)
le, lef := len(emails), len(emailFolders)
lev, lec := len(events), len(eventCalendars)
lu := len(users)
if lc+lcf+le+lef+lev+lec+lu == 0 {
return
// either scope the request to a set of users
if lc+lcf+le+lef+lev+lec == 0 {
if len(users) == 0 {
users = selectors.Any()
}
// if only users are provided, we only get one selector
if lu > 0 && lc+lcf+le+lef+lev+lec == 0 {
sel.Include(sel.Users(users))
return
}
// otherwise, add selectors for each type of data
// or add selectors for each type of data
includeExchangeContacts(sel, users, contactFolders, contacts)
includeExchangeEmails(sel, users, emailFolders, email)
includeExchangeEvents(sel, users, eventCalendars, events)

View File

@ -10,6 +10,7 @@ import (
"github.com/alcionai/corso/src/cli"
"github.com/alcionai/corso/src/cli/config"
"github.com/alcionai/corso/src/internal/operations"
"github.com/alcionai/corso/src/internal/path"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/control"
@ -18,6 +19,14 @@ import (
"github.com/alcionai/corso/src/pkg/storage"
)
var (
email = path.EmailCategory
contacts = path.ContactsCategory
events = path.EventsCategory
)
var backupDataSets = []path.CategoryType{email, contacts, events}
type RestoreExchangeIntegrationSuite struct {
suite.Suite
acct account.Account
@ -26,7 +35,7 @@ type RestoreExchangeIntegrationSuite struct {
cfgFP string
repo *repository.Repository
m365UserID string
backupOp operations.BackupOperation
backupOps map[path.CategoryType]operations.BackupOperation
}
func TestRestoreExchangeIntegrationSuite(t *testing.T) {
@ -71,27 +80,57 @@ func (suite *RestoreExchangeIntegrationSuite) SetupSuite() {
suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st)
require.NoError(t, err)
// restoration requires an existing backup
sel := selectors.NewExchangeBackup()
sel.Include(sel.MailFolders([]string{suite.m365UserID}, []string{"Inbox"}))
suite.backupOp, err = suite.repo.NewBackup(
suite.backupOps = make(map[path.CategoryType]operations.BackupOperation)
for _, set := range backupDataSets {
var (
sel = selectors.NewExchangeBackup()
scopes []selectors.ExchangeScope
)
switch set {
case email:
scopes = sel.MailFolders([]string{suite.m365UserID}, []string{"Inbox"})
case contacts:
scopes = sel.ContactFolders([]string{suite.m365UserID}, selectors.Any())
case events:
scopes = sel.EventCalendars([]string{suite.m365UserID}, selectors.Any())
}
sel.Include(scopes)
bop, err := suite.repo.NewBackup(
ctx,
sel.Selector,
control.NewOptions(false))
require.NoError(t, suite.backupOp.Run(ctx))
require.NoError(t, bop.Run(ctx))
require.NoError(t, err)
suite.backupOps[set] = bop
// sanity check, ensure we can find the backup and its details immediately
_, err = suite.repo.Backup(ctx, bop.Results.BackupID)
require.NoError(t, err, "retrieving recent backup by ID")
_, _, err = suite.repo.BackupDetails(ctx, string(bop.Results.BackupID))
require.NoError(t, err, "retrieving recent backup details by ID")
}
}
func (suite *RestoreExchangeIntegrationSuite) TestExchangeRestoreCmd() {
for _, set := range backupDataSets {
suite.T().Run(set.String(), func(t *testing.T) {
ctx := config.SetViper(tester.NewContext(), suite.vpr)
t := suite.T()
cmd := tester.StubRootCmd(
"restore", "exchange",
"--config-file", suite.cfgFP,
"--backup", string(suite.backupOp.Results.BackupID))
"--backup", string(suite.backupOps[set].Results.BackupID))
cli.BuildCommandTree(cmd)
// run the command
require.NoError(t, cmd.ExecuteContext(ctx))
})
}
}

View File

@ -163,7 +163,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() {
}{
{
name: "no selectors",
expectIncludeLen: 0,
expectIncludeLen: 3,
},
{
name: "any users",

View File

@ -260,14 +260,29 @@ func (gc *GraphConnector) RestoreExchangeDataCollection(
for _, dc := range dcs {
var (
user string
category path.CategoryType
directory = strings.Join(dc.FullPath(), "")
user = dc.FullPath()[2]
items = dc.Items()
// TODO(ashmrtn): Update this when we have path struct support in collections.
category = path.ToCategoryType(dc.FullPath()[3])
exit bool
)
// email uses the new path format
category = path.ToCategoryType(dc.FullPath()[3])
if category == path.UnknownCategory {
// 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 {
pathCounter[directory] = true
folderID, errs = exchange.GetRestoreContainer(&gc.graphService, user, category)

View File

@ -555,22 +555,23 @@ func (ec exchangeCategory) unknownCat() categorizer {
// => {exchUser: userPN, exchMailFolder: mailFolder, exchMail: mailID}
func (ec exchangeCategory) pathValues(path []string) map[categorizer]string {
m := map[categorizer]string{}
if len(path) < 6 {
if len(path) < 5 {
return m
}
m[ExchangeUser] = path[2]
switch ec {
case ExchangeContact:
m[ExchangeContactFolder] = path[4]
m[ExchangeContact] = path[5]
m[ExchangeUser] = path[1]
m[ExchangeContactFolder] = path[3]
m[ExchangeContact] = path[4]
case ExchangeEvent:
m[ExchangeEventCalendar] = path[4]
m[ExchangeEvent] = path[5]
m[ExchangeUser] = path[1]
m[ExchangeEventCalendar] = path[3]
m[ExchangeEvent] = path[4]
case ExchangeMail:
m[ExchangeUser] = path[2]
m[ExchangeMailFolder] = path[4]
m[ExchangeMail] = path[5]
}

View File

@ -1,6 +1,7 @@
package selectors
import (
"strings"
"testing"
"time"
@ -883,8 +884,11 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
}
var (
contact = stubRepoRef(path.ExchangeService, path.ContactsCategory, "uid", "cfld", "cid")
event = stubRepoRef(path.ExchangeService, path.EventsCategory, "uid", "ecld", "eid")
// TODO: contacts and events currently do not comply with the mail path pattern
// contact = stubRepoRef(path.ExchangeService, path.ContactsCategory, "uid", "cfld", "cid")
// 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")
)
@ -1228,24 +1232,37 @@ func (suite *ExchangeSelectorSuite) TestExchangeCategory_leafCat() {
}
func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathValues() {
contactPath := stubPath(path.ExchangeService, path.ContactsCategory, "user", "cfolder", "contactitem")
contactMap := map[categorizer]string{
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],
}
// TODO: currently events and contacts are non-compliant with email path patterns
// contactPath := stubPath(path.ExchangeService, path.ContactsCategory, "user", "cfolder", "contactitem")
// contactMap := map[categorizer]string{
// 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: contactPath[2],
ExchangeUser: mailPath[2],
ExchangeMailFolder: mailPath[4],
ExchangeMail: mailPath[5],
}
contactPath := []string{"tid", "user", path.ContactsCategory.String(), "cfolder", "contactitem"}
contactMap := map[categorizer]string{
ExchangeUser: contactPath[1],
ExchangeContactFolder: contactPath[3],
ExchangeContact: contactPath[4],
}
eventPath := []string{"tid", "user", path.EventsCategory.String(), "ecalendar", "eventitem"}
eventMap := map[categorizer]string{
ExchangeUser: eventPath[1],
ExchangeEventCalendar: eventPath[3],
ExchangeEvent: eventPath[4],
}
table := []struct {
cat exchangeCategory

View File

@ -300,6 +300,16 @@ func pathTypeIn(p []string) pathType {
return exchangeEventPath
}
// fallback for unmigrated events and contacts paths
switch p[2] {
case path.EmailCategory.String():
return exchangeMailPath
case path.ContactsCategory.String():
return exchangeContactPath
case path.EventsCategory.String():
return exchangeEventPath
}
return unknownPathType
}