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) lc, lcf := len(contacts), len(contactFolders)
le, lef := len(emails), len(emailFolders) le, lef := len(emails), len(emailFolders)
lev, lec := len(events), len(eventCalendars) lev, lec := len(events), len(eventCalendars)
lu := len(users)
if lc+lcf+le+lef+lev+lec+lu == 0 { // either scope the request to a set of users
return 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)) sel.Include(sel.Users(users))
return return
} }
// otherwise, add selectors for each type of data // or add selectors for each type of data
includeExchangeContacts(sel, users, contactFolders, contacts) includeExchangeContacts(sel, users, contactFolders, contacts)
includeExchangeEmails(sel, users, emailFolders, email) includeExchangeEmails(sel, users, emailFolders, email)
includeExchangeEvents(sel, users, eventCalendars, events) 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/config"
"github.com/alcionai/corso/src/cli/print" "github.com/alcionai/corso/src/cli/print"
"github.com/alcionai/corso/src/internal/operations" "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/internal/tester"
"github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/control"
@ -24,6 +25,14 @@ import (
"github.com/alcionai/corso/src/pkg/storage" "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 // tests with no prior backup
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -69,6 +78,7 @@ func (suite *BackupExchangeIntegrationSuite) SetupSuite() {
tester.TestCfgStorageProvider: "S3", tester.TestCfgStorageProvider: "S3",
tester.TestCfgPrefix: cfg.Prefix, tester.TestCfgPrefix: cfg.Prefix,
} }
suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force) suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force)
require.NoError(t, err) require.NoError(t, err)
@ -81,17 +91,21 @@ func (suite *BackupExchangeIntegrationSuite) SetupSuite() {
} }
func (suite *BackupExchangeIntegrationSuite) TestExchangeBackupCmd() { 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) ctx := config.SetViper(tester.NewContext(), suite.vpr)
t := suite.T()
cmd := tester.StubRootCmd( cmd := tester.StubRootCmd(
"backup", "create", "exchange", "backup", "create", "exchange",
"--config-file", suite.cfgFP, "--config-file", suite.cfgFP,
"--user", suite.m365UserID, "--user", suite.m365UserID,
"--data", "email") "--data", set.String())
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
recorder := strings.Builder{}
cmd.SetOut(&recorder) cmd.SetOut(&recorder)
ctx = print.SetRootCmd(ctx, cmd) ctx = print.SetRootCmd(ctx, cmd)
@ -99,11 +113,15 @@ func (suite *BackupExchangeIntegrationSuite) TestExchangeBackupCmd() {
// run the command // run the command
require.NoError(t, cmd.ExecuteContext(ctx)) require.NoError(t, cmd.ExecuteContext(ctx))
// as an offhand check: the result should contain a string with the current hour
result := recorder.String() 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")) assert.Contains(t, result, time.Now().UTC().Format("2006-01-02T15"))
// and the m365 user id // and the m365 user id
assert.Contains(t, result, suite.m365UserID) assert.Contains(t, result, suite.m365UserID)
})
}
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -118,7 +136,7 @@ type PreparedBackupExchangeIntegrationSuite struct {
cfgFP string cfgFP string
repo *repository.Repository repo *repository.Repository
m365UserID string m365UserID string
backupOp operations.BackupOperation backupOps map[path.CategoryType]operations.BackupOperation
} }
func TestPreparedBackupExchangeIntegrationSuite(t *testing.T) { func TestPreparedBackupExchangeIntegrationSuite(t *testing.T) {
@ -162,28 +180,58 @@ func (suite *PreparedBackupExchangeIntegrationSuite) SetupSuite() {
suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st) suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st)
require.NoError(t, err) require.NoError(t, err)
// some tests require an existing backup suite.backupOps = make(map[path.CategoryType]operations.BackupOperation)
sel := selectors.NewExchangeBackup()
sel.Include(sel.MailFolders([]string{suite.m365UserID}, []string{"Inbox"}))
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, ctx,
sel.Selector, sel.Selector,
control.NewOptions(false)) control.NewOptions(false))
require.NoError(t, suite.backupOp.Run(ctx)) require.NoError(t, bop.Run(ctx))
require.NoError(t, err) 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() { 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) ctx := config.SetViper(tester.NewContext(), suite.vpr)
t := suite.T()
cmd := tester.StubRootCmd( cmd := tester.StubRootCmd(
"backup", "list", "exchange", "backup", "list", "exchange",
"--config-file", suite.cfgFP) "--config-file", suite.cfgFP)
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
recorder := strings.Builder{}
cmd.SetOut(&recorder) cmd.SetOut(&recorder)
ctx = print.SetRootCmd(ctx, cmd) ctx = print.SetRootCmd(ctx, cmd)
@ -193,24 +241,31 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeListCmd() {
// compare the output // compare the output
result := recorder.String() result := recorder.String()
assert.Contains(t, result, suite.backupOp.Results.BackupID) assert.Contains(t, result, suite.backupOps[set].Results.BackupID)
})
}
} }
func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() { 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) ctx := config.SetViper(tester.NewContext(), suite.vpr)
t := suite.T() bID := suite.backupOps[set].Results.BackupID
// fetch the details from the repo first // 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) require.NoError(t, err)
cmd := tester.StubRootCmd( cmd := tester.StubRootCmd(
"backup", "details", "exchange", "backup", "details", "exchange",
"--config-file", suite.cfgFP, "--config-file", suite.cfgFP,
"--backup", string(suite.backupOp.Results.BackupID)) "--backup", string(bID))
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
recorder := strings.Builder{}
cmd.SetOut(&recorder) cmd.SetOut(&recorder)
ctx = print.SetRootCmd(ctx, cmd) ctx = print.SetRootCmd(ctx, cmd)
@ -226,6 +281,8 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() {
assert.Contains(t, result, ent.RepoRef) assert.Contains(t, result, ent.RepoRef)
}) })
} }
})
}
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

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

