blob: 3bccabb0499400e493b2155322e0e1128a1caf10 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package verify
import (
"fmt"
"regexp"
"strings"
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
func TestValidateRFC1918Network(t *testing.T) {
x := []RFC1918NetworkTestCase{
// No errors
{TestName: "valid 10.x", CIDR: "10.0.0.0/8", MinPrefix: 0, MaxPrefix: 32},
{TestName: "valid 172.x", CIDR: "172.16.0.0/16", MinPrefix: 0, MaxPrefix: 32},
{TestName: "valid 192.x", CIDR: "192.168.0.0/32", MinPrefix: 0, MaxPrefix: 32},
{TestName: "valid, bounded 10.x CIDR", CIDR: "10.0.0.0/8", MinPrefix: 8, MaxPrefix: 32},
{TestName: "valid, bounded 172.x CIDR", CIDR: "172.16.0.0/16", MinPrefix: 12, MaxPrefix: 32},
{TestName: "valid, bounded 192.x CIDR", CIDR: "192.168.0.0/32", MinPrefix: 16, MaxPrefix: 32},
// With errors
{TestName: "empty CIDR", CIDR: "", MinPrefix: 0, MaxPrefix: 32, ExpectError: true},
{TestName: "missing mask", CIDR: "10.0.0.0", MinPrefix: 0, MaxPrefix: 32, ExpectError: true},
{TestName: "invalid CIDR", CIDR: "10.1.0.0/8", MinPrefix: 0, MaxPrefix: 32, ExpectError: true},
{TestName: "valid 10.x CIDR with lower bound violation", CIDR: "10.0.0.0/8", MinPrefix: 16, MaxPrefix: 32, ExpectError: true},
{TestName: "valid 10.x CIDR with upper bound violation", CIDR: "10.0.0.0/24", MinPrefix: 8, MaxPrefix: 16, ExpectError: true},
{TestName: "valid public CIDR", CIDR: "8.8.8.8/32", MinPrefix: 0, MaxPrefix: 32, ExpectError: true},
}
es := testRFC1918Networks(x)
if len(es) > 0 {
t.Errorf("Failed to validate RFC1918 Networks: %v", es)
}
}
type RFC1918NetworkTestCase struct {
TestName string
CIDR string
MinPrefix int
MaxPrefix int
ExpectError bool
}
func testRFC1918Networks(cases []RFC1918NetworkTestCase) []error {
es := make([]error, 0)
for _, c := range cases {
es = append(es, testRFC1918Network(c)...)
}
return es
}
func testRFC1918Network(testCase RFC1918NetworkTestCase) []error {
f := ValidateRFC1918Network(testCase.MinPrefix, testCase.MaxPrefix)
_, es := f(testCase.CIDR, testCase.TestName)
if testCase.ExpectError {
if len(es) > 0 {
return nil
}
return []error{fmt.Errorf("Didn't see expected error in case \"%s\" with CIDR=\"%s\" MinPrefix=%v MaxPrefix=%v",
testCase.TestName, testCase.CIDR, testCase.MinPrefix, testCase.MaxPrefix)}
}
return es
}
func TestProjectRegex(t *testing.T) {
tests := []struct {
project string
want bool
}{
{"example", true},
{"example.com:example", true},
{"12345", true},
{"", false},
{"example_", false},
}
r := regexp.MustCompile("^" + ProjectRegex + "$")
for _, test := range tests {
if got := r.MatchString(test.project); got != test.want {
t.Errorf("got %t, want %t for project %v", got, test.want, test.project)
}
}
}
func TestOrEmpty(t *testing.T) {
cases := map[string]struct {
Value string
ValidateFunc schema.SchemaValidateFunc
ExpectValidationErrors bool
}{
"accept empty value": {
Value: "",
ExpectValidationErrors: false,
},
"non empty value is accepted when valid": {
Value: "valid",
ExpectValidationErrors: false,
},
"non empty value is rejected if invalid": {
Value: "invalid",
ExpectValidationErrors: true,
},
}
for tn, tc := range cases {
validateFunc := OrEmpty(validation.StringInSlice([]string{"valid"}, false))
_, errors := 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)
}
}
}
func TestValidateGCEName(t *testing.T) {
x := []StringValidationTestCase{
// No errors
{TestName: "basic", Value: "foobar"},
{TestName: "with numbers", Value: "foobar123"},
{TestName: "short", Value: "f"},
{TestName: "long", Value: "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo"},
{TestName: "has a hyphen", Value: "foo-bar"},
// With errors
{TestName: "empty", Value: "", ExpectError: true},
{TestName: "starts with a capital", Value: "Foobar", ExpectError: true},
{TestName: "starts with a number", Value: "1foobar", ExpectError: true},
{TestName: "has an underscore", Value: "foo_bar", ExpectError: true},
{TestName: "too long", Value: "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoob", ExpectError: true},
}
es := TestStringValidationCases(x, ValidateGCEName)
if len(es) > 0 {
t.Errorf("Failed to validate GCP names: %v", es)
}
}
func TestValidateRFC3339Time(t *testing.T) {
cases := []StringValidationTestCase{
// No errors
{TestName: "midnight", Value: "00:00"},
{TestName: "one minute before midnight", Value: "23:59"},
// With errors
{TestName: "single-digit hour", Value: "3:00", ExpectError: true},
{TestName: "hour out of range", Value: "24:00", ExpectError: true},
{TestName: "minute out of range", Value: "03:60", ExpectError: true},
{TestName: "missing colon", Value: "0100", ExpectError: true},
{TestName: "not numbers", Value: "ab:cd", ExpectError: true},
}
es := TestStringValidationCases(cases, ValidateRFC3339Time)
if len(es) > 0 {
t.Errorf("Failed to validate RFC3339 times: %v", es)
}
}
func TestValidateRFC1035Name(t *testing.T) {
cases := []struct {
TestName string
Value string
Min, Max int
ExpectError bool
}{
{TestName: "valid", Min: 6, Max: 30, Value: "a-valid-name0"},
{TestName: "valid lower bound", Min: 12, Max: 30, Value: "a-valid-name"},
{TestName: "valid upper bound", Min: 6, Max: 12, Value: "a-valid-name"},
{TestName: "valid with numbers", Min: 6, Max: 30, Value: "valid000-name"},
{TestName: "must start with a letter", Min: 6, Max: 10, Value: "0invalid", ExpectError: true},
{TestName: "cannot end with a dash", Min: 6, Max: 10, Value: "invalid-", ExpectError: true},
{TestName: "too short", Min: 6, Max: 10, Value: "short", ExpectError: true},
{TestName: "too long", Min: 6, Max: 10, Value: "toolooooong", ExpectError: true},
{TestName: "min too small", Min: 1, Max: 10, Value: "", ExpectError: true},
{TestName: "min < max", Min: 6, Max: 5, Value: "", ExpectError: true},
}
for _, c := range cases {
errors := TestStringValidation(StringValidationTestCase{
TestName: c.TestName,
Value: c.Value,
ExpectError: c.ExpectError,
}, ValidateRFC1035Name(c.Min, c.Max))
if len(errors) > 0 {
t.Errorf("%s failed; %v", c.TestName, errors)
}
}
}
func TestValidateServiceAccountLink(t *testing.T) {
cases := []StringValidationTestCase{
// These test cases focus on the project name part of the regex
// The service account name is covered by the RFC1035Name tests above
// No errors
{TestName: "valid with dash", Value: "projects/my-project/serviceAccounts/svcacct@my-project.iam.gserviceaccount.com"},
{TestName: "valid with colon", Value: "projects/my:project/serviceAccounts/svcacct@project.my.iam.gserviceaccount.com"},
{TestName: "valid with dot and colon", Value: "projects/my.thing:project/serviceAccounts/svcacct@project.my.thing.iam.gserviceaccount.com"},
{TestName: "valid with compute default service account", Value: "projects/my-project/serviceAccounts/123456-compute@developer.gserviceaccount.com"},
{TestName: "valid with app engine default service account", Value: "projects/my-project/serviceAccounts/my-project@appspot.gserviceaccount.com"},
// Errors
{TestName: "multiple colons", Value: "projects/my:project:thing/serviceAccounts/svcacct@thing.project.my.iam.gserviceaccount.com", ExpectError: true},
{TestName: "project name empty", Value: "projects//serviceAccounts/svcacct@.iam.gserviceaccount.com", ExpectError: true},
{TestName: "dot only with no colon", Value: "projects/my.project/serviceAccounts/svcacct@my.project.iam.gserviceaccount.com", ExpectError: true},
{
TestName: "too long",
Value: "projects/foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoob/serviceAccounts/svcacct@" +
"foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoob.iam.gserviceaccount.com",
ExpectError: true,
},
}
es := TestStringValidationCases(cases, ValidateRegexp(ServiceAccountLinkRegex))
if len(es) > 0 {
t.Errorf("Failed to validate Service Account Links: %v", es)
}
}
func TestValidateProjectID(t *testing.T) {
x := []StringValidationTestCase{
// No errors
{TestName: "basic", Value: "foobar"},
{TestName: "with numbers", Value: "foobar123"},
{TestName: "short", Value: "foofoo"},
{TestName: "long", Value: "foobarfoobarfoobarfoobarfoobar"},
{TestName: "has a hyphen", Value: "foo-bar"},
// With errors
{TestName: "empty", Value: "", ExpectError: true},
{TestName: "has an slash", Value: "foo/bar", ExpectError: true},
{TestName: "has an uppercase letter", Value: "foo-Bar", ExpectError: true},
{TestName: "has a final hyphen", Value: "foo-bar-", ExpectError: true},
}
es := TestStringValidationCases(x, ValidateProjectID())
if len(es) > 0 {
t.Errorf("Failed to validate project ID's: %v", es)
}
}
func TestValidateDSProjectID(t *testing.T) {
x := []StringValidationTestCase{
// No errors
{TestName: "basic", Value: "foobar"},
{TestName: "with numbers", Value: "foobar123"},
{TestName: "short", Value: "foofoo"},
{TestName: "long", Value: "foobarfoobarfoobarfoobarfoobar"},
{TestName: "has projects", Value: "projects/foo-bar"},
{TestName: "has multiple projects", Value: "projects/projects/foobar"},
{TestName: "has a hyphen", Value: "foo-bar"},
// With errors
{TestName: "empty", Value: "", ExpectError: true},
{TestName: "has an uppercase letter", Value: "foo-Bar", ExpectError: true},
{TestName: "has a final hyphen", Value: "foo-bar-", ExpectError: true},
}
es := TestStringValidationCases(x, ValidateDSProjectID())
if len(es) > 0 {
t.Errorf("Failed to validate project ID's: %v", es)
}
}
func TestValidateProjectName(t *testing.T) {
x := []StringValidationTestCase{
// No errors
{TestName: "basic", Value: "fooBar"},
{TestName: "complex", Value: "project! 'A-1234'"},
{TestName: "with numbers", Value: "foobar123"},
{TestName: "short", Value: "foof"},
{TestName: "long", Value: "foobarfoobarfoobarfoobarfoobar"},
{TestName: "has a hyphen", Value: "foo-bar"},
{TestName: "starts with a number", Value: "1foobar"},
{TestName: "has a final hyphen", Value: "foo-bar-"},
// With errors
{TestName: "empty", Value: "", ExpectError: true},
{TestName: "has an slash", Value: "foo/bar", ExpectError: true},
{TestName: "too long", Value: strings.Repeat("a", 31), ExpectError: true},
}
es := TestStringValidationCases(x, ValidateProjectName())
if len(es) > 0 {
t.Errorf("Failed to validate project ID's: %v", es)
}
}
func TestValidateIAMCustomRoleIDRegex(t *testing.T) {
x := []StringValidationTestCase{
// No errors
{TestName: "basic", Value: "foobar"},
{TestName: "with numbers", Value: "foobar123"},
{TestName: "with capitals", Value: "FooBar"},
{TestName: "short", Value: "foo"},
{TestName: "long", Value: strings.Repeat("f", 64)},
{TestName: "has a dot", Value: "foo.bar"},
{TestName: "has an underscore", Value: "foo_bar"},
{TestName: "all of the above", Value: "foo.BarBaz_123"},
// With errors
{TestName: "empty", Value: "", ExpectError: true},
{TestName: "has an slash", Value: "foo/bar", ExpectError: true},
{TestName: "has a hyphen", Value: "foo-bar", ExpectError: true},
{TestName: "has a dollar", Value: "foo$", ExpectError: true},
{TestName: "has a space", Value: "foo bar", ExpectError: true},
{TestName: "too short", Value: "fo", ExpectError: true},
{TestName: "too long", Value: strings.Repeat("f", 65), ExpectError: true},
}
es := TestStringValidationCases(x, ValidateIAMCustomRoleID)
if len(es) > 0 {
t.Errorf("Failed to validate IAMCustomRole IDs: %v", es)
}
}