terraform/helper/validation/validation_test.go

501 lines
12 KiB
Go

package validation
import (
"regexp"
"testing"
"github.com/hashicorp/terraform/helper/schema"
)
type testCase struct {
val interface{}
f schema.SchemaValidateFunc
expectedErr *regexp.Regexp
}
func TestValidationAll(t *testing.T) {
runTestCases(t, []testCase{
{
val: "valid",
f: All(
StringLenBetween(5, 42),
StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"),
),
},
{
val: "foo",
f: All(
StringLenBetween(5, 42),
StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"),
),
expectedErr: regexp.MustCompile("expected length of [\\w]+ to be in the range \\(5 - 42\\), got foo"),
},
{
val: "!!!!!",
f: All(
StringLenBetween(5, 42),
StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"),
),
expectedErr: regexp.MustCompile("value must be alphanumeric"),
},
})
}
func TestValidationAny(t *testing.T) {
runTestCases(t, []testCase{
{
val: 43,
f: Any(
IntAtLeast(42),
IntAtMost(5),
),
},
{
val: 4,
f: Any(
IntAtLeast(42),
IntAtMost(5),
),
},
{
val: 7,
f: Any(
IntAtLeast(42),
IntAtMost(5),
),
expectedErr: regexp.MustCompile("expected [\\w]+ to be at least \\(42\\), got 7"),
},
{
val: 7,
f: Any(
IntAtLeast(42),
IntAtMost(5),
),
expectedErr: regexp.MustCompile("expected [\\w]+ to be at most \\(5\\), got 7"),
},
})
}
func TestValidationIntBetween(t *testing.T) {
runTestCases(t, []testCase{
{
val: 1,
f: IntBetween(1, 1),
},
{
val: 1,
f: IntBetween(0, 2),
},
{
val: 1,
f: IntBetween(2, 3),
expectedErr: regexp.MustCompile("expected [\\w]+ to be in the range \\(2 - 3\\), got 1"),
},
{
val: "1",
f: IntBetween(2, 3),
expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"),
},
})
}
func TestValidationIntAtLeast(t *testing.T) {
runTestCases(t, []testCase{
{
val: 1,
f: IntAtLeast(1),
},
{
val: 1,
f: IntAtLeast(0),
},
{
val: 1,
f: IntAtLeast(2),
expectedErr: regexp.MustCompile("expected [\\w]+ to be at least \\(2\\), got 1"),
},
{
val: "1",
f: IntAtLeast(2),
expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"),
},
})
}
func TestValidationIntAtMost(t *testing.T) {
runTestCases(t, []testCase{
{
val: 1,
f: IntAtMost(1),
},
{
val: 1,
f: IntAtMost(2),
},
{
val: 1,
f: IntAtMost(0),
expectedErr: regexp.MustCompile("expected [\\w]+ to be at most \\(0\\), got 1"),
},
{
val: "1",
f: IntAtMost(0),
expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"),
},
})
}
func TestValidationIntInSlice(t *testing.T) {
runTestCases(t, []testCase{
{
val: 42,
f: IntInSlice([]int{1, 42}),
},
{
val: 42,
f: IntInSlice([]int{10, 20}),
expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[10 20\\], got 42"),
},
{
val: "InvalidValue",
f: IntInSlice([]int{10, 20}),
expectedErr: regexp.MustCompile("expected type of [\\w]+ to be an integer"),
},
})
}
func TestValidationStringInSlice(t *testing.T) {
runTestCases(t, []testCase{
{
val: "ValidValue",
f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false),
},
// ignore case
{
val: "VALIDVALUE",
f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, true),
},
{
val: "VALIDVALUE",
f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false),
expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[ValidValue AnotherValidValue\\], got VALIDVALUE"),
},
{
val: "InvalidValue",
f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false),
expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[ValidValue AnotherValidValue\\], got InvalidValue"),
},
{
val: 1,
f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false),
expectedErr: regexp.MustCompile("expected type of [\\w]+ to be string"),
},
})
}
func TestValidationStringMatch(t *testing.T) {
runTestCases(t, []testCase{
{
val: "foobar",
f: StringMatch(regexp.MustCompile(".*foo.*"), ""),
},
{
val: "bar",
f: StringMatch(regexp.MustCompile(".*foo.*"), ""),
expectedErr: regexp.MustCompile("expected value of [\\w]+ to match regular expression " + regexp.QuoteMeta(`".*foo.*"`)),
},
{
val: "bar",
f: StringMatch(regexp.MustCompile(".*foo.*"), "value must contain foo"),
expectedErr: regexp.MustCompile("invalid value for [\\w]+ \\(value must contain foo\\)"),
},
})
}
func TestValidationRegexp(t *testing.T) {
runTestCases(t, []testCase{
{
val: ".*foo.*",
f: ValidateRegexp,
},
{
val: "foo(bar",
f: ValidateRegexp,
expectedErr: regexp.MustCompile(regexp.QuoteMeta("error parsing regexp: missing closing ): `foo(bar`")),
},
})
}
func TestValidationSingleIP(t *testing.T) {
runTestCases(t, []testCase{
{
val: "172.10.10.10",
f: SingleIP(),
},
{
val: "1.1.1",
f: SingleIP(),
expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")),
},
{
val: "1.1.1.0/20",
f: SingleIP(),
expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")),
},
{
val: "256.1.1.1",
f: SingleIP(),
expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")),
},
})
}
func TestValidationIPRange(t *testing.T) {
runTestCases(t, []testCase{
{
val: "172.10.10.10-172.10.10.12",
f: IPRange(),
},
{
val: "172.10.10.20",
f: IPRange(),
expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP range, got:")),
},
{
val: "172.10.10.20-172.10.10.12",
f: IPRange(),
expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP range, got:")),
},
})
}
func TestValidateRFC3339TimeString(t *testing.T) {
runTestCases(t, []testCase{
{
val: "2018-03-01T00:00:00Z",
f: ValidateRFC3339TimeString,
},
{
val: "2018-03-01T00:00:00-05:00",
f: ValidateRFC3339TimeString,
},
{
val: "2018-03-01T00:00:00+05:00",
f: ValidateRFC3339TimeString,
},
{
val: "03/01/2018",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "03-01-2018",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "2018-03-01",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "2018-03-01T",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "2018-03-01T00:00:00",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "2018-03-01T00:00:00Z05:00",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "2018-03-01T00:00:00Z-05:00",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
})
}
func TestValidateJsonString(t *testing.T) {
type testCases struct {
Value string
ErrCount int
}
invalidCases := []testCases{
{
Value: `{0:"1"}`,
ErrCount: 1,
},
{
Value: `{'abc':1}`,
ErrCount: 1,
},
{
Value: `{"def":}`,
ErrCount: 1,
},
{
Value: `{"xyz":[}}`,
ErrCount: 1,
},
}
for _, tc := range invalidCases {
_, errors := ValidateJsonString(tc.Value, "json")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected %q to trigger a validation error.", tc.Value)
}
}
validCases := []testCases{
{
Value: ``,
ErrCount: 0,
},
{
Value: `{}`,
ErrCount: 0,
},
{
Value: `{"abc":["1","2"]}`,
ErrCount: 0,
},
}
for _, tc := range validCases {
_, errors := ValidateJsonString(tc.Value, "json")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected %q not to trigger a validation error.", tc.Value)
}
}
}
func TestValidateListUniqueStrings(t *testing.T) {
runTestCases(t, []testCase{
{
val: []interface{}{"foo", "bar"},
f: ValidateListUniqueStrings,
},
{
val: []interface{}{"foo", "bar", "foo"},
f: ValidateListUniqueStrings,
expectedErr: regexp.MustCompile("duplicate entry - foo"),
},
{
val: []interface{}{"foo", "bar", "foo", "baz", "bar"},
f: ValidateListUniqueStrings,
expectedErr: regexp.MustCompile("duplicate entry - (?:foo|bar)"),
},
})
}
func TestValidationNoZeroValues(t *testing.T) {
runTestCases(t, []testCase{
{
val: "foo",
f: NoZeroValues,
},
{
val: 1,
f: NoZeroValues,
},
{
val: float64(1),
f: NoZeroValues,
},
{
val: "",
f: NoZeroValues,
expectedErr: regexp.MustCompile("must not be empty"),
},
{
val: 0,
f: NoZeroValues,
expectedErr: regexp.MustCompile("must not be zero"),
},
{
val: float64(0),
f: NoZeroValues,
expectedErr: regexp.MustCompile("must not be zero"),
},
})
}
func runTestCases(t *testing.T, cases []testCase) {
matchErr := func(errs []error, r *regexp.Regexp) bool {
// err must match one provided
for _, err := range errs {
if r.MatchString(err.Error()) {
return true
}
}
return false
}
for i, tc := range cases {
_, errs := tc.f(tc.val, "test_property")
if len(errs) == 0 && tc.expectedErr == nil {
continue
}
if len(errs) != 0 && tc.expectedErr == nil {
t.Fatalf("expected test case %d to produce no errors, got %v", i, errs)
}
if !matchErr(errs, tc.expectedErr) {
t.Fatalf("expected test case %d to produce error matching \"%s\", got %v", i, tc.expectedErr, errs)
}
}
}
func TestFloatBetween(t *testing.T) {
cases := map[string]struct {
Value interface{}
ValidateFunc schema.SchemaValidateFunc
ExpectValidationErrors bool
}{
"accept valid value": {
Value: 1.5,
ValidateFunc: FloatBetween(1.0, 2.0),
ExpectValidationErrors: false,
},
"accept valid value inclusive upper bound": {
Value: 1.0,
ValidateFunc: FloatBetween(0.0, 1.0),
ExpectValidationErrors: false,
},
"accept valid value inclusive lower bound": {
Value: 0.0,
ValidateFunc: FloatBetween(0.0, 1.0),
ExpectValidationErrors: false,
},
"reject out of range value": {
Value: -1.0,
ValidateFunc: FloatBetween(0.0, 1.0),
ExpectValidationErrors: true,
},
"reject incorrectly typed value": {
Value: 1,
ValidateFunc: FloatBetween(0.0, 1.0),
ExpectValidationErrors: true,
},
}
for tn, tc := range cases {
_, errors := tc.ValidateFunc(tc.Value, tn)
if len(errors) > 0 && !tc.ExpectValidationErrors {
t.Errorf("%s: unexpected errors %s", tn, errors)
} else if len(errors) == 0 && tc.ExpectValidationErrors {
t.Errorf("%s: expected errors but got none", tn)
}
}
}