View File

@ -242,19 +242,19 @@ func includeExchangeRestoreDataSelectors(
lc, lcf := len(contacts), len(contactFolders) lc, lcf := len(contacts), len(contactFolders)
le, lef := len(emails), len(emailFolders) le, lef := len(emails), len(emailFolders)
lev, lec := len(events), len(eventCalendars) lev, lec := len(events), len(eventCalendars)
lu := len(users)
if lc+lcf+le+lef+lev+lec+lu == 0 { // either scope the request to a set of users
return 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)) sel.Include(sel.Users(users))
return return
} }
// otherwise, add selectors for each type of data // or add selectors for each type of data
includeExchangeContacts(sel, users, contactFolders, contacts) includeExchangeContacts(sel, users, contactFolders, contacts)
includeExchangeEmails(sel, users, emailFolders, email) includeExchangeEmails(sel, users, emailFolders, email)
includeExchangeEvents(sel, users, eventCalendars, events) includeExchangeEvents(sel, users, eventCalendars, events)

View File

@ -10,6 +10,7 @@ import (
"github.com/alcionai/corso/src/cli" "github.com/alcionai/corso/src/cli"
"github.com/alcionai/corso/src/cli/config" "github.com/alcionai/corso/src/cli/config"
"github.com/alcionai/corso/src/internal/operations" "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/internal/tester"
"github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/control"
@ -18,6 +19,14 @@ import (
"github.com/alcionai/corso/src/pkg/storage" "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 { type RestoreExchangeIntegrationSuite struct {
suite.Suite suite.Suite
acct account.Account acct account.Account
@ -26,7 +35,7 @@ type RestoreExchangeIntegrationSuite struct {
cfgFP string cfgFP string
repo *repository.Repository repo *repository.Repository
m365UserID string m365UserID string
backupOp operations.BackupOperation backupOps map[path.CategoryType]operations.BackupOperation
} }
func TestRestoreExchangeIntegrationSuite(t *testing.T) { func TestRestoreExchangeIntegrationSuite(t *testing.T) {
@ -71,27 +80,57 @@ func (suite *RestoreExchangeIntegrationSuite) SetupSuite() {
suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st) suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st)
require.NoError(t, err) require.NoError(t, err)
// restoration requires an existing backup suite.backupOps = make(map[path.CategoryType]operations.BackupOperation)
sel := selectors.NewExchangeBackup()
sel.Include(sel.MailFolders([]string{suite.m365UserID}, []string{"Inbox"})) for _, set := range backupDataSets {
suite.backupOp, err = suite.repo.NewBackup( 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, ctx,
sel.Selector, sel.Selector,
control.NewOptions(false)) control.NewOptions(false))
require.NoError(t, suite.backupOp.Run(ctx)) require.NoError(t, bop.Run(ctx))
require.NoError(t, err) 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() { func (suite *RestoreExchangeIntegrationSuite) TestExchangeRestoreCmd() {
for _, set := range backupDataSets {
suite.T().Run(set.String(), func(t *testing.T) {
ctx := config.SetViper(tester.NewContext(), suite.vpr) ctx := config.SetViper(tester.NewContext(), suite.vpr)
t := suite.T()
cmd := tester.StubRootCmd( cmd := tester.StubRootCmd(
"restore", "exchange", "restore", "exchange",
"--config-file", suite.cfgFP, "--config-file", suite.cfgFP,
"--backup", string(suite.backupOp.Results.BackupID)) "--backup", string(suite.backupOps[set].Results.BackupID))
cli.BuildCommandTree(cmd) cli.BuildCommandTree(cmd)
// run the command // run the command
require.NoError(t, cmd.ExecuteContext(ctx)) require.NoError(t, cmd.ExecuteContext(ctx))
})
}
} }

View File

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

View File

@ -260,14 +260,29 @@ func (gc *GraphConnector) RestoreExchangeDataCollection(
for _, dc := range dcs { for _, dc := range dcs {
var ( var (
user string
category path.CategoryType
directory = strings.Join(dc.FullPath(), "") directory = strings.Join(dc.FullPath(), "")
user = dc.FullPath()[2]
items = dc.Items() items = dc.Items()
// TODO(ashmrtn): Update this when we have path struct support in collections. // TODO(ashmrtn): Update this when we have path struct support in collections.
category = path.ToCategoryType(dc.FullPath()[3])
exit bool 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 { if _, ok := pathCounter[directory]; !ok {
pathCounter[directory] = true pathCounter[directory] = true
folderID, errs = exchange.GetRestoreContainer(&gc.graphService, user, category) 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} // => {exchUser: userPN, exchMailFolder: mailFolder, exchMail: mailID}
func (ec exchangeCategory) pathValues(path []string) map[categorizer]string { func (ec exchangeCategory) pathValues(path []string) map[categorizer]string {
m := map[categorizer]string{} m := map[categorizer]string{}
if len(path) < 6 { if len(path) < 5 {
return m return m
} }
m[ExchangeUser] = path[2]
switch ec { switch ec {
case ExchangeContact: case ExchangeContact:
m[ExchangeContactFolder] = path[4] m[ExchangeUser] = path[1]
m[ExchangeContact] = path[5] m[ExchangeContactFolder] = path[3]
m[ExchangeContact] = path[4]
case ExchangeEvent: case ExchangeEvent:
m[ExchangeEventCalendar] = path[4] m[ExchangeUser] = path[1]
m[ExchangeEvent] = path[5] m[ExchangeEventCalendar] = path[3]
m[ExchangeEvent] = path[4]
case ExchangeMail: case ExchangeMail:
m[ExchangeUser] = path[2]
m[ExchangeMailFolder] = path[4] m[ExchangeMailFolder] = path[4]
m[ExchangeMail] = path[5] m[ExchangeMail] = path[5]
} }

View File

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

View File

@ -300,6 +300,16 @@ func pathTypeIn(p []string) pathType {
return exchangeEventPath 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 return unknownPathType
} }