update selector to match current design (#266)

* update selector to match current design

The selector design is progressing in the Showdown doc.
This updates the existing structs to match the expectations in that doc.
This commit is contained in:
Keepers 2022-07-05 10:20:19 -06:00 committed by GitHub
parent cdf368ad20
commit 25a1e972e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 493 additions and 187 deletions

View File

@ -1,129 +1,258 @@
package selectors package selectors
import ( import (
"strconv" "strings"
) )
// Exchange provides an api for scoping // ---------------------------------------------------------------------------
// data in the Exchange service. // Selectors
type Exchange struct { // ---------------------------------------------------------------------------
Selector
}
// ToExchange transforms the generic selector into an Exchange. type (
// Errors if the service defined by the selector is not ServiceExchange. // exchange provides an api for selecting
func (s Selector) ToExchange() (*Exchange, error) { // data scopes applicable to the Exchange service.
if s.service != ServiceExchange { exchange struct {
return nil, badCastErr(ServiceExchange, s.service) Selector
} }
src := Exchange{s}
return &src, nil // ExchangeBackup provides an api for selecting
} // data scopes applicable to the Exchange service,
// plus backup-specific methods.
ExchangeBackup struct {
exchange
}
// ExchangeRestore provides an api for selecting
// data scopes applicable to the Exchange service,
// plus restore-specific methods.
ExchangeRestore struct {
exchange
}
)
// NewExchange produces a new Selector with the service set to ServiceExchange. // NewExchange produces a new Selector with the service set to ServiceExchange.
func NewExchange(tenantID string) *Exchange { func NewExchangeBackup() *ExchangeBackup {
src := Exchange{ src := ExchangeBackup{
newSelector(tenantID, ServiceExchange), exchange{
newSelector(ServiceExchange, ""),
},
} }
return &src return &src
} }
// ToExchangeBackup transforms the generic selector into an ExchangeBackup.
// Errors if the service defined by the selector is not ServiceExchange.
func (s Selector) ToExchangeBackup() (*ExchangeBackup, error) {
if s.Service != ServiceExchange {
return nil, badCastErr(ServiceExchange, s.Service)
}
src := ExchangeBackup{exchange{s}}
return &src, nil
}
// NewExchangeRestore produces a new Selector with the service set to ServiceExchange.
func NewExchangeRestore(restorePointID string) *ExchangeRestore {
src := ExchangeRestore{
exchange{
newSelector(ServiceExchange, restorePointID),
},
}
return &src
}
// ToExchangeRestore transforms the generic selector into an ExchangeRestore.
// Errors if the service defined by the selector is not ServiceExchange.
func (s Selector) ToExchangeRestore() (*ExchangeRestore, error) {
if s.Service != ServiceExchange {
return nil, badCastErr(ServiceExchange, s.Service)
}
src := ExchangeRestore{exchange{s}}
return &src, nil
}
// IncludeContacts selects the specified contacts owned by the user.
func (s *exchange) IncludeContacts(u string, vs ...string) {
// todo
}
// IncludeContactFolders selects the specified contactFolders owned by the user.
func (s *exchange) IncludeContactFolders(u string, vs ...string) {
// todo
}
// IncludeEvents selects the specified events owned by the user.
func (s *exchange) IncludeEvents(u string, vs ...string) {
// todo
}
// IncludeMail selects the specified mail messages within the given folder,
// owned by the user.
func (s *exchange) IncludeMail(u, f string, vs ...string) {
// todo
}
// IncludeMailFolders selects the specified mail folders owned by the user.
func (s *exchange) IncludeMailFolders(u string, vs ...string) {
// todo
}
// IncludeUsers selects the specified users. All of their data is included.
func (s *exchange) IncludeUsers(us ...string) {
// todo
}
// ExcludeContacts selects the specified contacts owned by the user.
func (s *exchange) ExcludeContacts(u string, vs ...string) {
// todo
}
// ExcludeContactFolders selects the specified contactFolders owned by the user.
func (s *exchange) ExcludeContactFolders(u string, vs ...string) {
// todo
}
// ExcludeEvents selects the specified events owned by the user.
func (s *exchange) ExcludeEvents(u string, vs ...string) {
// todo
}
// ExcludeMail selects the specified mail messages within the given folder,
// owned by the user.
func (s *exchange) ExcludeMail(u, f string, vs ...string) {
// todo
}
// ExcludeMailFolders selects the specified mail folders owned by the user.
func (s *exchange) ExcludeMailFolders(u string, vs ...string) {
// todo
}
// ExcludeUsers selects the specified users. All of their data is excluded.
func (s *exchange) ExcludeUsers(us ...string) {
// todo
}
// ---------------------------------------------------------------------------
// Destination
// ---------------------------------------------------------------------------
type ExchangeDestination Destination
func NewExchangeDestination() ExchangeDestination {
return ExchangeDestination{}
}
// GetOrDefault gets the destination of the provided category. If no
// destination is set, returns the current value.
func (d ExchangeDestination) GetOrDefault(cat exchangeCategory, current string) string {
dest, ok := d[cat.String()]
if !ok {
return current
}
return dest
}
// Sets the destination value of the provided category. Returns an error
// if a destination is already declared for that category.
func (d ExchangeDestination) Set(cat exchangeCategory, dest string) error {
if len(dest) == 0 {
return nil
}
cs := cat.String()
if curr, ok := d[cs]; ok {
return existingDestinationErr(cs, curr)
}
d[cs] = dest
return nil
}
// ---------------------------------------------------------------------------
// Scopes
// ---------------------------------------------------------------------------
type (
// exchangeScope specifies the data available
// when interfacing with the Exchange service.
exchangeScope map[string]string
// exchangeCategory enumerates the type of the lowest level
// of data () in a scope.
exchangeCategory int
)
// Scopes retrieves the list of exchangeScopes in the selector. // Scopes retrieves the list of exchangeScopes in the selector.
func (s *Exchange) Scopes() []exchangeScope { func (s *exchange) Scopes() []exchangeScope {
scopes := []exchangeScope{} scopes := []exchangeScope{}
for _, v := range s.scopes { for _, v := range s.Includes {
scopes = append(scopes, exchangeScope(v)) scopes = append(scopes, exchangeScope(v))
} }
return scopes return scopes
} }
// the following are called by the client to specify the constraints //go:generate stringer -type=exchangeCategory
// each call appends one or more scopes to the selector.
// Users selects the specified users. All of their data is included.
func (s *Exchange) Users(us ...string) {
// todo
}
// Contacts selects the specified contacts owned by the user.
func (s *Exchange) Contacts(u string, vs ...string) {
// todo
}
// Events selects the specified events owned by the user.
func (s *Exchange) Events(u string, vs ...string) {
// todo
}
// MailFolders selects the specified mail folders owned by the user.
func (s *Exchange) MailFolders(u string, vs ...string) {
// todo
}
// MailMessages selects the specified mail messages within the given folder,
// owned by the user.
func (s *Exchange) MailMessages(u, f string, vs ...string) {
// todo
}
// -----------------------
// exchangeScope specifies the data available
// when interfacing with the Exchange service.
type exchangeScope map[string]string
type exchangeCategory int
// exchangeCategory describes the type of data in scope.
const ( const (
ExchangeCategoryUnknown exchangeCategory = iota ExchangeCategoryUnknown exchangeCategory = iota
ExchangeContact ExchangeContact
ExchangeContactFolder
ExchangeEvent ExchangeEvent
ExchangeFolder
ExchangeMail ExchangeMail
ExchangeMailFolder
ExchangeUser ExchangeUser
) )
// String complies with the stringer interface, so that exchangeCategories func exchangeCatAtoI(s string) exchangeCategory {
// can be added into the scope map. switch s {
func (ec exchangeCategory) String() string { case ExchangeContact.String():
return strconv.Itoa(int(ec)) return ExchangeContact
case ExchangeContactFolder.String():
return ExchangeContactFolder
case ExchangeEvent.String():
return ExchangeEvent
case ExchangeMail.String():
return ExchangeMail
case ExchangeMailFolder.String():
return ExchangeMailFolder
case ExchangeUser.String():
return ExchangeUser
default:
return ExchangeCategoryUnknown
}
} }
var (
exchangeScopeKeyContactID = ExchangeContact.String()
exchangeScopeKeyEventID = ExchangeEvent.String()
exchangeScopeKeyFolderID = ExchangeFolder.String()
exchangeScopeKeyMessageID = ExchangeMail.String()
exchangeScopeKeyUserID = ExchangeUser.String()
)
// Category describes the type of the data in scope. // Category describes the type of the data in scope.
func (s exchangeScope) Category() exchangeCategory { func (s exchangeScope) Category() exchangeCategory {
return exchangeCategory(getIota(s, scopeKeyCategory)) return exchangeCatAtoI(s[scopeKeyCategory])
} }
// Granularity describes the breadth of data in scope. // IncludeCategory checks whether the scope includes a
func (s exchangeScope) Granularity() scopeGranularity { // certain category of data.
return granularityOf(s) // Ex: to check if the scope includes mail data:
// s.IncludesCategory(selector.ExchangeMail)
func (s exchangeScope) IncludesCategory(cat exchangeCategory) bool {
sCat := s.Category()
if cat == ExchangeCategoryUnknown || sCat == ExchangeCategoryUnknown {
return false
}
if cat == ExchangeUser || sCat == ExchangeUser {
return true
}
switch sCat {
case ExchangeContact, ExchangeContactFolder:
return cat == ExchangeContact || cat == ExchangeContactFolder
case ExchangeEvent:
return cat == ExchangeEvent
case ExchangeMail, ExchangeMailFolder:
return cat == ExchangeMail || cat == ExchangeMailFolder
}
return false
} }
func (s exchangeScope) UserID() string { // Get returns the data category in the scope. If the scope
return s[exchangeScopeKeyUserID] // contains all data types for a user, it'll return the
} // ExchangeUser category.
func (s exchangeScope) Get(cat exchangeCategory) []string {
func (s exchangeScope) ContactID() string { v, ok := s[cat.String()]
return s[exchangeScopeKeyContactID] if !ok {
} return []string{None}
}
func (s exchangeScope) EventID() string { return strings.Split(v, ",")
return s[exchangeScopeKeyEventID]
}
func (s exchangeScope) FolderID() string {
return s[exchangeScopeKeyFolderID]
}
func (s exchangeScope) MessageID() string {
return s[exchangeScopeKeyMessageID]
} }

View File

@ -1,12 +1,11 @@
package selectors_test package selectors
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/pkg/selectors"
) )
type ExchangeSourceSuite struct { type ExchangeSourceSuite struct {
@ -17,10 +16,205 @@ func TestExchangeSourceSuite(t *testing.T) {
suite.Run(t, new(ExchangeSourceSuite)) suite.Run(t, new(ExchangeSourceSuite))
} }
func (suite *ExchangeSourceSuite) TestNewExchangeSource() { func (suite *ExchangeSourceSuite) TestNewExchangeBackup() {
t := suite.T() t := suite.T()
es := selectors.NewExchange("tid") eb := NewExchangeBackup()
assert.Equal(t, es.TenantID, "tid") assert.Equal(t, eb.Service, ServiceExchange)
assert.Equal(t, es.Service(), selectors.ServiceExchange) assert.Zero(t, eb.RestorePointID)
assert.NotZero(t, es.Scopes()) assert.NotZero(t, eb.Scopes())
}
func (suite *ExchangeSourceSuite) TestToExchangeBackup() {
t := suite.T()
eb := NewExchangeBackup()
s := eb.Selector
eb, err := s.ToExchangeBackup()
require.NoError(t, err)
assert.Equal(t, eb.Service, ServiceExchange)
assert.Zero(t, eb.RestorePointID)
assert.NotZero(t, eb.Scopes())
}
func (suite *ExchangeSourceSuite) TestNewExchangeRestore() {
t := suite.T()
er := NewExchangeRestore("rpid")
assert.Equal(t, er.Service, ServiceExchange)
assert.Equal(t, er.RestorePointID, "rpid")
assert.NotZero(t, er.Scopes())
}
func (suite *ExchangeSourceSuite) TestToExchangeRestore() {
t := suite.T()
eb := NewExchangeRestore("rpid")
s := eb.Selector
eb, err := s.ToExchangeRestore()
require.NoError(t, err)
assert.Equal(t, eb.Service, ServiceExchange)
assert.Equal(t, eb.RestorePointID, "rpid")
assert.NotZero(t, eb.Scopes())
}
func (suite *ExchangeSourceSuite) TestNewExchangeDestination() {
t := suite.T()
dest := NewExchangeDestination()
assert.Len(t, dest, 0)
}
func (suite *ExchangeSourceSuite) TestExchangeDestination_Set() {
dest := NewExchangeDestination()
table := []exchangeCategory{
ExchangeCategoryUnknown,
ExchangeContact,
ExchangeContactFolder,
ExchangeEvent,
ExchangeMail,
ExchangeMailFolder,
ExchangeUser,
}
for _, test := range table {
suite.T().Run(test.String(), func(t *testing.T) {
assert.NoError(t, dest.Set(test, "foo"))
assert.Error(t, dest.Set(test, "foo"))
})
}
assert.NoError(suite.T(), dest.Set(ExchangeUser, ""))
}
func (suite *ExchangeSourceSuite) TestExchangeDestination_GetOrDefault() {
dest := NewExchangeDestination()
table := []exchangeCategory{
ExchangeCategoryUnknown,
ExchangeContact,
ExchangeContactFolder,
ExchangeEvent,
ExchangeMail,
ExchangeMailFolder,
ExchangeUser,
}
for _, test := range table {
suite.T().Run(test.String(), func(t *testing.T) {
assert.Equal(t, "bar", dest.GetOrDefault(test, "bar"))
assert.NoError(t, dest.Set(test, "foo"))
assert.Equal(t, "foo", dest.GetOrDefault(test, "bar"))
})
}
}
var allScopesExceptUnknown = map[string]string{
ExchangeContact.String(): All,
ExchangeContactFolder.String(): All,
ExchangeEvent.String(): All,
ExchangeMail.String(): All,
ExchangeMailFolder.String(): All,
ExchangeUser.String(): All,
}
func (suite *ExchangeSourceSuite) TestExchangeBackup_Scopes() {
eb := NewExchangeBackup()
eb.Includes = []map[string]string{allScopesExceptUnknown}
// todo: swap the above for this
// eb := NewExchangeBackup().IncludeUsers(All)
scopes := eb.Scopes()
assert.Len(suite.T(), scopes, 1)
assert.Equal(
suite.T(),
allScopesExceptUnknown,
map[string]string(scopes[0]))
}
func (suite *ExchangeSourceSuite) TestExchangeScope_Category() {
table := []struct {
is exchangeCategory
expect exchangeCategory
check assert.ComparisonAssertionFunc
}{
{ExchangeCategoryUnknown, ExchangeCategoryUnknown, assert.Equal},
{ExchangeCategoryUnknown, ExchangeUser, assert.NotEqual},
{ExchangeContact, ExchangeContact, assert.Equal},
{ExchangeContact, ExchangeMailFolder, assert.NotEqual},
{ExchangeContactFolder, ExchangeContactFolder, assert.Equal},
{ExchangeContactFolder, ExchangeMailFolder, assert.NotEqual},
{ExchangeEvent, ExchangeEvent, assert.Equal},
{ExchangeEvent, ExchangeContact, assert.NotEqual},
{ExchangeMail, ExchangeMail, assert.Equal},
{ExchangeMail, ExchangeMailFolder, assert.NotEqual},
{ExchangeMailFolder, ExchangeMailFolder, assert.Equal},
{ExchangeMailFolder, ExchangeContactFolder, assert.NotEqual},
{ExchangeUser, ExchangeUser, assert.Equal},
{ExchangeUser, ExchangeCategoryUnknown, assert.NotEqual},
}
for _, test := range table {
suite.T().Run(test.is.String()+test.expect.String(), func(t *testing.T) {
eb := NewExchangeBackup()
eb.Includes = []map[string]string{{scopeKeyCategory: test.is.String()}}
scope := eb.Scopes()[0]
test.check(t, test.expect, scope.Category())
})
}
}
func (suite *ExchangeSourceSuite) TestExchangeScope_IncludesCategory() {
table := []struct {
is exchangeCategory
expect exchangeCategory
check assert.BoolAssertionFunc
}{
{ExchangeCategoryUnknown, ExchangeCategoryUnknown, assert.False},
{ExchangeCategoryUnknown, ExchangeUser, assert.False},
{ExchangeContact, ExchangeContactFolder, assert.True},
{ExchangeContact, ExchangeMailFolder, assert.False},
{ExchangeContactFolder, ExchangeContact, assert.True},
{ExchangeContactFolder, ExchangeMailFolder, assert.False},
{ExchangeEvent, ExchangeUser, assert.True},
{ExchangeEvent, ExchangeContact, assert.False},
{ExchangeMail, ExchangeMailFolder, assert.True},
{ExchangeMail, ExchangeContact, assert.False},
{ExchangeMailFolder, ExchangeMail, assert.True},
{ExchangeMailFolder, ExchangeContactFolder, assert.False},
{ExchangeUser, ExchangeUser, assert.True},
{ExchangeUser, ExchangeCategoryUnknown, assert.False},
{ExchangeUser, ExchangeMail, assert.True},
}
for _, test := range table {
suite.T().Run(test.is.String()+test.expect.String(), func(t *testing.T) {
eb := NewExchangeBackup()
eb.Includes = []map[string]string{{scopeKeyCategory: test.is.String()}}
scope := eb.Scopes()[0]
test.check(t, scope.IncludesCategory(test.expect))
})
}
}
func (suite *ExchangeSourceSuite) TestExchangeScope_Get() {
eb := NewExchangeBackup()
eb.Includes = []map[string]string{allScopesExceptUnknown}
// todo: swap the above for this
// eb := NewExchangeBackup().IncludeUsers(All)
scope := eb.Scopes()[0]
table := []exchangeCategory{
ExchangeContact,
ExchangeContactFolder,
ExchangeEvent,
ExchangeMail,
ExchangeMailFolder,
ExchangeUser,
}
assert.Equal(
suite.T(),
[]string{None},
scope.Get(ExchangeCategoryUnknown))
expect := []string{All}
for _, test := range table {
suite.T().Run(test.String(), func(t *testing.T) {
assert.Equal(t, expect, scope.Get(test))
})
}
} }

View File

@ -0,0 +1,29 @@
// Code generated by "stringer -type=exchangeCategory"; DO NOT EDIT.
package selectors
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ExchangeCategoryUnknown-0]
_ = x[ExchangeContact-1]
_ = x[ExchangeContactFolder-2]
_ = x[ExchangeEvent-3]
_ = x[ExchangeMail-4]
_ = x[ExchangeMailFolder-5]
_ = x[ExchangeUser-6]
}
const _exchangeCategory_name = "ExchangeCategoryUnknownExchangeContactExchangeContactFolderExchangeEventExchangeMailExchangeMailFolderExchangeUser"
var _exchangeCategory_index = [...]uint8{0, 23, 38, 59, 72, 84, 102, 114}
func (i exchangeCategory) String() string {
if i < 0 || i >= exchangeCategory(len(_exchangeCategory_index)-1) {
return "exchangeCategory(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _exchangeCategory_name[_exchangeCategory_index[i]:_exchangeCategory_index[i+1]]
}

View File

@ -1,8 +1,6 @@
package selectors package selectors
import ( import (
"strconv"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -17,71 +15,57 @@ const (
var ErrorBadSelectorCast = errors.New("wrong selector service type") var ErrorBadSelectorCast = errors.New("wrong selector service type")
const ( const (
scopeKeyGranularity = "granularity" scopeKeyCategory = "category"
scopeKeyCategory = "category"
) )
const ( const (
// All is the wildcard value used to express "all data of <type>" // All is the wildcard value used to express "all data of <type>"
// Ex: Events(u1, All) => all events for user u1. // Ex: {user: u1, events: All) => all events for user u1.
All = "*" All = "ß∂ƒ∑´®≈ç√¬˜"
// None is usesd to express "no data of <type>"
// Ex: {user: u1, events: None} => no events for user u1.
None = "√ç≈œ´∆¬˚¨π"
) )
// ---------------------------------------------------------------------------
// Selector
// ---------------------------------------------------------------------------
// The core selector. Has no api for setting or retrieving data. // The core selector. Has no api for setting or retrieving data.
// Is only used to pass along more specific selector instances. // Is only used to pass along more specific selector instances.
type Selector struct { type Selector struct {
TenantID string // The tenant making the request. RestorePointID string `json:"restorePointID,omitempty"` // A restore point id, used only by restore operations.
service service // The service scope of the data. Exchange, Teams, Sharepoint, etc. Service service `json:"service,omitempty"` // The service scope of the data. Exchange, Teams, Sharepoint, etc.
scopes []map[string]string // A slice of scopes. Expected to get cast to fooScope within each service handler. Excludes []map[string]string `json:"exclusions,omitempty"` // A slice of exclusions. Each exclusion applies to all inclusions.
Includes []map[string]string `json:"scopes,omitempty"` // A slice of inclusions. Expected to get cast to a service wrapper within each service handler.
} }
// helper for specific selector instance constructors. // helper for specific selector instance constructors.
func newSelector(tenantID string, s service) Selector { func newSelector(s service, restorePointID string) Selector {
return Selector{ return Selector{
TenantID: tenantID, RestorePointID: restorePointID,
service: s, Service: s,
scopes: []map[string]string{}, Excludes: []map[string]string{},
Includes: []map[string]string{},
} }
} }
// Service return the service enum for the selector. // ---------------------------------------------------------------------------
func (s Selector) Service() service { // Destination
return s.service // ---------------------------------------------------------------------------
}
type Destination map[string]string
var ErrorDestinationAlreadySet = errors.New("destination is already declared")
// ---------------------------------------------------------------------------
// helpers
// ---------------------------------------------------------------------------
func badCastErr(cast, is service) error { func badCastErr(cast, is service) error {
return errors.Wrapf(ErrorBadSelectorCast, "%s service is not %s", cast, is) return errors.Wrapf(ErrorBadSelectorCast, "%s service is not %s", cast, is)
} }
type scopeGranularity int func existingDestinationErr(category, is string) error {
return errors.Wrapf(ErrorDestinationAlreadySet, "%s destination already set to %s", category, is)
// granularity expresses the breadth of the request
const (
GranularityUnknown scopeGranularity = iota
SingleItem
AllIn
)
// String complies with the stringer interface, so that granularities
// can be added into the scope map.
func (g scopeGranularity) String() string {
return strconv.Itoa(int(g))
}
func granularityOf(selector map[string]string) scopeGranularity {
return scopeGranularity(getIota(selector, scopeKeyGranularity))
}
// retrieves the iota, stored as a string, and transforms it to
// an int. Any errors will return a 0 by default.
func getIota(m map[string]string, key string) int {
v, ok := m[key]
if !ok {
return 0
}
i, err := strconv.Atoi(v)
if err != nil {
return 0
}
return i
} }

View File

@ -1,7 +1,6 @@
package selectors package selectors
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -18,48 +17,19 @@ func TestSelectorSuite(t *testing.T) {
func (suite *SelectorSuite) TestNewSelector() { func (suite *SelectorSuite) TestNewSelector() {
t := suite.T() t := suite.T()
s := newSelector("tid", ServiceUnknown) s := newSelector(ServiceUnknown, "rpid")
assert.NotNil(t, s) assert.NotNil(t, s)
assert.Equal(t, s.TenantID, "tid") assert.Equal(t, s.Service, ServiceUnknown)
assert.Equal(t, s.service, ServiceUnknown) assert.Equal(t, s.RestorePointID, "rpid")
assert.NotNil(t, s.scopes) assert.NotNil(t, s.Includes)
}
func (suite *SelectorSuite) TestSelector_Service() {
table := []service{
ServiceUnknown,
ServiceExchange,
}
for _, test := range table {
suite.T().Run(fmt.Sprintf("testing %d", test), func(t *testing.T) {
s := newSelector("tid", test)
assert.Equal(t, s.Service(), test)
})
}
}
func (suite *SelectorSuite) TestGetIota() {
table := []struct {
name string
val string
expect int
}{
{"zero", "0", 0},
{"positive", "1", 1},
{"negative", "-1", -1},
{"empty", "", 0},
{"NaN", "fnords", 0},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
m := map[string]string{"test": test.val}
result := getIota(m, "test")
assert.Equal(t, result, test.expect)
})
}
} }
func (suite *SelectorSuite) TestBadCastErr() { func (suite *SelectorSuite) TestBadCastErr() {
err := badCastErr(ServiceUnknown, ServiceExchange) err := badCastErr(ServiceUnknown, ServiceExchange)
assert.Error(suite.T(), err) assert.Error(suite.T(), err)
} }
func (suite *SelectorSuite) TestExistingDestinationErr() {
err := existingDestinationErr("foo", "bar")
assert.Error(suite.T(), err)
}