Skip to content

Commit 9af5f68

Browse files
committed
test: add unit test to document validation behavior
1 parent f1a0fd5 commit 9af5f68

File tree

2 files changed

+207
-218
lines changed

2 files changed

+207
-218
lines changed

provider/parameter_test.go

Lines changed: 207 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package provider_test
33
import (
44
"fmt"
55
"regexp"
6+
"strconv"
67
"strings"
78
"testing"
89

@@ -686,180 +687,224 @@ data "coder_parameter" "region" {
686687
}
687688
}
688689

690+
// TestParameterValidationEnforcement tests various parameter states and the
691+
// validation enforcement that should be applied to them. The table is described
692+
// by a markdown table. This is done so that the test cases can be more easily
693+
// edited and read.
694+
//
695+
// Copy and paste the table to https://www.tablesgenerator.com/markdown_tables for easier editing
696+
//
689697
//nolint:paralleltest,tparallel // Parameters load values from env vars
690698
func TestParameterValidationEnforcement(t *testing.T) {
691-
for _, tc := range []struct {
699+
table := strings.TrimSpace(`
700+
| Name | Type | Input Value | Default | Options | Validation | -> | Output Value | Optional | Error |
701+
|---------------|---------------|-------------|---------|-------------------|------------|----|--------------|----------|--------------|
702+
| | Empty Vals | | | | | | | | |
703+
| Emty | string,number | | | | | | "" | false | |
704+
| EmtyOpts | string,number | | | 1,2,3 | | | "" | false | |
705+
| EmtyRegex | string | | | | world | | | | regex error |
706+
| EmtyMin | number | | | | 1-10 | | | | 1 < < 10 |
707+
| EmtyMinOpt | number | | | 1,2,3 | 2-5 | | | | 2 < < 5 |
708+
| EmtyRegexOpt | string | | | "hello","goodbye" | goodbye | | | | regex error |
709+
| EmtyRegexOk | string | | | | .* | | "" | false | |
710+
| | | | | | | | | | |
711+
| | Default Set | No inputs | | | | | | | |
712+
| NumDef | number | | 5 | | | | 5 | true | |
713+
| NumDefVal | number | | 5 | | 3-7 | | 5 | true | |
714+
| NumDefInv | number | | 5 | | 10- | | 5 | | 10 < 5 < 0 |
715+
| NumDefOpts | number | | 5 | 1,3,5,7 | 2-6 | | 5 | true | |
716+
| NumDefNotOpts | number | | 5 | 1,3,7,9 | 2-6 | | | | valid option |
717+
| | | | | | | | | | |
718+
| StrDef | string | | hello | | | | hello | true | |
719+
| StrDefInv | string | | hello | | world | | | | regex error |
720+
| StrDefOpts | string | | a | a,b,c | | | a | true | |
721+
| StrDefNotOpts | string | | a | b,c,d | | | | | valid option |
722+
| StrDefOpts | string | | a | a,b,c,d,e,f | [a-c] | | a | true | |
723+
| | | | | | | | | | |
724+
| | Input Vals | | | | | | | | |
725+
| NumIns | number | 3 | 5 | | | | 3 | true | |
726+
| | | | | | | | | | |
727+
| | | | | | | | | | |
728+
`)
729+
730+
type row struct {
692731
Name string
693-
Config string
694-
Value string
695-
ExpectError *regexp.Regexp
696-
Check func(state *terraform.ResourceState)
697-
}{
698-
// Empty
699-
{
700-
Name: "EmptyString",
701-
Config: `
702-
data "coder_parameter" "parameter" {
703-
name = "parameter"
704-
type = "string"
705-
}
706-
`,
707-
ExpectError: nil,
708-
Check: func(state *terraform.ResourceState) {
709-
attrs := state.Primary.Attributes
710-
for key, value := range map[string]interface{}{
711-
"default": "",
712-
"value": "",
713-
"optional": "false",
714-
} {
715-
require.Equal(t, value, attrs[key])
716-
}
717-
},
718-
},
719-
{
720-
Name: "EmptyNumber",
721-
Config: `
722-
data "coder_parameter" "parameter" {
723-
name = "parameter"
724-
type = "number"
732+
Types []string
733+
InputValue string
734+
Default string
735+
Options []string
736+
Validation *provider.Validation
737+
OutputValue string
738+
Optional bool
739+
Error *regexp.Regexp
740+
}
741+
742+
rows := make([]row, 0)
743+
lines := strings.Split(table, "\n")
744+
validMinMax := regexp.MustCompile("^[0-9]*-[0-9]*$")
745+
for _, line := range lines[2:] {
746+
columns := strings.Split(line, "|")
747+
columns = columns[1 : len(columns)-1]
748+
for i := range columns {
749+
// Trim the whitespace from all columns
750+
columns[i] = strings.TrimSpace(columns[i])
751+
}
752+
753+
if columns[0] == "" {
754+
continue // Skip rows with empty names
755+
}
756+
757+
optional, err := strconv.ParseBool(columns[8])
758+
if columns[8] != "" {
759+
// Value does not matter if not specified
760+
require.NoError(t, err)
761+
}
762+
763+
var rerr *regexp.Regexp
764+
if columns[9] != "" {
765+
rerr, err = regexp.Compile(columns[9])
766+
if err != nil {
767+
t.Fatalf("failed to parse error column %q: %v", columns[9], err)
725768
}
726-
`,
727-
ExpectError: nil,
728-
Check: func(state *terraform.ResourceState) {
729-
attrs := state.Primary.Attributes
730-
for key, value := range map[string]interface{}{
731-
"default": "",
732-
"value": "",
733-
"optional": "false",
734-
} {
735-
require.Equal(t, value, attrs[key])
736-
}
737-
},
738-
},
739-
// EmptyWithOption
740-
{
741-
Name: "EmptyWithOption",
742-
Config: `
743-
data "coder_parameter" "parameter" {
744-
name = "parameter"
745-
type = "number"
746-
747-
option {
748-
name = "option"
749-
value = "5"
769+
}
770+
var options []string
771+
if columns[4] != "" {
772+
options = strings.Split(columns[4], ",")
773+
}
774+
775+
var validation *provider.Validation
776+
if columns[5] != "" {
777+
// Min-Max validation should look like:
778+
// 1-10 :: min=1, max=10
779+
// -10 :: max=10
780+
// 1- :: min=1
781+
if validMinMax.MatchString(columns[5]) {
782+
parts := strings.Split(columns[5], "-")
783+
min, _ := strconv.ParseInt(parts[0], 10, 64)
784+
max, _ := strconv.ParseInt(parts[1], 10, 64)
785+
validation = &provider.Validation{
786+
Min: int(min),
787+
MinDisabled: parts[0] == "",
788+
Max: int(max),
789+
MaxDisabled: parts[1] == "",
790+
Monotonic: "",
791+
Regex: "",
792+
Error: "{min} < {value} < {max}",
750793
}
751-
}
752-
`,
753-
ExpectError: nil,
754-
Check: func(state *terraform.ResourceState) {
755-
attrs := state.Primary.Attributes
756-
for key, value := range map[string]interface{}{
757-
"default": "",
758-
"value": "",
759-
"optional": "false",
760-
} {
761-
require.Equal(t, value, attrs[key])
794+
} else {
795+
validation = &provider.Validation{
796+
Min: 0,
797+
MinDisabled: true,
798+
Max: 0,
799+
MaxDisabled: true,
800+
Monotonic: "",
801+
Regex: columns[5],
802+
Error: "regex error",
762803
}
763-
},
764-
},
765-
// DefaultSet
766-
{
767-
Name: "DefaultSet",
768-
Config: `
769-
data "coder_parameter" "parameter" {
770-
name = "parameter"
771-
type = "number"
772-
default = "5"
773804
}
774-
`,
775-
ExpectError: nil,
776-
Check: func(state *terraform.ResourceState) {
777-
attrs := state.Primary.Attributes
778-
for key, value := range map[string]interface{}{
779-
"default": "5",
780-
"value": "5",
781-
"optional": "true",
782-
} {
783-
require.Equal(t, value, attrs[key])
784-
}
785-
},
786-
},
787-
{
788-
Name: "DefaultSetInOption",
789-
Config: `
790-
data "coder_parameter" "parameter" {
791-
name = "parameter"
792-
type = "number"
793-
default = "5"
794-
option {
795-
name = "option"
796-
value = "5"
805+
}
806+
807+
rows = append(rows, row{
808+
Name: columns[0],
809+
Types: strings.Split(columns[1], ","),
810+
InputValue: columns[2],
811+
Default: columns[3],
812+
Options: options,
813+
Validation: validation,
814+
OutputValue: columns[7],
815+
Optional: optional,
816+
Error: rerr,
817+
})
818+
}
819+
820+
stringLiteral := func(s string) string {
821+
if s == "" {
822+
return `""`
823+
}
824+
return fmt.Sprintf("%q", s)
825+
}
826+
827+
for rowIndex, row := range rows {
828+
for _, rt := range row.Types {
829+
//nolint:paralleltest,tparallel // Parameters load values from env vars
830+
t.Run(fmt.Sprintf("%d|%s:%s", rowIndex, row.Name, rt), func(t *testing.T) {
831+
if row.InputValue != "" {
832+
t.Setenv(provider.ParameterEnvironmentVariable("parameter"), row.InputValue)
797833
}
798-
}
799-
`,
800-
ExpectError: nil,
801-
Check: func(state *terraform.ResourceState) {
802-
attrs := state.Primary.Attributes
803-
for key, value := range map[string]interface{}{
804-
"default": "5",
805-
"value": "5",
806-
"optional": "true",
807-
} {
808-
require.Equal(t, value, attrs[key])
834+
835+
var cfg strings.Builder
836+
cfg.WriteString("data \"coder_parameter\" \"parameter\" {\n")
837+
cfg.WriteString("\tname = \"parameter\"\n")
838+
cfg.WriteString(fmt.Sprintf("\ttype = \"%s\"\n", rt))
839+
if row.Default != "" {
840+
cfg.WriteString(fmt.Sprintf("\tdefault = %s\n", stringLiteral(row.Default)))
809841
}
810-
},
811-
},
812-
{
813-
Name: "DefaultSetOutOption",
814-
Config: `
815-
data "coder_parameter" "parameter" {
816-
name = "parameter"
817-
type = "number"
818-
default = "2"
819-
option {
820-
name = "option"
821-
value = "5"
842+
843+
for _, opt := range row.Options {
844+
cfg.WriteString("\toption {\n")
845+
cfg.WriteString(fmt.Sprintf("\t\tname = %s\n", stringLiteral(opt)))
846+
cfg.WriteString(fmt.Sprintf("\t\tvalue = %s\n", stringLiteral(opt)))
847+
cfg.WriteString("\t}\n")
822848
}
823-
}
824-
`,
825-
ExpectError: nil,
826-
Check: func(state *terraform.ResourceState) {
827-
attrs := state.Primary.Attributes
828-
for key, value := range map[string]interface{}{
829-
"default": "5",
830-
"value": "5",
831-
"optional": "true",
832-
} {
833-
require.Equal(t, value, attrs[key])
849+
850+
if row.Validation != nil {
851+
cfg.WriteString("\tvalidation {\n")
852+
if !row.Validation.MinDisabled {
853+
cfg.WriteString(fmt.Sprintf("\t\tmin = %d\n", row.Validation.Min))
854+
}
855+
if !row.Validation.MaxDisabled {
856+
cfg.WriteString(fmt.Sprintf("\t\tmax = %d\n", row.Validation.Max))
857+
}
858+
if row.Validation.Monotonic != "" {
859+
cfg.WriteString(fmt.Sprintf("\t\tmonotonic = \"%s\"\n", row.Validation.Monotonic))
860+
}
861+
if row.Validation.Regex != "" {
862+
cfg.WriteString(fmt.Sprintf("\t\tregex = %q\n", row.Validation.Regex))
863+
}
864+
cfg.WriteString(fmt.Sprintf("\t\terror = %q\n", row.Validation.Error))
865+
cfg.WriteString("\t}\n")
834866
}
835-
},
836-
},
837-
} {
838-
tc := tc
839-
//nolint:paralleltest,tparallel // Parameters load values from env vars
840-
t.Run(tc.Name, func(t *testing.T) {
841-
if tc.Value != "" {
842-
t.Setenv(provider.ParameterEnvironmentVariable("parameter"), tc.Value)
843-
}
844-
resource.Test(t, resource.TestCase{
845-
ProviderFactories: coderFactory(),
846-
IsUnitTest: true,
847-
Steps: []resource.TestStep{{
848-
Config: tc.Config,
849-
ExpectError: tc.ExpectError,
850-
Check: func(state *terraform.State) error {
851-
require.Len(t, state.Modules, 1)
852-
require.Len(t, state.Modules[0].Resources, 1)
853-
param := state.Modules[0].Resources["data.coder_parameter.parameter"]
854-
require.NotNil(t, param)
855-
if tc.Check != nil {
856-
tc.Check(param)
857-
}
858-
return nil
859-
},
860-
}},
867+
868+
cfg.WriteString("}\n")
869+
870+
resource.Test(t, resource.TestCase{
871+
ProviderFactories: coderFactory(),
872+
IsUnitTest: true,
873+
Steps: []resource.TestStep{{
874+
Config: cfg.String(),
875+
ExpectError: row.Error,
876+
Check: func(state *terraform.State) error {
877+
require.Len(t, state.Modules, 1)
878+
require.Len(t, state.Modules[0].Resources, 1)
879+
param := state.Modules[0].Resources["data.coder_parameter.parameter"]
880+
require.NotNil(t, param)
881+
882+
if row.Default == "" {
883+
_, ok := param.Primary.Attributes["default"]
884+
require.False(t, ok, "default should not be set")
885+
} else {
886+
require.Equal(t, strings.Trim(row.Default, `"`), param.Primary.Attributes["default"])
887+
}
888+
889+
if row.OutputValue == "" {
890+
_, ok := param.Primary.Attributes["value"]
891+
require.False(t, ok, "output value should not be set")
892+
} else {
893+
require.Equal(t, strings.Trim(row.OutputValue, `"`), param.Primary.Attributes["value"])
894+
}
895+
896+
for key, expected := range map[string]string{
897+
"optional": strconv.FormatBool(row.Optional),
898+
} {
899+
require.Equal(t, expected, param.Primary.Attributes[key])
900+
}
901+
902+
return nil
903+
},
904+
}},
905+
})
861906
})
862-
})
907+
}
863908
}
864909
}
865910

0 commit comments

Comments
 (0)