add Account pattern to mimic storage (#221)
* add Account pattern to mimic storage Introduces /pkg/account, which produces a generic Account struct that can be configured from specific provider details (such as m365 credentials), and serialized back to those details as needed. Next steps include replacing repository.Account with the new account. After that, tenantID should get removed from the m365 credentials handling and placed into the Account configuration instead.
This commit is contained in:
parent
243343c0e9
commit
3a3303a817
53
src/pkg/account/account.go
Normal file
53
src/pkg/account/account.go
Normal file
@ -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
|
||||||
|
}
|
||||||
97
src/pkg/account/account_test.go
Normal file
97
src/pkg/account/account_test.go
Normal file
@ -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"])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/pkg/account/m365.go
Normal file
59
src/pkg/account/m365.go
Normal file
@ -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
|
||||||
|
}
|
||||||
122
src/pkg/account/m365_test.go
Normal file
122
src/pkg/account/m365_test.go
Normal file
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -133,7 +133,7 @@ func (suite *S3CfgSuite) TestStorage_S3Config_InvalidCases() {
|
|||||||
st, err := storage.NewStorage(storage.ProviderUnknown, goodS3Config)
|
st, err := storage.NewStorage(storage.ProviderUnknown, goodS3Config)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
test.amend(st)
|
test.amend(st)
|
||||||
_, err = st.CommonConfig()
|
_, err = st.S3Config()
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user