@@ -2,7 +2,9 @@ package provider_test
2
2
3
3
import (
4
4
"fmt"
5
+ "os"
5
6
"regexp"
7
+ "strconv"
6
8
"strings"
7
9
"testing"
8
10
@@ -686,6 +688,217 @@ data "coder_parameter" "region" {
686
688
}
687
689
}
688
690
691
+ // TestParameterValidationEnforcement tests various parameter states and the
692
+ // validation enforcement that should be applied to them. The table is described
693
+ // by a markdown table. This is done so that the test cases can be more easily
694
+ // edited and read.
695
+ //
696
+ // Copy and paste the table to https://www.tablesgenerator.com/markdown_tables for easier editing
697
+ //
698
+ //nolint:paralleltest,tparallel // Parameters load values from env vars
699
+ func TestParameterValidationEnforcement (t * testing.T ) {
700
+ // Some interesting observations:
701
+ // - Validation logic does not apply to the value of 'options'
702
+ // - [NumDefInvOpt] So an invalid option can be present and selected, but would fail
703
+ // - Validation logic does not apply to the default if a value is given
704
+ // - [NumIns/DefInv] So the default can be invalid if an input value is valid.
705
+ // The value is therefore not really optional, but it is marked as such.
706
+ // - [NumInsNotOptsVal | NumsInsNotOpts] values do not need to be in the option set?
707
+ table , err := os .ReadFile ("testdata/parameter_table.md" )
708
+ require .NoError (t , err )
709
+
710
+ type row struct {
711
+ Name string
712
+ Types []string
713
+ InputValue string
714
+ Default string
715
+ Options []string
716
+ Validation * provider.Validation
717
+ OutputValue string
718
+ Optional bool
719
+ Error * regexp.Regexp
720
+ }
721
+
722
+ rows := make ([]row , 0 )
723
+ lines := strings .Split (string (table ), "\n " )
724
+ validMinMax := regexp .MustCompile ("^[0-9]*-[0-9]*$" )
725
+ for _ , line := range lines [2 :] {
726
+ columns := strings .Split (line , "|" )
727
+ columns = columns [1 : len (columns )- 1 ]
728
+ for i := range columns {
729
+ // Trim the whitespace from all columns
730
+ columns [i ] = strings .TrimSpace (columns [i ])
731
+ }
732
+
733
+ if columns [0 ] == "" {
734
+ continue // Skip rows with empty names
735
+ }
736
+
737
+ optional , err := strconv .ParseBool (columns [8 ])
738
+ if columns [8 ] != "" {
739
+ // Value does not matter if not specified
740
+ require .NoError (t , err )
741
+ }
742
+
743
+ var rerr * regexp.Regexp
744
+ if columns [9 ] != "" {
745
+ rerr , err = regexp .Compile (columns [9 ])
746
+ if err != nil {
747
+ t .Fatalf ("failed to parse error column %q: %v" , columns [9 ], err )
748
+ }
749
+ }
750
+ var options []string
751
+ if columns [4 ] != "" {
752
+ options = strings .Split (columns [4 ], "," )
753
+ }
754
+
755
+ var validation * provider.Validation
756
+ if columns [5 ] != "" {
757
+ // Min-Max validation should look like:
758
+ // 1-10 :: min=1, max=10
759
+ // -10 :: max=10
760
+ // 1- :: min=1
761
+ if validMinMax .MatchString (columns [5 ]) {
762
+ parts := strings .Split (columns [5 ], "-" )
763
+ min , _ := strconv .ParseInt (parts [0 ], 10 , 64 )
764
+ max , _ := strconv .ParseInt (parts [1 ], 10 , 64 )
765
+ validation = & provider.Validation {
766
+ Min : int (min ),
767
+ MinDisabled : parts [0 ] == "" ,
768
+ Max : int (max ),
769
+ MaxDisabled : parts [1 ] == "" ,
770
+ Monotonic : "" ,
771
+ Regex : "" ,
772
+ Error : "{min} < {value} < {max}" ,
773
+ }
774
+ } else {
775
+ validation = & provider.Validation {
776
+ Min : 0 ,
777
+ MinDisabled : true ,
778
+ Max : 0 ,
779
+ MaxDisabled : true ,
780
+ Monotonic : "" ,
781
+ Regex : columns [5 ],
782
+ Error : "regex error" ,
783
+ }
784
+ }
785
+ }
786
+
787
+ rows = append (rows , row {
788
+ Name : columns [0 ],
789
+ Types : strings .Split (columns [1 ], "," ),
790
+ InputValue : columns [2 ],
791
+ Default : columns [3 ],
792
+ Options : options ,
793
+ Validation : validation ,
794
+ OutputValue : columns [7 ],
795
+ Optional : optional ,
796
+ Error : rerr ,
797
+ })
798
+ }
799
+
800
+ stringLiteral := func (s string ) string {
801
+ if s == "" {
802
+ return `""`
803
+ }
804
+ return fmt .Sprintf ("%q" , s )
805
+ }
806
+
807
+ for rowIndex , row := range rows {
808
+ for _ , rt := range row .Types {
809
+ //nolint:paralleltest,tparallel // Parameters load values from env vars
810
+ t .Run (fmt .Sprintf ("%d|%s:%s" , rowIndex , row .Name , rt ), func (t * testing.T ) {
811
+ if row .InputValue != "" {
812
+ t .Setenv (provider .ParameterEnvironmentVariable ("parameter" ), row .InputValue )
813
+ }
814
+
815
+ if row .Error != nil {
816
+ if row .OutputValue != "" {
817
+ t .Errorf ("output value %q should not be set if error is set" , row .OutputValue )
818
+ }
819
+ }
820
+
821
+ var cfg strings.Builder
822
+ cfg .WriteString ("data \" coder_parameter\" \" parameter\" {\n " )
823
+ cfg .WriteString ("\t name = \" parameter\" \n " )
824
+ if rt == "multi-select" || rt == "tag-select" {
825
+ cfg .WriteString (fmt .Sprintf ("\t type = \" %s\" \n " , "list(string)" ))
826
+ cfg .WriteString (fmt .Sprintf ("\t form_type = \" %s\" \n " , rt ))
827
+ } else {
828
+ cfg .WriteString (fmt .Sprintf ("\t type = \" %s\" \n " , rt ))
829
+ }
830
+ if row .Default != "" {
831
+ cfg .WriteString (fmt .Sprintf ("\t default = %s\n " , stringLiteral (row .Default )))
832
+ }
833
+
834
+ for _ , opt := range row .Options {
835
+ cfg .WriteString ("\t option {\n " )
836
+ cfg .WriteString (fmt .Sprintf ("\t \t name = %s\n " , stringLiteral (opt )))
837
+ cfg .WriteString (fmt .Sprintf ("\t \t value = %s\n " , stringLiteral (opt )))
838
+ cfg .WriteString ("\t }\n " )
839
+ }
840
+
841
+ if row .Validation != nil {
842
+ cfg .WriteString ("\t validation {\n " )
843
+ if ! row .Validation .MinDisabled {
844
+ cfg .WriteString (fmt .Sprintf ("\t \t min = %d\n " , row .Validation .Min ))
845
+ }
846
+ if ! row .Validation .MaxDisabled {
847
+ cfg .WriteString (fmt .Sprintf ("\t \t max = %d\n " , row .Validation .Max ))
848
+ }
849
+ if row .Validation .Monotonic != "" {
850
+ cfg .WriteString (fmt .Sprintf ("\t \t monotonic = \" %s\" \n " , row .Validation .Monotonic ))
851
+ }
852
+ if row .Validation .Regex != "" {
853
+ cfg .WriteString (fmt .Sprintf ("\t \t regex = %q\n " , row .Validation .Regex ))
854
+ }
855
+ cfg .WriteString (fmt .Sprintf ("\t \t error = %q\n " , row .Validation .Error ))
856
+ cfg .WriteString ("\t }\n " )
857
+ }
858
+
859
+ cfg .WriteString ("}\n " )
860
+
861
+ resource .Test (t , resource.TestCase {
862
+ ProviderFactories : coderFactory (),
863
+ IsUnitTest : true ,
864
+ Steps : []resource.TestStep {{
865
+ Config : cfg .String (),
866
+ ExpectError : row .Error ,
867
+ Check : func (state * terraform.State ) error {
868
+ require .Len (t , state .Modules , 1 )
869
+ require .Len (t , state .Modules [0 ].Resources , 1 )
870
+ param := state .Modules [0 ].Resources ["data.coder_parameter.parameter" ]
871
+ require .NotNil (t , param )
872
+
873
+ if row .Default == "" {
874
+ _ , ok := param .Primary .Attributes ["default" ]
875
+ require .False (t , ok , "default should not be set" )
876
+ } else {
877
+ require .Equal (t , strings .Trim (row .Default , `"` ), param .Primary .Attributes ["default" ])
878
+ }
879
+
880
+ if row .OutputValue == "" {
881
+ _ , ok := param .Primary .Attributes ["value" ]
882
+ require .False (t , ok , "output value should not be set" )
883
+ } else {
884
+ require .Equal (t , strings .Trim (row .OutputValue , `"` ), param .Primary .Attributes ["value" ])
885
+ }
886
+
887
+ for key , expected := range map [string ]string {
888
+ "optional" : strconv .FormatBool (row .Optional ),
889
+ } {
890
+ require .Equal (t , expected , param .Primary .Attributes [key ], "optional" )
891
+ }
892
+
893
+ return nil
894
+ },
895
+ }},
896
+ })
897
+ })
898
+ }
899
+ }
900
+ }
901
+
689
902
func TestValueValidatesType (t * testing.T ) {
690
903
t .Parallel ()
691
904
for _ , tc := range []struct {
@@ -798,6 +1011,25 @@ func TestValueValidatesType(t *testing.T) {
798
1011
Value : `[]` ,
799
1012
MinDisabled : true ,
800
1013
MaxDisabled : true ,
1014
+ }, {
1015
+ Name : "ValidListOfStrings" ,
1016
+ Type : "list(string)" ,
1017
+ Value : `["first","second","third"]` ,
1018
+ MinDisabled : true ,
1019
+ MaxDisabled : true ,
1020
+ }, {
1021
+ Name : "InvalidListOfStrings" ,
1022
+ Type : "list(string)" ,
1023
+ Value : `["first","second","third"` ,
1024
+ MinDisabled : true ,
1025
+ MaxDisabled : true ,
1026
+ Error : regexp .MustCompile ("is not valid list of strings" ),
1027
+ }, {
1028
+ Name : "EmptyListOfStrings" ,
1029
+ Type : "list(string)" ,
1030
+ Value : `[]` ,
1031
+ MinDisabled : true ,
1032
+ MaxDisabled : true ,
801
1033
}} {
802
1034
tc := tc
803
1035
t .Run (tc .Name , func (t * testing.T ) {
0 commit comments