@@ -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 ) {