diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index 73b13b850..fc8fcd1b2 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -8,7 +8,6 @@ import ( "github.com/alcionai/corso/cli/config" "github.com/alcionai/corso/cli/utils" - "github.com/alcionai/corso/pkg/credentials" "github.com/alcionai/corso/pkg/logger" "github.com/alcionai/corso/pkg/repository" ) @@ -47,19 +46,14 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error { return nil } - s, cfgTenantID, err := config.MakeS3Config(true, nil) + s, acct, err := config.GetStorageAndAccount(true, nil) if err != nil { return err } - m365 := credentials.GetM365() - a := repository.Account{ - TenantID: m365.TenantID, - ClientID: m365.ClientID, - ClientSecret: m365.ClientSecret, - } - if len(cfgTenantID) > 0 { - a.TenantID = cfgTenantID + m365, err := acct.M365Config() + if err != nil { + return errors.Wrap(err, "Failed to parse m365 account config") } logger.Ctx(ctx).Debugw( @@ -68,7 +62,7 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error { "clientID", m365.ClientID, "hasClientSecret", len(m365.ClientSecret) > 0) - r, err := repository.Connect(ctx, a, s) + r, err := repository.Connect(ctx, acct, s) if err != nil { return errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider) } diff --git a/src/cli/config/config.go b/src/cli/config/config.go index b457c3497..9f5f8cd5a 100644 --- a/src/cli/config/config.go +++ b/src/cli/config/config.go @@ -9,8 +9,8 @@ import ( "github.com/spf13/viper" "github.com/alcionai/corso/cli/utils" + "github.com/alcionai/corso/pkg/account" "github.com/alcionai/corso/pkg/credentials" - "github.com/alcionai/corso/pkg/repository" "github.com/alcionai/corso/pkg/storage" ) @@ -61,7 +61,7 @@ func InitConfig(configFilePath string) error { // WriteRepoConfig currently just persists corso config to the config file // It does not check for conflicts or existing data. -func WriteRepoConfig(s3Config storage.S3Config, account repository.Account) error { +func WriteRepoConfig(s3Config storage.S3Config, account account.M365Config) error { // Rudimentary support for persisting repo config // TODO: Handle conflicts, support other config types viper.Set(ProviderTypeKey, storage.ProviderS3.String()) @@ -79,44 +79,61 @@ func WriteRepoConfig(s3Config storage.S3Config, account repository.Account) erro return nil } -func ReadRepoConfig() (s3Config storage.S3Config, account repository.Account, err error) { +func ReadRepoConfig() (storage.S3Config, account.Account, error) { + var ( + s3Config storage.S3Config + acct account.Account + err error + ) + if err = viper.ReadInConfig(); err != nil { - return s3Config, account, errors.Wrap(err, "reading config file: "+viper.ConfigFileUsed()) + return s3Config, acct, errors.Wrap(err, "reading config file: "+viper.ConfigFileUsed()) } if providerType := viper.GetString(ProviderTypeKey); providerType != storage.ProviderS3.String() { - return s3Config, account, errors.New("Unsupported storage provider: " + providerType) + return s3Config, acct, errors.New("Unsupported storage provider: " + providerType) } s3Config.Bucket = viper.GetString(BucketNameKey) s3Config.Endpoint = viper.GetString(EndpointKey) s3Config.Prefix = viper.GetString(PrefixKey) - account.TenantID = viper.GetString(TenantIDKey) - return s3Config, account, nil + m365Creds := credentials.GetM365() + cfgTenantID := viper.GetString(TenantIDKey) + if len(cfgTenantID) > 0 { + m365Creds.TenantID = cfgTenantID + } + acct, err = account.NewAccount( + account.ProviderM365, + account.M365Config{ + M365: m365Creds, + }, + ) + + return s3Config, acct, err } -// MakeS3Config creates a storage instance by mediating all the possible -// data sources (config file, env vars, flag overrides) in the config. -func MakeS3Config(readFromFile bool, overrides map[string]string) (storage.Storage, string, error) { +// GetStorageAndAccount creates a storage and account instance by mediating all the possible +// data sources (config file, env vars, flag overrides) and the config file. +func GetStorageAndAccount(readFromFile bool, overrides map[string]string) (storage.Storage, account.Account, error) { var ( - s3Cfg storage.S3Config - account repository.Account - err error + s3Cfg storage.S3Config + acct account.Account + err error ) // possibly read the prior config from a .corso file if readFromFile { - s3Cfg, account, err = ReadRepoConfig() + s3Cfg, acct, err = ReadRepoConfig() if err != nil { - return storage.Storage{}, "", errors.Wrap(err, "reading corso config file") + return storage.Storage{}, acct, errors.Wrap(err, "reading corso config file") } } // compose the s3 storage config and credentials aws := credentials.GetAWS(overrides) if err := aws.Validate(); err != nil { - return storage.Storage{}, "", errors.Wrap(err, "validating aws credentials") + return storage.Storage{}, acct, errors.Wrap(err, "validating aws credentials") } s3Cfg = storage.S3Config{ AWS: aws, @@ -128,7 +145,7 @@ func MakeS3Config(readFromFile bool, overrides map[string]string) (storage.Stora // compose the common config and credentials corso := credentials.GetCorso() if err := corso.Validate(); err != nil { - return storage.Storage{}, "", errors.Wrap(err, "validating corso credentials") + return storage.Storage{}, acct, errors.Wrap(err, "validating corso credentials") } cCfg := storage.CommonConfig{ Corso: corso, @@ -142,15 +159,15 @@ func MakeS3Config(readFromFile bool, overrides map[string]string) (storage.Stora credentials.AWSSessionToken: aws.SessionToken, credentials.CorsoPassword: corso.CorsoPassword, }); err != nil { - return storage.Storage{}, "", err + return storage.Storage{}, acct, err } // return a complete storage s, err := storage.NewStorage(storage.ProviderS3, s3Cfg, cCfg) if err != nil { - return storage.Storage{}, "", errors.Wrap(err, "configuring repository storage") + return storage.Storage{}, acct, errors.Wrap(err, "configuring repository storage") } - return s, account.TenantID, nil + return s, acct, nil } // returns the first non-zero valued string diff --git a/src/cli/config/config_test.go b/src/cli/config/config_test.go index 76ec9da6e..8942407e6 100644 --- a/src/cli/config/config_test.go +++ b/src/cli/config/config_test.go @@ -8,10 +8,11 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/alcionai/corso/cli/config" - "github.com/alcionai/corso/pkg/repository" + ctesting "github.com/alcionai/corso/internal/testing" "github.com/alcionai/corso/pkg/storage" ) @@ -49,7 +50,10 @@ func (suite *ConfigSuite) TestReadRepoConfigBasic() { s3Cfg, account, err := config.ReadRepoConfig() assert.NoError(suite.T(), err) assert.Equal(suite.T(), b, s3Cfg.Bucket) - assert.Equal(suite.T(), tID, account.TenantID) + + m365, err := account.M365Config() + require.NoError(suite.T(), err) + assert.Equal(suite.T(), tID, m365.TenantID) } func (suite *ConfigSuite) TestWriteReadConfig() { @@ -60,12 +64,16 @@ func (suite *ConfigSuite) TestWriteReadConfig() { assert.NoError(suite.T(), err) s3Cfg := storage.S3Config{Bucket: "write-read-config-bucket"} - account := repository.Account{TenantID: "6f34ac30-8196-469b-bf8f-d83deadbbbbd"} - err = config.WriteRepoConfig(s3Cfg, account) + acct, err := ctesting.NewM365Account() + require.NoError(suite.T(), err) + m365, err := acct.M365Config() + require.NoError(suite.T(), err) + + err = config.WriteRepoConfig(s3Cfg, m365) assert.NoError(suite.T(), err) readS3Cfg, readAccount, err := config.ReadRepoConfig() assert.NoError(suite.T(), err) assert.Equal(suite.T(), s3Cfg, readS3Cfg) - assert.Equal(suite.T(), account, readAccount) + assert.Equal(suite.T(), acct, readAccount) } diff --git a/src/cli/repo/s3.go b/src/cli/repo/s3.go index 2fb45e9bf..ebf2c250c 100644 --- a/src/cli/repo/s3.go +++ b/src/cli/repo/s3.go @@ -65,7 +65,7 @@ func initS3Cmd(cmd *cobra.Command, args []string) error { storage.Endpoint: endpoint, storage.Prefix: prefix, } - s, cfgTenantID, err := config.MakeS3Config(false, overrides) + s, a, err := config.GetStorageAndAccount(false, overrides) if err != nil { return err } @@ -74,14 +74,9 @@ func initS3Cmd(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Retrieving s3 configuration") } - m365 := credentials.GetM365() - a := repository.Account{ - TenantID: m365.TenantID, - ClientID: m365.ClientID, - ClientSecret: m365.ClientSecret, - } - if len(cfgTenantID) > 0 { - a.TenantID = cfgTenantID + m365, err := a.M365Config() + if err != nil { + return errors.Wrap(err, "Failed to parse m365 account config") } log.Debugw( @@ -100,7 +95,7 @@ func initS3Cmd(cmd *cobra.Command, args []string) error { fmt.Printf("Initialized a S3 repository within bucket %s.\n", s3Cfg.Bucket) - if err = config.WriteRepoConfig(s3Cfg, a); err != nil { + if err = config.WriteRepoConfig(s3Cfg, m365); err != nil { return errors.Wrap(err, "Failed to write repository configuration") } return nil @@ -130,7 +125,7 @@ func connectS3Cmd(cmd *cobra.Command, args []string) error { storage.Endpoint: endpoint, storage.Prefix: prefix, } - s, cfgTenantID, err := config.MakeS3Config(true, overrides) + s, a, err := config.GetStorageAndAccount(true, overrides) if err != nil { return err } @@ -138,15 +133,9 @@ func connectS3Cmd(cmd *cobra.Command, args []string) error { if err != nil { return errors.Wrap(err, "Retrieving s3 configuration") } - - m365 := credentials.GetM365() - a := repository.Account{ - TenantID: m365.TenantID, - ClientID: m365.ClientID, - ClientSecret: m365.ClientSecret, - } - if len(cfgTenantID) > 0 { - a.TenantID = cfgTenantID + m365, err := a.M365Config() + if err != nil { + return errors.Wrap(err, "Failed to parse m365 account config") } log.Debugw( @@ -165,7 +154,7 @@ func connectS3Cmd(cmd *cobra.Command, args []string) error { fmt.Printf("Connected to S3 bucket %s.\n", s3Cfg.Bucket) - if err = config.WriteRepoConfig(s3Cfg, a); err != nil { + if err = config.WriteRepoConfig(s3Cfg, m365); err != nil { return errors.Wrap(err, "Failed to write repository configuration") } return nil diff --git a/src/cli/restore/exchange.go b/src/cli/restore/exchange.go index f96a34e23..206b8e863 100644 --- a/src/cli/restore/exchange.go +++ b/src/cli/restore/exchange.go @@ -8,7 +8,6 @@ import ( "github.com/alcionai/corso/cli/config" "github.com/alcionai/corso/cli/utils" - "github.com/alcionai/corso/pkg/credentials" "github.com/alcionai/corso/pkg/logger" "github.com/alcionai/corso/pkg/repository" ) @@ -55,19 +54,14 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Missing required flags") } - s, cfgTenantID, err := config.MakeS3Config(true, nil) + s, a, err := config.GetStorageAndAccount(true, nil) if err != nil { return err } - m365 := credentials.GetM365() - a := repository.Account{ - TenantID: m365.TenantID, - ClientID: m365.ClientID, - ClientSecret: m365.ClientSecret, - } - if len(cfgTenantID) > 0 { - a.TenantID = cfgTenantID + m365, err := a.M365Config() + if err != nil { + return errors.Wrap(err, "Failed to parse m365 account config") } logger.Ctx(ctx).Debugw( @@ -83,7 +77,7 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error { } defer utils.CloseRepo(ctx, r) - ro, err := r.NewRestore(ctx, restorePointID, []string{cfgTenantID, user, "mail", folder, mail}) + ro, err := r.NewRestore(ctx, restorePointID, []string{m365.TenantID, user, "mail", folder, mail}) if err != nil { return errors.Wrap(err, "Failed to initialize Exchange restore") } diff --git a/src/go.mod b/src/go.mod index 02d806916..d4a0d3adc 100644 --- a/src/go.mod +++ b/src/go.mod @@ -9,7 +9,7 @@ require ( github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 github.com/kopia/kopia v0.10.7 - github.com/microsoft/kiota-abstractions-go v0.8.0 + github.com/microsoft/kiota-abstractions-go v0.8.1 github.com/microsoft/kiota-authentication-azure-go v0.3.0 github.com/microsoft/kiota-serialization-json-go v0.5.1 github.com/microsoftgraph/msgraph-sdk-go v0.25.0 @@ -42,7 +42,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chmduquesne/rollinghash v4.0.0+incompatible // indirect - github.com/cjlapao/common-go v0.0.20 // indirect + github.com/cjlapao/common-go v0.0.21 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect diff --git a/src/go.sum b/src/go.sum index 8e132ab8d..4c19232cc 100644 --- a/src/go.sum +++ b/src/go.sum @@ -70,6 +70,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cjlapao/common-go v0.0.20 h1:AIZBtuxwJPXFqyxFOlvVDWZgbsuTSquZEUw4KMx8pNg= github.com/cjlapao/common-go v0.0.20/go.mod h1:EVLEXIxsBHblPMrhJYOvL4yBCcBj7IYDdW88VlfxpPM= +github.com/cjlapao/common-go v0.0.21 h1:z4PDFLQG4pJxHEmM8ecmnjDgTcR0Xr/30WZiNZF2oYM= +github.com/cjlapao/common-go v0.0.21/go.mod h1:QHUcl8KX3RgNVonFJ1WpW4mlr9NyWOHmzqxaRbwooPo= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -238,6 +240,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microsoft/kiota-abstractions-go v0.8.0 h1:mtilyDlfM9EUe4blByCmQh/iO7S0176zHd+oxr7rh20= github.com/microsoft/kiota-abstractions-go v0.8.0/go.mod h1:uWv0yQ4lYJtK9NIK5RAmw/LeIMUbIG0asu36v4yjyjY= +github.com/microsoft/kiota-abstractions-go v0.8.1 h1:ACCwRwddJYOx+SRqfgcR8Wo8PZTd4g+JMa8lY8ABy+4= +github.com/microsoft/kiota-abstractions-go v0.8.1/go.mod h1:05aCidCKhzer+yfhGeePaMUY3MH+wrAkQztBVEreTtc= github.com/microsoft/kiota-authentication-azure-go v0.3.0 h1:iLyy5qldAjBiYMGMk1r/rJkcmARA8cKboiN7/XbRxv4= github.com/microsoft/kiota-authentication-azure-go v0.3.0/go.mod h1:qyZWSCug2eG1zrRnCSacyFHGsgQa4aSCWn3EOkY9Z1M= github.com/microsoft/kiota-http-go v0.5.0 h1:p88Jz3Puk2ycwiLBhO/L7fygje+ACT2qvN2gKHfMSLE= diff --git a/src/internal/operations/backup_test.go b/src/internal/operations/backup_test.go index 4fbf757bd..3fb729240 100644 --- a/src/internal/operations/backup_test.go +++ b/src/internal/operations/backup_test.go @@ -71,12 +71,8 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run() { // not the user we want to use, but all the others are // suffering from JsonParseNode syndrome m365User := "george.martinez@8qzvrj.onmicrosoft.com" - m365 := credentials.GetM365() - acct := repository.Account{ - ClientID: m365.ClientID, - ClientSecret: m365.ClientSecret, - TenantID: m365.TenantID, - } + acct, err := ctesting.NewM365Account() + require.NoError(t, err) // need to initialize the repository before we can test connecting to it. st, err := ctesting.NewPrefixedS3Storage(t) diff --git a/src/internal/testing/account.go b/src/internal/testing/account.go new file mode 100644 index 000000000..0db0fb615 --- /dev/null +++ b/src/internal/testing/account.go @@ -0,0 +1,23 @@ +package testing + +import ( + "github.com/alcionai/corso/pkg/account" + "github.com/alcionai/corso/pkg/credentials" +) + +// NewM365Account returns an account.Account object initialized with environment +// variables used for integration tests that use Graph Connector. +func NewM365Account() (account.Account, error) { + // todo (keepers) include account details in test config + // cfg, err := readTestConfig() + // if err != nil { + // return account.Account{}, errors.Wrap(err, "configuring m365 account from test file") + // } + + return account.NewAccount( + account.ProviderM365, + account.M365Config{ + M365: credentials.GetM365(), + }, + ) +} diff --git a/src/pkg/repository/repository.go b/src/pkg/repository/repository.go index a86f803f6..7be0314a0 100644 --- a/src/pkg/repository/repository.go +++ b/src/pkg/repository/repository.go @@ -9,6 +9,7 @@ import ( "github.com/alcionai/corso/internal/kopia" "github.com/alcionai/corso/internal/operations" + "github.com/alcionai/corso/pkg/account" "github.com/alcionai/corso/pkg/credentials" "github.com/alcionai/corso/pkg/storage" ) @@ -19,18 +20,11 @@ type Repository struct { CreatedAt time.Time Version string // in case of future breaking changes - Account Account // the user's m365 account connection details + Account account.Account // the user's m365 account connection details Storage storage.Storage // the storage provider details and configuration dataLayer *kopia.KopiaWrapper } -// Account holds the user's m365 account details. -type Account struct { - TenantID string - ClientID string - ClientSecret string -} - // Initialize will: // * validate the m365 account & secrets // * connect to the m365 account to ensure communication capability @@ -41,7 +35,7 @@ type Account struct { // * return the connected repository func Initialize( ctx context.Context, - acct Account, + acct account.Account, storage storage.Storage, ) (*Repository, error) { k := kopia.New(storage) @@ -65,7 +59,7 @@ func Initialize( // * return the connected repository func Connect( ctx context.Context, - acct Account, + acct account.Account, storage storage.Storage, ) (*Repository, error) { k := kopia.New(storage) @@ -99,10 +93,14 @@ func (r *Repository) Close(ctx context.Context) error { // NewBackup generates a backupOperation runner. func (r Repository) NewBackup(ctx context.Context, targets []string) (operations.BackupOperation, error) { + m365, err := r.Account.M365Config() + if err != nil { + return operations.BackupOperation{}, errors.Wrap(err, "retrieving m365 account credentials") + } creds := credentials.M365{ - ClientID: r.Account.ClientID, - ClientSecret: r.Account.ClientSecret, - TenantID: r.Account.TenantID, + ClientID: m365.ClientID, + ClientSecret: m365.ClientSecret, + TenantID: m365.TenantID, } return operations.NewBackupOperation( ctx, @@ -114,10 +112,14 @@ func (r Repository) NewBackup(ctx context.Context, targets []string) (operations // NewRestore generates a restoreOperation runner. func (r Repository) NewRestore(ctx context.Context, restorePointID string, targets []string) (operations.RestoreOperation, error) { + m365, err := r.Account.M365Config() + if err != nil { + return operations.RestoreOperation{}, errors.Wrap(err, "retrieving m365 account credentials") + } creds := credentials.M365{ - ClientID: r.Account.ClientID, - ClientSecret: r.Account.ClientSecret, - TenantID: r.Account.TenantID, + ClientID: m365.ClientID, + ClientSecret: m365.ClientSecret, + TenantID: m365.TenantID, } return operations.NewRestoreOperation( ctx, diff --git a/src/pkg/repository/repository_test.go b/src/pkg/repository/repository_test.go index 4c2384e62..75e0ad309 100644 --- a/src/pkg/repository/repository_test.go +++ b/src/pkg/repository/repository_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/suite" ctesting "github.com/alcionai/corso/internal/testing" - "github.com/alcionai/corso/pkg/credentials" + "github.com/alcionai/corso/pkg/account" "github.com/alcionai/corso/pkg/repository" "github.com/alcionai/corso/pkg/storage" ) @@ -30,7 +30,7 @@ func (suite *RepositorySuite) TestInitialize() { table := []struct { name string storage func() (storage.Storage, error) - account repository.Account + account account.Account errCheck assert.ErrorAssertionFunc }{ { @@ -38,7 +38,7 @@ func (suite *RepositorySuite) TestInitialize() { func() (storage.Storage, error) { return storage.NewStorage(storage.ProviderUnknown) }, - repository.Account{}, + account.Account{}, assert.Error, }, } @@ -58,7 +58,7 @@ func (suite *RepositorySuite) TestConnect() { table := []struct { name string storage func() (storage.Storage, error) - account repository.Account + account account.Account errCheck assert.ErrorAssertionFunc }{ { @@ -66,7 +66,7 @@ func (suite *RepositorySuite) TestConnect() { func() (storage.Storage, error) { return storage.NewStorage(storage.ProviderUnknown) }, - repository.Account{}, + account.Account{}, assert.Error, }, } @@ -109,7 +109,7 @@ func (suite *RepositoryIntegrationSuite) TestInitialize() { table := []struct { name string - account repository.Account + account account.Account storage func(*testing.T) (storage.Storage, error) errCheck assert.ErrorAssertionFunc }{ @@ -143,11 +143,11 @@ func (suite *RepositoryIntegrationSuite) TestConnect() { st, err := ctesting.NewPrefixedS3Storage(t) require.NoError(t, err) - _, err = repository.Initialize(ctx, repository.Account{}, st) + _, err = repository.Initialize(ctx, account.Account{}, st) require.NoError(t, err) // now re-connect - _, err = repository.Connect(ctx, repository.Account{}, st) + _, err = repository.Connect(ctx, account.Account{}, st) assert.NoError(t, err) } @@ -155,12 +155,8 @@ func (suite *RepositoryIntegrationSuite) TestNewBackup() { t := suite.T() ctx := context.Background() - m365 := credentials.GetM365() - acct := repository.Account{ - ClientID: m365.ClientID, - ClientSecret: m365.ClientSecret, - TenantID: m365.TenantID, - } + acct, err := ctesting.NewM365Account() + require.NoError(t, err) // need to initialize the repository before we can test connecting to it. st, err := ctesting.NewPrefixedS3Storage(t) @@ -178,12 +174,8 @@ func (suite *RepositoryIntegrationSuite) TestNewRestore() { t := suite.T() ctx := context.Background() - m365 := credentials.GetM365() - acct := repository.Account{ - ClientID: m365.ClientID, - ClientSecret: m365.ClientSecret, - TenantID: m365.TenantID, - } + acct, err := ctesting.NewM365Account() + require.NoError(t, err) // need to initialize the repository before we can test connecting to it. st, err := ctesting.NewPrefixedS3Storage(t)