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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy