diff --git a/src/pkg/selectors/exchange.go b/src/pkg/selectors/exchange.go index 3610dba78..49a07aa24 100644 --- a/src/pkg/selectors/exchange.go +++ b/src/pkg/selectors/exchange.go @@ -1,129 +1,258 @@ package selectors import ( - "strconv" + "strings" ) -// Exchange provides an api for scoping -// data in the Exchange service. -type Exchange struct { - Selector -} +// --------------------------------------------------------------------------- +// Selectors +// --------------------------------------------------------------------------- -// ToExchange transforms the generic selector into an Exchange. -// Errors if the service defined by the selector is not ServiceExchange. -func (s Selector) ToExchange() (*Exchange, error) { - if s.service != ServiceExchange { - return nil, badCastErr(ServiceExchange, s.service) +type ( + // exchange provides an api for selecting + // data scopes applicable to the Exchange service. + exchange struct { + 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. -func NewExchange(tenantID string) *Exchange { - src := Exchange{ - newSelector(tenantID, ServiceExchange), +func NewExchangeBackup() *ExchangeBackup { + src := ExchangeBackup{ + exchange{ + newSelector(ServiceExchange, ""), + }, } 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. -func (s *Exchange) Scopes() []exchangeScope { +func (s *exchange) Scopes() []exchangeScope { scopes := []exchangeScope{} - for _, v := range s.scopes { + for _, v := range s.Includes { scopes = append(scopes, exchangeScope(v)) } return scopes } -// the following are called by the client to specify the constraints -// 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. +//go:generate stringer -type=exchangeCategory const ( ExchangeCategoryUnknown exchangeCategory = iota ExchangeContact + ExchangeContactFolder ExchangeEvent - ExchangeFolder ExchangeMail + ExchangeMailFolder ExchangeUser ) -// String complies with the stringer interface, so that exchangeCategories -// can be added into the scope map. -func (ec exchangeCategory) String() string { - return strconv.Itoa(int(ec)) +func exchangeCatAtoI(s string) exchangeCategory { + switch s { + case ExchangeContact.String(): + 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. func (s exchangeScope) Category() exchangeCategory { - return exchangeCategory(getIota(s, scopeKeyCategory)) + return exchangeCatAtoI(s[scopeKeyCategory]) } -// Granularity describes the breadth of data in scope. -func (s exchangeScope) Granularity() scopeGranularity { - return granularityOf(s) +// IncludeCategory checks whether the scope includes a +// certain category of data. +// 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 { - return s[exchangeScopeKeyUserID] -} - -func (s exchangeScope) ContactID() string { - return s[exchangeScopeKeyContactID] -} - -func (s exchangeScope) EventID() string { - return s[exchangeScopeKeyEventID] -} - -func (s exchangeScope) FolderID() string { - return s[exchangeScopeKeyFolderID] -} - -func (s exchangeScope) MessageID() string { - return s[exchangeScopeKeyMessageID] +// Get returns the data category in the scope. If the scope +// contains all data types for a user, it'll return the +// ExchangeUser category. +func (s exchangeScope) Get(cat exchangeCategory) []string { + v, ok := s[cat.String()] + if !ok { + return []string{None} + } + return strings.Split(v, ",") } diff --git a/src/pkg/selectors/exchange_test.go b/src/pkg/selectors/exchange_test.go index 1da78b728..6636cb058 100644 --- a/src/pkg/selectors/exchange_test.go +++ b/src/pkg/selectors/exchange_test.go @@ -1,12 +1,11 @@ -package selectors_test +package selectors import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - - "github.com/alcionai/corso/pkg/selectors" ) type ExchangeSourceSuite struct { @@ -17,10 +16,205 @@ func TestExchangeSourceSuite(t *testing.T) { suite.Run(t, new(ExchangeSourceSuite)) } -func (suite *ExchangeSourceSuite) TestNewExchangeSource() { +func (suite *ExchangeSourceSuite) TestNewExchangeBackup() { t := suite.T() - es := selectors.NewExchange("tid") - assert.Equal(t, es.TenantID, "tid") - assert.Equal(t, es.Service(), selectors.ServiceExchange) - assert.NotZero(t, es.Scopes()) + eb := NewExchangeBackup() + assert.Equal(t, eb.Service, ServiceExchange) + assert.Zero(t, eb.RestorePointID) + 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)) + }) + } } diff --git a/src/pkg/selectors/exchangecategory_string.go b/src/pkg/selectors/exchangecategory_string.go new file mode 100644 index 000000000..89e5b244b --- /dev/null +++ b/src/pkg/selectors/exchangecategory_string.go @@ -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]] +} diff --git a/src/pkg/selectors/selectors.go b/src/pkg/selectors/selectors.go index 0ec1f7b80..599c3160a 100644 --- a/src/pkg/selectors/selectors.go +++ b/src/pkg/selectors/selectors.go @@ -1,8 +1,6 @@ package selectors import ( - "strconv" - "github.com/pkg/errors" ) @@ -17,71 +15,57 @@ const ( var ErrorBadSelectorCast = errors.New("wrong selector service type") const ( - scopeKeyGranularity = "granularity" - scopeKeyCategory = "category" + scopeKeyCategory = "category" ) const ( // All is the wildcard value used to express "all data of " - // Ex: Events(u1, All) => all events for user u1. - All = "*" + // Ex: {user: u1, events: All) => all events for user u1. + All = "ß∂ƒ∑´®≈ç√¬˜" + // None is usesd to express "no data of " + // Ex: {user: u1, events: None} => no events for user u1. + None = "√ç≈œ´∆¬˚¨π" ) +// --------------------------------------------------------------------------- +// Selector +// --------------------------------------------------------------------------- + // The core selector. Has no api for setting or retrieving data. // Is only used to pass along more specific selector instances. type Selector struct { - TenantID string // The tenant making the request. - service service // 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. + RestorePointID string `json:"restorePointID,omitempty"` // A restore point id, used only by restore operations. + Service service `json:"service,omitempty"` // The service scope of the data. Exchange, Teams, Sharepoint, etc. + 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. -func newSelector(tenantID string, s service) Selector { +func newSelector(s service, restorePointID string) Selector { return Selector{ - TenantID: tenantID, - service: s, - scopes: []map[string]string{}, + RestorePointID: restorePointID, + Service: s, + Excludes: []map[string]string{}, + Includes: []map[string]string{}, } } -// Service return the service enum for the selector. -func (s Selector) Service() service { - return s.service -} +// --------------------------------------------------------------------------- +// Destination +// --------------------------------------------------------------------------- + +type Destination map[string]string + +var ErrorDestinationAlreadySet = errors.New("destination is already declared") + +// --------------------------------------------------------------------------- +// helpers +// --------------------------------------------------------------------------- func badCastErr(cast, is service) error { return errors.Wrapf(ErrorBadSelectorCast, "%s service is not %s", cast, is) } -type scopeGranularity int - -// 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 +func existingDestinationErr(category, is string) error { + return errors.Wrapf(ErrorDestinationAlreadySet, "%s destination already set to %s", category, is) } diff --git a/src/pkg/selectors/selectors_test.go b/src/pkg/selectors/selectors_test.go index 0b333d890..9d28b596e 100644 --- a/src/pkg/selectors/selectors_test.go +++ b/src/pkg/selectors/selectors_test.go @@ -1,7 +1,6 @@ package selectors import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -18,48 +17,19 @@ func TestSelectorSuite(t *testing.T) { func (suite *SelectorSuite) TestNewSelector() { t := suite.T() - s := newSelector("tid", ServiceUnknown) + s := newSelector(ServiceUnknown, "rpid") assert.NotNil(t, s) - assert.Equal(t, s.TenantID, "tid") - assert.Equal(t, s.service, ServiceUnknown) - assert.NotNil(t, s.scopes) -} - -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) - }) - } + assert.Equal(t, s.Service, ServiceUnknown) + assert.Equal(t, s.RestorePointID, "rpid") + assert.NotNil(t, s.Includes) } func (suite *SelectorSuite) TestBadCastErr() { err := badCastErr(ServiceUnknown, ServiceExchange) assert.Error(suite.T(), err) } + +func (suite *SelectorSuite) TestExistingDestinationErr() { + err := existingDestinationErr("foo", "bar") + assert.Error(suite.T(), err) +}