diff --git a/src/pkg/account/account.go b/src/pkg/account/account.go new file mode 100644 index 000000000..faa00a240 --- /dev/null +++ b/src/pkg/account/account.go @@ -0,0 +1,53 @@ +package account + +import "errors" + +type accountProvider int + +//go:generate stringer -type=accountProvider -linecomment +const ( + ProviderUnknown accountProvider = iota // Unknown Provider + ProviderM365 // M365 +) + +// storage parsing errors +var ( + errMissingRequired = errors.New("missing required storage configuration") +) + +type ( + config map[string]string + configurer interface { + Config() (config, error) + } +) + +// Account defines an account provider, along with any credentials +// and identifiers requried to set up or communicate with that provider. +type Account struct { + Provider accountProvider + Config config +} + +// NewAccount aggregates all the supplied configurations into a single configuration +func NewAccount(p accountProvider, cfgs ...configurer) (Account, error) { + cs, err := unionConfigs(cfgs...) + return Account{ + Provider: p, + Config: cs, + }, err +} + +func unionConfigs(cfgs ...configurer) (config, error) { + union := config{} + for _, cfg := range cfgs { + c, err := cfg.Config() + if err != nil { + return nil, err + } + for k, v := range c { + union[k] = v + } + } + return union, nil +} diff --git a/src/pkg/account/account_test.go b/src/pkg/account/account_test.go new file mode 100644 index 000000000..ec1fcdeb4 --- /dev/null +++ b/src/pkg/account/account_test.go @@ -0,0 +1,97 @@ +package account + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type testConfig struct { + expect string + err error +} + +func (c testConfig) Config() (config, error) { + return config{"expect": c.expect}, c.err +} + +type AccountSuite struct { + suite.Suite +} + +func TestAccountSuite(t *testing.T) { + suite.Run(t, new(AccountSuite)) +} + +func (suite *AccountSuite) TestNewAccount() { + table := []struct { + name string + p accountProvider + c testConfig + errCheck assert.ErrorAssertionFunc + }{ + {"unknown no error", ProviderUnknown, testConfig{"configVal", nil}, assert.NoError}, + {"m365 no error", ProviderM365, testConfig{"configVal", nil}, assert.NoError}, + {"unknown w/ error", ProviderUnknown, testConfig{"configVal", assert.AnError}, assert.Error}, + {"m365 w/ error", ProviderM365, testConfig{"configVal", assert.AnError}, assert.Error}, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + s, err := NewAccount(test.p, test.c) + test.errCheck(t, err) + // remaining tests are dependent upon error-free state + if test.c.err != nil { + return + } + assert.Equalf(t, + test.p, + s.Provider, + "expected account provider [%s], got [%s]", test.p, s.Provider) + assert.Equalf(t, + test.c.expect, + s.Config["expect"], + "expected account config [%s], got [%s]", test.c.expect, s.Config["expect"]) + }) + } +} + +type fooConfig struct { + foo string + err error +} + +func (c fooConfig) Config() (config, error) { + return config{"foo": c.foo}, c.err +} + +func (suite *AccountSuite) TestUnionConfigs() { + table := []struct { + name string + tc testConfig + fc fooConfig + errCheck assert.ErrorAssertionFunc + }{ + {"no error", testConfig{"test", nil}, fooConfig{"foo", nil}, assert.NoError}, + {"tc error", testConfig{"test", assert.AnError}, fooConfig{"foo", nil}, assert.Error}, + {"fc error", testConfig{"test", nil}, fooConfig{"foo", assert.AnError}, assert.Error}, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + cs, err := unionConfigs(test.tc, test.fc) + test.errCheck(t, err) + // remaining tests depend on error-free state + if test.tc.err != nil || test.fc.err != nil { + return + } + assert.Equalf(t, + test.tc.expect, + cs["expect"], + "expected unioned config to have value [%s] at key [expect], got [%s]", test.tc.expect, cs["expect"]) + assert.Equalf(t, + test.fc.foo, + cs["foo"], + "expected unioned config to have value [%s] at key [foo], got [%s]", test.fc.foo, cs["foo"]) + }) + } +} diff --git a/src/pkg/account/m365.go b/src/pkg/account/m365.go new file mode 100644 index 000000000..bbcd2ff13 --- /dev/null +++ b/src/pkg/account/m365.go @@ -0,0 +1,59 @@ +package account + +import ( + "github.com/pkg/errors" + + "github.com/alcionai/corso/pkg/credentials" +) + +type M365Config struct { + credentials.M365 // requires: ClientID, ClientSecret, TenantID + + // (todo) TenantID string +} + +// config key consts +const ( + keyM365ClientID = "m365_clientID" + keyM365ClientSecret = "m365_clientSecret" + keyM365TenantID = "m365_tenantID" +) + +// config exported name consts +const ( +// (todo) TenantID = "TENANT_ID" +) + +func (c M365Config) Config() (config, error) { + cfg := config{ + keyM365ClientID: c.ClientID, + keyM365ClientSecret: c.ClientSecret, + keyM365TenantID: c.TenantID, + } + return cfg, c.validate() +} + +// M365Config retrieves the M365Config details from the Account config. +func (a Account) M365Config() (M365Config, error) { + c := M365Config{} + if len(a.Config) > 0 { + c.ClientID = a.Config[keyM365ClientID] + c.ClientSecret = a.Config[keyM365ClientSecret] + c.TenantID = a.Config[keyM365TenantID] + } + return c, c.validate() +} + +func (c M365Config) validate() error { + check := map[string]string{ + credentials.ClientID: c.ClientID, + credentials.ClientSecret: c.ClientSecret, + credentials.TenantID: c.TenantID, + } + for k, v := range check { + if len(v) == 0 { + return errors.Wrap(errMissingRequired, k) + } + } + return nil +} diff --git a/src/pkg/account/m365_test.go b/src/pkg/account/m365_test.go new file mode 100644 index 000000000..fee798bf2 --- /dev/null +++ b/src/pkg/account/m365_test.go @@ -0,0 +1,122 @@ +package account_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/alcionai/corso/pkg/account" + "github.com/alcionai/corso/pkg/credentials" +) + +type M365CfgSuite struct { + suite.Suite +} + +func TestM365CfgSuite(t *testing.T) { + suite.Run(t, new(M365CfgSuite)) +} + +var goodM365Config = account.M365Config{ + M365: credentials.M365{ + ClientID: "cid", + ClientSecret: "cs", + TenantID: "tid", + }, +} + +func (suite *M365CfgSuite) TestM365Config_Config() { + m365 := goodM365Config + c, err := m365.Config() + require.NoError(suite.T(), err) + + table := []struct { + key string + expect string + }{ + {"m365_clientID", m365.ClientID}, + {"m365_clientSecret", m365.ClientSecret}, + {"m365_tenantID", m365.TenantID}, + } + for _, test := range table { + assert.Equal(suite.T(), test.expect, c[test.key]) + } +} + +func (suite *M365CfgSuite) TestAccount_M365Config() { + t := suite.T() + + in := goodM365Config + a, err := account.NewAccount(account.ProviderM365, in) + require.NoError(t, err) + out, err := a.M365Config() + require.NoError(t, err) + + assert.Equal(t, in.ClientID, out.ClientID) + assert.Equal(t, in.ClientSecret, out.ClientSecret) + assert.Equal(t, in.TenantID, out.TenantID) +} + +func makeTestM365Cfg(cid, cs, tid string) account.M365Config { + return account.M365Config{ + M365: credentials.M365{ + ClientID: cid, + ClientSecret: cs, + TenantID: tid, + }, + } +} + +func (suite *M365CfgSuite) TestAccount_M365Config_InvalidCases() { + // missing required properties + table := []struct { + name string + cfg account.M365Config + }{ + {"missing client ID", makeTestM365Cfg("", "cs", "tid")}, + {"missing client secret", makeTestM365Cfg("cid", "", "tid")}, + {"missing tenant ID", makeTestM365Cfg("cid", "cs", "")}, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + _, err := account.NewAccount(account.ProviderUnknown, test.cfg) + assert.Error(t, err) + }) + } + + // required property not populated in account + table2 := []struct { + name string + amend func(account.Account) + }{ + { + "missing clientID", + func(a account.Account) { + a.Config["m365_clientID"] = "" + }, + }, + { + "missing client secret", + func(a account.Account) { + a.Config["m365_clientSecret"] = "" + }, + }, + { + "missing tenant id", + func(a account.Account) { + a.Config["m365_tenantID"] = "" + }, + }, + } + for _, test := range table2 { + suite.T().Run(test.name, func(t *testing.T) { + st, err := account.NewAccount(account.ProviderUnknown, goodM365Config) + assert.NoError(t, err) + test.amend(st) + _, err = st.M365Config() + assert.Error(t, err) + }) + } +} diff --git a/src/pkg/storage/s3_test.go b/src/pkg/storage/s3_test.go index 985fd2161..4adc25f20 100644 --- a/src/pkg/storage/s3_test.go +++ b/src/pkg/storage/s3_test.go @@ -133,7 +133,7 @@ func (suite *S3CfgSuite) TestStorage_S3Config_InvalidCases() { st, err := storage.NewStorage(storage.ProviderUnknown, goodS3Config) assert.NoError(t, err) test.amend(st) - _, err = st.CommonConfig() + _, err = st.S3Config() assert.Error(t, err) }) }