This is an automated email from the ASF dual-hosted git repository.

rob pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new ab35846  Cache Config Parser (#3360)
ab35846 is described below

commit ab35846ef711a26c69280dd654fd1594054ce7c7
Author: Matthew Allen Moltzau <[email protected]>
AuthorDate: Thu Mar 21 13:17:51 2019 -0600

    Cache Config Parser (#3360)
    
    * Initial work of cache config parser
    
    * Updated comments, renamed constants, and other minor edits
    
    * Cache config parser is turning out. Restructured the config error. I have 
comments to check a few things in ATS. The cache_config_test is broken atm.
    
    * Implemented time validators and cleaned up. Only a few comments now. 
Still fleshing out tests
    
    * Some changes required after referring to ATS
    
    The whole config line is case insensitive now.
    
    Made action mandantory
    
    * Finished positive and negative config tests
    
    * 100% test coverage!
    
    Finished test cases for cache config
    
    Wrote tests for time format validators
    
    * Added license headers
    
    * Added case where config has comment
    
    * Added some comments for go doc.
    
    Added PURGE and PUSH methods.
    
    Edited switch to allow fallthrough.
    
    * Updated according to PR comments
    
    * Removed dot imports
    * Added additional test cases
    * Fixed new test cases by using ^ and $ tokens in the regex
    * Using new package name to be more idiomatic to go
    * Tried to make a few things clearer
---
 .../testing/api/v14/config/cachecfg/cachecfg.go    | 260 +++++++++++++++++++++
 .../api/v14/config/cachecfg/cachecfg_test.go       | 222 ++++++++++++++++++
 traffic_ops/testing/api/v14/config/common.go       |  87 +++++++
 traffic_ops/testing/api/v14/config/common_test.go  | 110 +++++++++
 traffic_ops/testing/api/v14/config/error.go        |  79 +++++++
 .../testing/api/v14/config/table_test_structs.go   |  28 +++
 6 files changed, 786 insertions(+)

diff --git a/traffic_ops/testing/api/v14/config/cachecfg/cachecfg.go 
b/traffic_ops/testing/api/v14/config/cachecfg/cachecfg.go
new file mode 100644
index 0000000..af632e5
--- /dev/null
+++ b/traffic_ops/testing/api/v14/config/cachecfg/cachecfg.go
@@ -0,0 +1,260 @@
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package cachecfg
+
+import (
+       "regexp"
+       "strings"
+
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/v14/config"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/test"
+       "github.com/go-ozzo/ozzo-validation/is"
+)
+
+// Parse takes a string presumed to be an ATS cache.config and validates that 
it is
+// syntatically correct.
+//
+// The general format of a cache config is three types of labels separated by 
spaces:
+//
+//  primary_destination=value secondary_specifier=value action=value
+//
+// For a full description of how to format a cache config, refer to the ATS 
documentation
+// for the cache config:
+// 
https://docs.trafficserver.apache.org/en/latest/admin-guide/files/cache.config.en.html
+//
+func Parse(config string) test.Error {
+       lines := strings.Split(config, "\n")
+
+       if len(lines) == 1 {
+               return parseConfigRule(lines[0])
+       }
+
+       for i, ln := range lines {
+               err := parseConfigRule(ln)
+               if err != nil {
+                       return err.Prepend("error on line %d: ", i+1)
+               }
+       }
+
+       return nil
+}
+
+func parsePrimaryDestinations(lhs string, rhs string) test.Error {
+
+       switch lhs {
+       case "dest_domain":
+               // dest_host is an alias for dest_domain
+               fallthrough
+       case "dest_host":
+               if err := is.Host.Validate(rhs); err != nil {
+                       return config.ErrorContext.NewError(config.InvalidHost, 
`"%s" %v`, rhs, err)
+               }
+       case "dest_ip":
+               if err := is.IP.Validate(rhs); err != nil {
+                       return config.ErrorContext.NewError(config.InvalidIP, 
`"%s" %v`, rhs, err)
+               }
+       case "host_regex":
+               fallthrough
+       case "url_regex":
+               // only makes sure the regex compiles, not that the regex 
generates anything valid
+               if _, err := regexp.Compile(rhs); err != nil {
+                       return 
config.ErrorContext.NewError(config.InvalidRegex, "%v", err)
+               }
+       default:
+               return config.ErrorContext.NewError(config.InvalidLabel)
+       }
+
+       return nil
+}
+
+func parseSecondarySpecifiers(lhs string, rhs string) test.Error {
+
+       switch lhs {
+       case "port":
+               if err := is.Port.Validate(rhs); err != nil {
+                       return 
config.ErrorContext.AddErrorCode(config.InvalidPort, err)
+               }
+       case "scheme":
+               if rhs != "http" && rhs != "https" {
+                       return 
config.ErrorContext.NewError(config.InvalidHTTPScheme)
+               }
+       case "prefix":
+               // no clear validation to perform
+       case "suffix":
+               // examples: gif jpeg
+               // no clear validation to perform
+       case "method":
+               // assuming all methods are valid
+               // see RFC 2616-9 for list of all methods
+               // PURGE and PUSH are specific to ATS
+               switch rhs {
+               case "get":
+               case "put":
+               case "post":
+               case "delete":
+               case "trace":
+               case "options":
+               case "head":
+               case "connect":
+               case "patch":
+               case "purge":
+               case "push":
+               default:
+                       return 
config.ErrorContext.NewError(config.UnknownMethod, `unknown method "%v"`, rhs)
+               }
+
+       case "time":
+               if err := config.Validate24HrTimeRange(rhs); err != nil {
+                       return 
config.ErrorContext.AddErrorCode(config.InvalidTimeRange24Hr, err)
+               }
+       case "src_ip":
+               if err := is.IP.Validate(rhs); err != nil {
+                       return 
config.ErrorContext.AddErrorCode(config.InvalidIP, err)
+               }
+       case "internal":
+               if rhs != "true" && rhs != "false" {
+                       return config.ErrorContext.NewError(config.InvalidBool)
+               }
+
+       default:
+               return config.ErrorContext.NewError(config.InvalidLabel)
+       }
+
+       return nil
+}
+
+func parseActions(lhs string, rhs string) test.Error {
+
+       switch lhs {
+       case "action":
+               switch rhs {
+               case "never-cache":
+               case "ignore-no-cache":
+               case "ignore-client-no-cache":
+               case "ignore-server-no-cache":
+               default:
+                       return 
config.ErrorContext.NewError(config.InvalidAction)
+               }
+
+       case "cache-responses-to-cookies":
+               digit := rhs[0]
+               if digit < '0' || '4' > digit || len(rhs) > 1 {
+                       return 
config.ErrorContext.NewError(config.InvalidCacheCookieResponse)
+               }
+
+       // All of these are time formats
+       case "pin-in-cache":
+               fallthrough
+       case "revalidate":
+               fallthrough
+       case "ttl-in-cache":
+               err := config.ValidateDHMSTimeFormat(rhs)
+               if err != nil {
+                       return 
config.ErrorContext.AddErrorCode(config.InvalidTimeFormatDHMS, err)
+               }
+       default:
+               return config.ErrorContext.NewError(config.InvalidLabel)
+       }
+
+       return nil
+}
+
+func parseConfigRule(rule string) test.Error {
+
+       var err test.Error
+
+       rule = strings.TrimSpace(rule)
+       if rule == "" || strings.HasPrefix(rule, "#") {
+               return nil
+       }
+
+       assignments := strings.Fields(rule)
+       last := len(assignments) - 1
+       if last < 1 {
+               return config.ErrorContext.NewError(config.NotEnoughAssignments)
+       }
+
+       // no individual secondary specifier label can be used twice
+       count := map[string]int{
+               "port":     0,
+               "scheme":   0,
+               "prefix":   0,
+               "suffix":   0,
+               "method":   0,
+               "time":     0,
+               "src_ip":   0,
+               "internal": 0,
+       }
+
+       // neither the rhs or lhs can contain any whitespace
+       assignment := regexp.MustCompile(`([a-z_\-\d]+)=(\S+)`)
+
+       destination := false
+       action := false
+
+       for _, elem := range assignments {
+               match := assignment.FindStringSubmatch(strings.ToLower(elem))
+               if match == nil {
+                       return 
config.ErrorContext.NewError(config.BadAssignmentMatch, `could not match 
assignment: "%v"`, elem)
+               }
+
+               err = parsePrimaryDestinations(match[1], match[2])
+               if err == nil {
+                       if destination {
+                               return 
config.ErrorContext.NewError(config.ExcessLabel, "too many primary destination 
labels")
+                       } else {
+                               destination = true
+                               continue
+                       }
+               }
+               if err.Code() != config.InvalidLabel {
+                       return err.Prepend(`coult not parse primary destination 
from "%s": `, match[0])
+               }
+
+               err = parseSecondarySpecifiers(match[1], match[2])
+               if err == nil {
+                       if count[match[1]]++; count[match[1]] == 2 {
+                               return 
config.ErrorContext.NewError(config.ExcessLabel, `the label "%s" can only be 
used once per rule`, match[1])
+                       }
+                       continue
+               }
+               if err.Code() != config.InvalidLabel {
+                       return err.Prepend(`could not parse secondary specifier 
from "%s": `, match[0])
+               }
+
+               err = parseActions(match[1], match[2])
+               if err == nil {
+                       action = true
+                       continue
+               }
+
+               if err.Code() == config.InvalidLabel {
+                       return err
+               } else {
+                       return err.Prepend(`could not parse action from "%s": 
`, match[0])
+               }
+
+       }
+
+       if !destination {
+               return config.ErrorContext.NewError(config.MissingLabel, 
"missing primary destination label")
+       }
+
+       if !action {
+               return config.ErrorContext.NewError(config.MissingLabel, 
"missing action lablel")
+       }
+
+       return nil
+}
diff --git a/traffic_ops/testing/api/v14/config/cachecfg/cachecfg_test.go 
b/traffic_ops/testing/api/v14/config/cachecfg/cachecfg_test.go
new file mode 100644
index 0000000..db684a9
--- /dev/null
+++ b/traffic_ops/testing/api/v14/config/cachecfg/cachecfg_test.go
@@ -0,0 +1,222 @@
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package cachecfg_test
+
+import (
+       "fmt"
+       "testing"
+
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/v14/config"
+       
"github.com/apache/trafficcontrol/traffic_ops/testing/api/v14/config/cachecfg"
+)
+
+var commonNegativeTests = []config.NegativeTest{
+       {
+               "too few assignments",
+               "foo1=foo",
+               config.NotEnoughAssignments,
+       },
+       {
+               "empty assignment",
+               "dest_domain= foo1=foo",
+               config.BadAssignmentMatch,
+       },
+       {
+               "missing equals in assignment",
+               "dest_domain foo1=foo",
+               config.BadAssignmentMatch,
+       },
+       {
+               "more than one primary destination",
+               "dest_domain=example dest_domain=example",
+               config.ExcessLabel,
+       },
+       {
+               "using a single secondary specifier twice",
+               "dest_domain=example scheme=http scheme=https",
+               config.ExcessLabel,
+       },
+       {
+               "missing primary destination label",
+               "port=80 method=get time=08:00-14:00",
+               config.MissingLabel,
+       },
+       {
+               "unknown primary field",
+               "foo1=foo foo2=foo foo3=foo",
+               config.InvalidLabel,
+       },
+       {
+               "good first line, but bad second",
+               fmt.Sprintf("%s\n\n%s",
+                       "dest_domain=example.com suffix=js revalidate=1d",
+                       "foo1=foo foo2=foo foo3=foo"),
+               config.InvalidLabel,
+       },
+}
+
+var primaryDestinationNegativeTests = []config.NegativeTest{
+       {
+               "bad url regex",
+               "url_regex=(example.com/.* foo2=foo",
+               config.InvalidRegex,
+       },
+       {
+               "bad host regex",
+               "host_regex=(example.* foo2=foo",
+               config.InvalidRegex,
+       },
+       {
+               "bad hostname",
+               "dest_domain=my%20bad%20domain.com foo1=foo",
+               config.InvalidHost,
+       },
+       {
+               "bad ip",
+               "dest_ip=bad_ip foo1=foo",
+               config.InvalidIP,
+       },
+}
+
+var secondarySpecifierNegativeTests = []config.NegativeTest{
+       {
+               "bad port",
+               "dest_domain=example port=90009000",
+               config.InvalidPort,
+       },
+       {
+               "bad scheme",
+               "dest_domain=example scheme=httpz",
+               config.InvalidHTTPScheme,
+       },
+       {
+               "bad method",
+               "dest_domain=example method=xxx",
+               config.UnknownMethod,
+       },
+       {
+               "bad time range",
+               "dest_domain=example time=16:00",
+               config.InvalidTimeRange24Hr,
+       },
+       {
+               "bad src ip",
+               "dest_domain=example src_ip=bad_ip",
+               config.InvalidIP,
+       },
+       {
+               "bad boolean value",
+               "dest_domain=example internal=xxx",
+               config.InvalidBool,
+       },
+}
+
+var actionNegativeTests = []config.NegativeTest{
+       {
+               "bad action value",
+               "dest_domain=example action=xxx",
+               config.InvalidAction,
+       },
+       {
+               "bad cache-responses-to-cookies",
+               "dest_domain=example cache-responses-to-cookies=42",
+               config.InvalidCacheCookieResponse,
+       },
+       {
+               "bad time format",
+               "dest_domain=example pin-in-cache=xxx",
+               config.InvalidTimeFormatDHMS,
+       },
+       {
+               "missing action label",
+               "dest_domain=example scheme=http",
+               config.MissingLabel,
+       },
+}
+
+var positiveTests = []config.PositiveTest{
+       {
+               "empty config",
+               "",
+       },
+       {
+               "empty multiline config",
+               "\n",
+       },
+       {
+               "empty config with whitespace",
+               "\t ",
+       },
+       {
+               "normal config returned from traffic ops",
+               fmt.Sprintf("%s\n%s",
+                       "# DO NOT EDIT - Generated for ATS_EDGE_TIER_CACHE by 
Traffic Ops on Fri Feb 15 22:01:53 UTC 2019",
+                       "dest_domain=origin.infra.ciab.test port=80 scheme=http 
action=never-cache"),
+       },
+       {
+               "tab-delimitted config",
+               
"dest_domain=origin.infra.ciab.test\tport=80\tscheme=http\taction=never-cache",
+       },
+       {
+               "multi-space delimitted config",
+               "dest_domain=origin.infra.ciab.test  port=80  scheme=http  
action=never-cache",
+       },
+       {
+               "multiline config with empty line",
+               fmt.Sprintf("%s\n\n%s",
+                       "dest_domain=example.com suffix=js revalidate=1d",
+                       "dest_domain=example.com prefix=foo suffix=js 
revalidate=7d"),
+       },
+       {
+               "many empty lines in config",
+               fmt.Sprintf("\n%s\n\n%s",
+                       "dest_domain=example.com suffix=js revalidate=1d\n",
+                       "dest_domain=example.com prefix=foo suffix=js 
revalidate=7d\n"),
+       },
+}
+
+func negativeTestDriver(tests []config.NegativeTest, t *testing.T) {
+       for _, test := range tests {
+               actual := cachecfg.Parse(test.Config)
+               if actual == nil || actual.Code() != test.Expected {
+                       t.Errorf(`
+  config: "%v"
+  returned error: "%v"
+  error should be related to: %v`, test.Config, actual, test.Description)
+               }
+       }
+}
+
+func TestCacheConfig(t *testing.T) {
+
+       // Negative Tests
+       negativeTestDriver(commonNegativeTests, t)
+       negativeTestDriver(primaryDestinationNegativeTests, t)
+       negativeTestDriver(secondarySpecifierNegativeTests, t)
+       negativeTestDriver(actionNegativeTests, t)
+
+       // Positive Tests
+       for _, test := range positiveTests {
+               actual := cachecfg.Parse(test.Config)
+               if actual != nil {
+                       t.Errorf(`
+  config: "%v"
+  returned error: "%v"
+  error should be nil
+  description: %v`, test.Config, actual, test.Description)
+               }
+
+       }
+}
diff --git a/traffic_ops/testing/api/v14/config/common.go 
b/traffic_ops/testing/api/v14/config/common.go
new file mode 100644
index 0000000..b21931e
--- /dev/null
+++ b/traffic_ops/testing/api/v14/config/common.go
@@ -0,0 +1,87 @@
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package config
+
+import (
+       "fmt"
+       "regexp"
+       "strconv"
+       "time"
+)
+
+// militaryTimeFmt defines the 24hr format
+// see https://golang.org/pkg/time/#Parse
+const militaryTimeFmt = "15:04"
+
+// Validate24HrTimeRange determines whether the provided
+// string fits in a format such as "08:00-16:00".
+func Validate24HrTimeRange(rng string) error {
+       rangeFormat := regexp.MustCompile(`^(\S+)-(\S+)$`)
+       match := rangeFormat.FindStringSubmatch(rng)
+       if match == nil {
+               return fmt.Errorf("string %v is not a range", rng)
+       }
+
+       t1, err := time.Parse(militaryTimeFmt, match[1])
+       if err != nil {
+               return fmt.Errorf("time range must be a 24Hr format")
+       }
+
+       t2, err := time.Parse(militaryTimeFmt, match[2])
+       if err != nil {
+               return fmt.Errorf("second time range must be a 24Hr format")
+       }
+
+       if t1.After(t2) {
+               return fmt.Errorf("first time should be smaller than the 
second")
+       }
+
+       return nil
+}
+
+// ValidateDHMSTimeFormat determines whether the provided
+// string fits in a format such as "1d8h", where the valid
+// units are days, hours, minutes, and seconds.
+func ValidateDHMSTimeFormat(time string) error {
+
+       if time == "" {
+               return fmt.Errorf("time string cannot be empty")
+       }
+
+       dhms := regexp.MustCompile(`^(\d+)([dhms])(\S*)$`)
+       match := dhms.FindStringSubmatch(time)
+
+       if match == nil {
+               return fmt.Errorf("invalid time format")
+       }
+
+       var count = map[string]int{
+               "d": 0,
+               "h": 0,
+               "m": 0,
+               "s": 0,
+       }
+       for match != nil {
+               if _, err := strconv.Atoi(match[1]); err != nil {
+                       return err
+               }
+               if count[match[2]]++; count[match[2]] == 2 {
+                       return fmt.Errorf("%s unit specified multiple times", 
match[2])
+               }
+               match = dhms.FindStringSubmatch(match[3])
+       }
+
+       return nil
+}
diff --git a/traffic_ops/testing/api/v14/config/common_test.go 
b/traffic_ops/testing/api/v14/config/common_test.go
new file mode 100644
index 0000000..c03947d
--- /dev/null
+++ b/traffic_ops/testing/api/v14/config/common_test.go
@@ -0,0 +1,110 @@
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package config
+
+import "testing"
+
+func Test24HrTimeRange(t *testing.T) {
+
+       var tests = []struct {
+               time string
+               ok   bool
+       }{
+               {"15:04", false},
+               {"16:00-08:00", false},
+               {"16:00-8:00", false},
+               {"xxx-8:00", false},
+               {"8:00-xxx", false},
+               {"8:00-16:00", true},
+               {"08:00-16:00", true},
+               {"", false},
+               {" ", false},
+               {"-", false},
+               {"--", false},
+               {" - ", false},
+               {"asdf", false},
+               {"08:00-asdf", false},
+               {"asdf-08:00", false},
+               {"-08:00", false},
+               {"08:00-", false},
+               {"08-09:00", false},
+               {"09:00-10", false},
+               {"09:00-10:0", false},
+               {"9:00-10:0", false},
+               {"08:00-32:00", false},
+               {"32:00-33:00", false},
+               {"08:00--16:00", false},
+               {"08:00-16:00-", false},
+               {"08:00-16:00-17:00", false},
+               {"08:00-09:00 16:00-17:00", false},
+               {"foo 16:00-17:00", false},
+               {"16:00-17:00 foo", false},
+       }
+
+       for _, test := range tests {
+               if err := Validate24HrTimeRange(test.time); (err == nil) != 
test.ok {
+                       if test.ok {
+                               t.Errorf(`
+  test should have passed
+  time: %v
+  err: %v`, test.time, err)
+                       } else {
+                               t.Errorf(`
+  test should not have passed
+  time: %v`, test.time)
+                       }
+               }
+       }
+
+}
+
+func TestDHMSTimeFormat(t *testing.T) {
+
+       var tests = []struct {
+               time string
+               ok   bool
+       }{
+               {"1s", true},
+               {"1m", true},
+               {"1h", true},
+               {"1d", true},
+               {"1d2h", true},
+               {"1m2s", true},
+               {"1d2h3m4s", true},
+               {"1s2h3m4d", true},
+               {"10000000000000000000000s", false},
+               {"1s2s", false},
+               {"1x", false},
+               {"1", false},
+               {"x", false},
+               {"", false},
+       }
+
+       for _, test := range tests {
+               if err := ValidateDHMSTimeFormat(test.time); (err == nil) != 
test.ok {
+                       if test.ok {
+                               t.Errorf(`
+  test should have passed
+  time: %v
+  err: %v`, test.time, err)
+                       } else {
+                               t.Errorf(`
+  test should not have passed
+  time: %v`, test.time)
+                       }
+               }
+       }
+
+}
diff --git a/traffic_ops/testing/api/v14/config/error.go 
b/traffic_ops/testing/api/v14/config/error.go
new file mode 100644
index 0000000..708c821
--- /dev/null
+++ b/traffic_ops/testing/api/v14/config/error.go
@@ -0,0 +1,79 @@
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package config
+
+import "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/test"
+
+// Error codes:
+const (
+       BadAssignmentMatch = iota + 1
+       NotEnoughAssignments
+       ExcessLabel
+       InvalidLabel
+       MissingLabel
+       InvalidAction
+       InvalidBool
+       InvalidCacheCookieResponse
+       InvalidHTTPScheme
+       InvalidHost
+       InvalidIP
+       UnknownMethod
+       InvalidPort
+       InvalidRegex
+       InvalidTimeFormatDHMS
+       InvalidTimeRange24Hr
+)
+
+// ErrorContext contains the error codes mentioned above.
+// Any error made must have one of those error codes.
+var ErrorContext *test.ErrorContext
+
+func init() {
+       iterableErrorCodes := []uint{
+               BadAssignmentMatch,
+               NotEnoughAssignments,
+               ExcessLabel,
+               InvalidLabel,
+               MissingLabel,
+               InvalidAction,
+               InvalidBool,
+               InvalidCacheCookieResponse,
+               InvalidHTTPScheme,
+               InvalidHost,
+               InvalidIP,
+               UnknownMethod,
+               InvalidPort,
+               InvalidRegex,
+               InvalidTimeFormatDHMS,
+               InvalidTimeRange24Hr,
+       }
+
+       ErrorContext = test.NewErrorContext("cache config", iterableErrorCodes)
+
+       ErrorContext.SetDefaultMessageForCode(InvalidLabel,
+               "invalid label")
+       ErrorContext.SetDefaultMessageForCode(InvalidAction,
+               "invalid action")
+       ErrorContext.SetDefaultMessageForCode(NotEnoughAssignments,
+               "not enough assignments in rule")
+       ErrorContext.SetDefaultMessageForCode(InvalidHTTPScheme,
+               "invalid scheme (must be either http or https)")
+       ErrorContext.SetDefaultMessageForCode(InvalidBool,
+               "label must have a value of 'true' or 'false'")
+       ErrorContext.SetDefaultMessageForCode(InvalidCacheCookieResponse,
+               "Value for cache-responses-to-cookies must be an integer in the 
range 0..4")
+
+       ErrorContext.TurnPanicOn()
+}
diff --git a/traffic_ops/testing/api/v14/config/table_test_structs.go 
b/traffic_ops/testing/api/v14/config/table_test_structs.go
new file mode 100644
index 0000000..8ea3a0a
--- /dev/null
+++ b/traffic_ops/testing/api/v14/config/table_test_structs.go
@@ -0,0 +1,28 @@
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package config
+
+// NegativeTest is a struct for table tests.
+type NegativeTest struct {
+       Description string
+       Config      string
+       Expected    int
+}
+
+// PositiveTest is a struct for table tests.
+type PositiveTest struct {
+       Description string
+       Config      string
+}

Reply via email to