From 34697efbeb0e668699a2cfbd9114cf1c0132dbad Mon Sep 17 00:00:00 2001 From: ashmrtn <3891298+ashmrtn@users.noreply.github.com> Date: Tue, 30 Aug 2022 16:23:55 -0700 Subject: [PATCH] Logic to validate service/category pairs for paths (#689) * Logic to validate service/category pairs for paths * convert string to service or category const * check that a given service/category pair is a valid resource type * Add basic tests for validateServiceAndCategory --- src/internal/path/resource_path.go | 56 +++++++++++++ src/internal/path/service_category_test.go | 98 ++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/internal/path/service_category_test.go diff --git a/src/internal/path/resource_path.go b/src/internal/path/resource_path.go index 505a0c6ba..211440956 100644 --- a/src/internal/path/resource_path.go +++ b/src/internal/path/resource_path.go @@ -1,5 +1,11 @@ package path +import ( + "github.com/pkg/errors" +) + +const unknownServiceCombination = "unknown service/category combination %q/%q" + type ServiceType int //go:generate stringer -type=ServiceType -linecomment @@ -8,6 +14,15 @@ const ( ExchangeService // exchange ) +func toServiceType(service string) ServiceType { + switch service { + case ExchangeService.String(): + return ExchangeService + default: + return UnknownService + } +} + type CategoryType int //go:generate stringer -type=CategoryType -linecomment @@ -16,6 +31,47 @@ const ( EmailCategory // email ) +func toCategoryType(category string) CategoryType { + switch category { + case EmailCategory.String(): + return EmailCategory + default: + return UnknownCategory + } +} + +// serviceCategories is a mapping of all valid service/category pairs. +var serviceCategories = map[ServiceType]map[CategoryType]struct{}{ + ExchangeService: { + EmailCategory: {}, + }, +} + +func validateServiceAndCategory(s, c string) (ServiceType, CategoryType, error) { + // Validity of service checked on first-level lookup to serviceCategories. + service := toServiceType(s) + + category := toCategoryType(c) + if category == UnknownCategory { + return UnknownService, UnknownCategory, errors.Errorf("unknown category string %q", c) + } + + cats, ok := serviceCategories[service] + if !ok { + return UnknownService, UnknownCategory, errors.Errorf("unknown service string %q", s) + } + + if _, ok := cats[category]; !ok { + return UnknownService, UnknownCategory, errors.Errorf( + unknownServiceCombination, + service, + category, + ) + } + + return service, category, nil +} + // dataLayerResourcePath allows callers to extract information from a // resource-specific path. This struct is unexported so that callers are // forced to use the pre-defined constructors, making it impossible to create a diff --git a/src/internal/path/service_category_test.go b/src/internal/path/service_category_test.go new file mode 100644 index 000000000..0ef61c536 --- /dev/null +++ b/src/internal/path/service_category_test.go @@ -0,0 +1,98 @@ +package path + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type ServiceCategoryUnitSuite struct { + suite.Suite +} + +func TestServiceCategoryUnitSuite(t *testing.T) { + suite.Run(t, new(ServiceCategoryUnitSuite)) +} + +func (suite *ServiceCategoryUnitSuite) TestValidateServiceAndCategoryBadStringErrors() { + table := []struct { + name string + service string + category string + }{ + { + name: "Service", + service: "foo", + category: EmailCategory.String(), + }, + { + name: "Category", + service: ExchangeService.String(), + category: "foo", + }, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + _, _, err := validateServiceAndCategory(test.service, test.category) + assert.Error(suite.T(), err) + }) + } +} + +func (suite *ServiceCategoryUnitSuite) TestValidateServiceAndCategory() { + table := []struct { + name string + service string + category string + expectedService ServiceType + expectedCategory CategoryType + check assert.ErrorAssertionFunc + }{ + { + name: "UnknownService", + service: UnknownService.String(), + category: EmailCategory.String(), + check: assert.Error, + }, + { + name: "UnknownCategory", + service: ExchangeService.String(), + category: UnknownCategory.String(), + check: assert.Error, + }, + { + name: "BadServiceString", + service: "foo", + category: EmailCategory.String(), + check: assert.Error, + }, + { + name: "BadCategoryString", + service: ExchangeService.String(), + category: "foo", + check: assert.Error, + }, + { + name: "ExchangeEmail", + service: ExchangeService.String(), + category: EmailCategory.String(), + expectedService: ExchangeService, + expectedCategory: EmailCategory, + check: assert.NoError, + }, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + s, c, err := validateServiceAndCategory(test.service, test.category) + test.check(t, err) + + if err != nil { + return + } + + assert.Equal(t, test.expectedService, s) + assert.Equal(t, test.expectedCategory, c) + }) + } +}