github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/helper/validation/validation_test.go (about)

     1  package validation
     2  
     3  import (
     4  	"regexp"
     5  	"testing"
     6  
     7  	"github.com/hashicorp/terraform/helper/schema"
     8  )
     9  
    10  type testCase struct {
    11  	val         interface{}
    12  	f           schema.SchemaValidateFunc
    13  	expectedErr *regexp.Regexp
    14  }
    15  
    16  func TestValidationAll(t *testing.T) {
    17  	runTestCases(t, []testCase{
    18  		{
    19  			val: "valid",
    20  			f: All(
    21  				StringLenBetween(5, 42),
    22  				StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"),
    23  			),
    24  		},
    25  		{
    26  			val: "foo",
    27  			f: All(
    28  				StringLenBetween(5, 42),
    29  				StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"),
    30  			),
    31  			expectedErr: regexp.MustCompile("expected length of [\\w]+ to be in the range \\(5 - 42\\), got foo"),
    32  		},
    33  		{
    34  			val: "!!!!!",
    35  			f: All(
    36  				StringLenBetween(5, 42),
    37  				StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"),
    38  			),
    39  			expectedErr: regexp.MustCompile("value must be alphanumeric"),
    40  		},
    41  	})
    42  }
    43  
    44  func TestValidationAny(t *testing.T) {
    45  	runTestCases(t, []testCase{
    46  		{
    47  			val: 43,
    48  			f: Any(
    49  				IntAtLeast(42),
    50  				IntAtMost(5),
    51  			),
    52  		},
    53  		{
    54  			val: 4,
    55  			f: Any(
    56  				IntAtLeast(42),
    57  				IntAtMost(5),
    58  			),
    59  		},
    60  		{
    61  			val: 7,
    62  			f: Any(
    63  				IntAtLeast(42),
    64  				IntAtMost(5),
    65  			),
    66  			expectedErr: regexp.MustCompile("expected [\\w]+ to be at least \\(42\\), got 7"),
    67  		},
    68  		{
    69  			val: 7,
    70  			f: Any(
    71  				IntAtLeast(42),
    72  				IntAtMost(5),
    73  			),
    74  			expectedErr: regexp.MustCompile("expected [\\w]+ to be at most \\(5\\), got 7"),
    75  		},
    76  	})
    77  }
    78  
    79  func TestValidationIntBetween(t *testing.T) {
    80  	runTestCases(t, []testCase{
    81  		{
    82  			val: 1,
    83  			f:   IntBetween(1, 1),
    84  		},
    85  		{
    86  			val: 1,
    87  			f:   IntBetween(0, 2),
    88  		},
    89  		{
    90  			val:         1,
    91  			f:           IntBetween(2, 3),
    92  			expectedErr: regexp.MustCompile("expected [\\w]+ to be in the range \\(2 - 3\\), got 1"),
    93  		},
    94  		{
    95  			val:         "1",
    96  			f:           IntBetween(2, 3),
    97  			expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"),
    98  		},
    99  	})
   100  }
   101  
   102  func TestValidationIntAtLeast(t *testing.T) {
   103  	runTestCases(t, []testCase{
   104  		{
   105  			val: 1,
   106  			f:   IntAtLeast(1),
   107  		},
   108  		{
   109  			val: 1,
   110  			f:   IntAtLeast(0),
   111  		},
   112  		{
   113  			val:         1,
   114  			f:           IntAtLeast(2),
   115  			expectedErr: regexp.MustCompile("expected [\\w]+ to be at least \\(2\\), got 1"),
   116  		},
   117  		{
   118  			val:         "1",
   119  			f:           IntAtLeast(2),
   120  			expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"),
   121  		},
   122  	})
   123  }
   124  
   125  func TestValidationIntAtMost(t *testing.T) {
   126  	runTestCases(t, []testCase{
   127  		{
   128  			val: 1,
   129  			f:   IntAtMost(1),
   130  		},
   131  		{
   132  			val: 1,
   133  			f:   IntAtMost(2),
   134  		},
   135  		{
   136  			val:         1,
   137  			f:           IntAtMost(0),
   138  			expectedErr: regexp.MustCompile("expected [\\w]+ to be at most \\(0\\), got 1"),
   139  		},
   140  		{
   141  			val:         "1",
   142  			f:           IntAtMost(0),
   143  			expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"),
   144  		},
   145  	})
   146  }
   147  
   148  func TestValidationIntInSlice(t *testing.T) {
   149  	runTestCases(t, []testCase{
   150  		{
   151  			val: 42,
   152  			f:   IntInSlice([]int{1, 42}),
   153  		},
   154  		{
   155  			val:         42,
   156  			f:           IntInSlice([]int{10, 20}),
   157  			expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[10 20\\], got 42"),
   158  		},
   159  		{
   160  			val:         "InvalidValue",
   161  			f:           IntInSlice([]int{10, 20}),
   162  			expectedErr: regexp.MustCompile("expected type of [\\w]+ to be an integer"),
   163  		},
   164  	})
   165  }
   166  
   167  func TestValidationStringInSlice(t *testing.T) {
   168  	runTestCases(t, []testCase{
   169  		{
   170  			val: "ValidValue",
   171  			f:   StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false),
   172  		},
   173  		// ignore case
   174  		{
   175  			val: "VALIDVALUE",
   176  			f:   StringInSlice([]string{"ValidValue", "AnotherValidValue"}, true),
   177  		},
   178  		{
   179  			val:         "VALIDVALUE",
   180  			f:           StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false),
   181  			expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[ValidValue AnotherValidValue\\], got VALIDVALUE"),
   182  		},
   183  		{
   184  			val:         "InvalidValue",
   185  			f:           StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false),
   186  			expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[ValidValue AnotherValidValue\\], got InvalidValue"),
   187  		},
   188  		{
   189  			val:         1,
   190  			f:           StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false),
   191  			expectedErr: regexp.MustCompile("expected type of [\\w]+ to be string"),
   192  		},
   193  	})
   194  }
   195  
   196  func TestValidationStringMatch(t *testing.T) {
   197  	runTestCases(t, []testCase{
   198  		{
   199  			val: "foobar",
   200  			f:   StringMatch(regexp.MustCompile(".*foo.*"), ""),
   201  		},
   202  		{
   203  			val:         "bar",
   204  			f:           StringMatch(regexp.MustCompile(".*foo.*"), ""),
   205  			expectedErr: regexp.MustCompile("expected value of [\\w]+ to match regular expression " + regexp.QuoteMeta(`".*foo.*"`)),
   206  		},
   207  		{
   208  			val:         "bar",
   209  			f:           StringMatch(regexp.MustCompile(".*foo.*"), "value must contain foo"),
   210  			expectedErr: regexp.MustCompile("invalid value for [\\w]+ \\(value must contain foo\\)"),
   211  		},
   212  	})
   213  }
   214  
   215  func TestValidationRegexp(t *testing.T) {
   216  	runTestCases(t, []testCase{
   217  		{
   218  			val: ".*foo.*",
   219  			f:   ValidateRegexp,
   220  		},
   221  		{
   222  			val:         "foo(bar",
   223  			f:           ValidateRegexp,
   224  			expectedErr: regexp.MustCompile(regexp.QuoteMeta("error parsing regexp: missing closing ): `foo(bar`")),
   225  		},
   226  	})
   227  }
   228  
   229  func TestValidationSingleIP(t *testing.T) {
   230  	runTestCases(t, []testCase{
   231  		{
   232  			val: "172.10.10.10",
   233  			f:   SingleIP(),
   234  		},
   235  		{
   236  			val:         "1.1.1",
   237  			f:           SingleIP(),
   238  			expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")),
   239  		},
   240  		{
   241  			val:         "1.1.1.0/20",
   242  			f:           SingleIP(),
   243  			expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")),
   244  		},
   245  		{
   246  			val:         "256.1.1.1",
   247  			f:           SingleIP(),
   248  			expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")),
   249  		},
   250  	})
   251  }
   252  
   253  func TestValidationIPRange(t *testing.T) {
   254  	runTestCases(t, []testCase{
   255  		{
   256  			val: "172.10.10.10-172.10.10.12",
   257  			f:   IPRange(),
   258  		},
   259  		{
   260  			val:         "172.10.10.20",
   261  			f:           IPRange(),
   262  			expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP range, got:")),
   263  		},
   264  		{
   265  			val:         "172.10.10.20-172.10.10.12",
   266  			f:           IPRange(),
   267  			expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP range, got:")),
   268  		},
   269  	})
   270  }
   271  
   272  func TestValidateRFC3339TimeString(t *testing.T) {
   273  	runTestCases(t, []testCase{
   274  		{
   275  			val: "2018-03-01T00:00:00Z",
   276  			f:   ValidateRFC3339TimeString,
   277  		},
   278  		{
   279  			val: "2018-03-01T00:00:00-05:00",
   280  			f:   ValidateRFC3339TimeString,
   281  		},
   282  		{
   283  			val: "2018-03-01T00:00:00+05:00",
   284  			f:   ValidateRFC3339TimeString,
   285  		},
   286  		{
   287  			val:         "03/01/2018",
   288  			f:           ValidateRFC3339TimeString,
   289  			expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
   290  		},
   291  		{
   292  			val:         "03-01-2018",
   293  			f:           ValidateRFC3339TimeString,
   294  			expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
   295  		},
   296  		{
   297  			val:         "2018-03-01",
   298  			f:           ValidateRFC3339TimeString,
   299  			expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
   300  		},
   301  		{
   302  			val:         "2018-03-01T",
   303  			f:           ValidateRFC3339TimeString,
   304  			expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
   305  		},
   306  		{
   307  			val:         "2018-03-01T00:00:00",
   308  			f:           ValidateRFC3339TimeString,
   309  			expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
   310  		},
   311  		{
   312  			val:         "2018-03-01T00:00:00Z05:00",
   313  			f:           ValidateRFC3339TimeString,
   314  			expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
   315  		},
   316  		{
   317  			val:         "2018-03-01T00:00:00Z-05:00",
   318  			f:           ValidateRFC3339TimeString,
   319  			expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
   320  		},
   321  	})
   322  }
   323  
   324  func TestValidateJsonString(t *testing.T) {
   325  	type testCases struct {
   326  		Value    string
   327  		ErrCount int
   328  	}
   329  
   330  	invalidCases := []testCases{
   331  		{
   332  			Value:    `{0:"1"}`,
   333  			ErrCount: 1,
   334  		},
   335  		{
   336  			Value:    `{'abc':1}`,
   337  			ErrCount: 1,
   338  		},
   339  		{
   340  			Value:    `{"def":}`,
   341  			ErrCount: 1,
   342  		},
   343  		{
   344  			Value:    `{"xyz":[}}`,
   345  			ErrCount: 1,
   346  		},
   347  	}
   348  
   349  	for _, tc := range invalidCases {
   350  		_, errors := ValidateJsonString(tc.Value, "json")
   351  		if len(errors) != tc.ErrCount {
   352  			t.Fatalf("Expected %q to trigger a validation error.", tc.Value)
   353  		}
   354  	}
   355  
   356  	validCases := []testCases{
   357  		{
   358  			Value:    ``,
   359  			ErrCount: 0,
   360  		},
   361  		{
   362  			Value:    `{}`,
   363  			ErrCount: 0,
   364  		},
   365  		{
   366  			Value:    `{"abc":["1","2"]}`,
   367  			ErrCount: 0,
   368  		},
   369  	}
   370  
   371  	for _, tc := range validCases {
   372  		_, errors := ValidateJsonString(tc.Value, "json")
   373  		if len(errors) != tc.ErrCount {
   374  			t.Fatalf("Expected %q not to trigger a validation error.", tc.Value)
   375  		}
   376  	}
   377  }
   378  
   379  func TestValidateListUniqueStrings(t *testing.T) {
   380  	runTestCases(t, []testCase{
   381  		{
   382  			val: []interface{}{"foo", "bar"},
   383  			f:   ValidateListUniqueStrings,
   384  		},
   385  		{
   386  			val:         []interface{}{"foo", "bar", "foo"},
   387  			f:           ValidateListUniqueStrings,
   388  			expectedErr: regexp.MustCompile("duplicate entry - foo"),
   389  		},
   390  		{
   391  			val:         []interface{}{"foo", "bar", "foo", "baz", "bar"},
   392  			f:           ValidateListUniqueStrings,
   393  			expectedErr: regexp.MustCompile("duplicate entry - (?:foo|bar)"),
   394  		},
   395  	})
   396  }
   397  
   398  func TestValidationNoZeroValues(t *testing.T) {
   399  	runTestCases(t, []testCase{
   400  		{
   401  			val: "foo",
   402  			f:   NoZeroValues,
   403  		},
   404  		{
   405  			val: 1,
   406  			f:   NoZeroValues,
   407  		},
   408  		{
   409  			val: float64(1),
   410  			f:   NoZeroValues,
   411  		},
   412  		{
   413  			val:         "",
   414  			f:           NoZeroValues,
   415  			expectedErr: regexp.MustCompile("must not be empty"),
   416  		},
   417  		{
   418  			val:         0,
   419  			f:           NoZeroValues,
   420  			expectedErr: regexp.MustCompile("must not be zero"),
   421  		},
   422  		{
   423  			val:         float64(0),
   424  			f:           NoZeroValues,
   425  			expectedErr: regexp.MustCompile("must not be zero"),
   426  		},
   427  	})
   428  }
   429  
   430  func runTestCases(t *testing.T, cases []testCase) {
   431  	matchErr := func(errs []error, r *regexp.Regexp) bool {
   432  		// err must match one provided
   433  		for _, err := range errs {
   434  			if r.MatchString(err.Error()) {
   435  				return true
   436  			}
   437  		}
   438  
   439  		return false
   440  	}
   441  
   442  	for i, tc := range cases {
   443  		_, errs := tc.f(tc.val, "test_property")
   444  
   445  		if len(errs) == 0 && tc.expectedErr == nil {
   446  			continue
   447  		}
   448  
   449  		if len(errs) != 0 && tc.expectedErr == nil {
   450  			t.Fatalf("expected test case %d to produce no errors, got %v", i, errs)
   451  		}
   452  
   453  		if !matchErr(errs, tc.expectedErr) {
   454  			t.Fatalf("expected test case %d to produce error matching \"%s\", got %v", i, tc.expectedErr, errs)
   455  		}
   456  	}
   457  }
   458  
   459  func TestFloatBetween(t *testing.T) {
   460  	cases := map[string]struct {
   461  		Value                  interface{}
   462  		ValidateFunc           schema.SchemaValidateFunc
   463  		ExpectValidationErrors bool
   464  	}{
   465  		"accept valid value": {
   466  			Value:                  1.5,
   467  			ValidateFunc:           FloatBetween(1.0, 2.0),
   468  			ExpectValidationErrors: false,
   469  		},
   470  		"accept valid value inclusive upper bound": {
   471  			Value:                  1.0,
   472  			ValidateFunc:           FloatBetween(0.0, 1.0),
   473  			ExpectValidationErrors: false,
   474  		},
   475  		"accept valid value inclusive lower bound": {
   476  			Value:                  0.0,
   477  			ValidateFunc:           FloatBetween(0.0, 1.0),
   478  			ExpectValidationErrors: false,
   479  		},
   480  		"reject out of range value": {
   481  			Value:                  -1.0,
   482  			ValidateFunc:           FloatBetween(0.0, 1.0),
   483  			ExpectValidationErrors: true,
   484  		},
   485  		"reject incorrectly typed value": {
   486  			Value:                  1,
   487  			ValidateFunc:           FloatBetween(0.0, 1.0),
   488  			ExpectValidationErrors: true,
   489  		},
   490  	}
   491  
   492  	for tn, tc := range cases {
   493  		_, errors := tc.ValidateFunc(tc.Value, tn)
   494  		if len(errors) > 0 && !tc.ExpectValidationErrors {
   495  			t.Errorf("%s: unexpected errors %s", tn, errors)
   496  		} else if len(errors) == 0 && tc.ExpectValidationErrors {
   497  			t.Errorf("%s: expected errors but got none", tn)
   498  		}
   499  	}
   500  }