@@ -140,3 +140,151 @@ func TestRegexValidator(t *testing.T) {
140140 }
141141 })
142142}
143+
144+ func TestAllowedEnumValidator (t * testing.T ) {
145+ baseBP := config.Blueprint {
146+ BlueprintName : "test-bp" ,
147+ Groups : []config.Group {
148+ {
149+ Name : "primary" ,
150+ Modules : []config.Module {
151+ {
152+ ID : "test-module" ,
153+ Source : "test/module" ,
154+ Settings : config .NewDict (map [string ]cty.Value {
155+ "network_routing_mode" : cty .StringVal ("GLOBAL" ),
156+ }),
157+ },
158+ },
159+ },
160+ },
161+ }
162+
163+ validator := AllowedEnumValidator {}
164+
165+ t .Run ("validates_network_routing_mode_from_metadata_example" , func (t * testing.T ) {
166+ bp := baseBP
167+ bp .Groups [0 ].Modules [0 ].Settings = config .NewDict (map [string ]cty.Value {
168+ "network_routing_mode" : cty .StringVal ("INVALID_MODE" ),
169+ })
170+
171+ rule := modulereader.ValidationRule {
172+ Validator : "allowed_enum" ,
173+ ErrorMessage : "'network_routing_mode' must be GLOBAL or REGIONAL." ,
174+ Inputs : map [string ]interface {}{
175+ "vars" : []interface {}{"network_routing_mode" },
176+ "allowed" : []interface {}{"GLOBAL" , "REGIONAL" },
177+ },
178+ }
179+
180+ // 1. Test failure with invalid value
181+ err := validator .Validate (bp , bp .Groups [0 ].Modules [0 ], rule , bp .Groups [0 ], 0 )
182+ if err == nil {
183+ t .Fatal ("expected error for invalid routing mode, got nil" )
184+ }
185+ if ! strings .Contains (err .Error (), "'network_routing_mode' must be GLOBAL or REGIONAL." ) {
186+ t .Fatalf ("unexpected error message: %q" , err .Error ())
187+ }
188+
189+ // 2. Test failure with lowercase value (default case_sensitive is true)
190+ bp .Groups [0 ].Modules [0 ].Settings = config .NewDict (map [string ]cty.Value {
191+ "network_routing_mode" : cty .StringVal ("global" ),
192+ })
193+ if err := validator .Validate (bp , bp .Groups [0 ].Modules [0 ], rule , bp .Groups [0 ], 0 ); err == nil {
194+ t .Fatal ("expected error for lowercase 'global' due to default case sensitivity, got nil" )
195+ }
196+
197+ // 3. Test success with correct uppercase value
198+ bp .Groups [0 ].Modules [0 ].Settings = config .NewDict (map [string ]cty.Value {
199+ "network_routing_mode" : cty .StringVal ("GLOBAL" ),
200+ })
201+ if err := validator .Validate (bp , bp .Groups [0 ].Modules [0 ], rule , bp .Groups [0 ], 0 ); err != nil {
202+ t .Fatalf ("unexpected error for valid routing mode 'GLOBAL': %v" , err )
203+ }
204+
205+ // 4. Test success with correct uppercase value 'REGIONAL'
206+ bp .Groups [0 ].Modules [0 ].Settings = config .NewDict (map [string ]cty.Value {
207+ "network_routing_mode" : cty .StringVal ("REGIONAL" ),
208+ })
209+ if err := validator .Validate (bp , bp .Groups [0 ].Modules [0 ], rule , bp .Groups [0 ], 0 ); err != nil {
210+ t .Fatalf ("unexpected error for valid routing mode 'REGIONAL': %v" , err )
211+ }
212+ })
213+
214+ t .Run ("handles_explicit_case_insensitive_matching" , func (t * testing.T ) {
215+ bp := baseBP
216+ bp .Groups [0 ].Modules [0 ].Settings = config .NewDict (map [string ]cty.Value {
217+ "tier" : cty .StringVal ("basic_ssd" ),
218+ })
219+
220+ rule := modulereader.ValidationRule {
221+ Validator : "allowed_enum" ,
222+ Inputs : map [string ]interface {}{
223+ "vars" : []interface {}{"tier" },
224+ "allowed" : []interface {}{"BASIC_SSD" },
225+ "case_sensitive" : false ,
226+ },
227+ }
228+
229+ if err := validator .Validate (bp , bp .Groups [0 ].Modules [0 ], rule , bp .Groups [0 ], 0 ); err != nil {
230+ t .Fatalf ("expected case-insensitive match to pass, got: %v" , err )
231+ }
232+ })
233+
234+ t .Run ("fails_on_null_when_allow_null_is_false" , func (t * testing.T ) {
235+ bp := baseBP
236+ bp .Groups [0 ].Modules [0 ].Settings = config .NewDict (map [string ]cty.Value {
237+ "tier" : cty .NullVal (cty .String ),
238+ })
239+
240+ rule := modulereader.ValidationRule {
241+ Validator : "allowed_enum" ,
242+ Inputs : map [string ]interface {}{
243+ "vars" : []interface {}{"tier" },
244+ "allowed" : []interface {}{"BASIC_SSD" },
245+ "allow_null" : false ,
246+ },
247+ }
248+
249+ if err := validator .Validate (bp , bp .Groups [0 ].Modules [0 ], rule , bp .Groups [0 ], 0 ); err == nil {
250+ t .Fatal ("expected error for null value when allow_null is false, got nil" )
251+ }
252+ })
253+
254+ t .Run ("passes_on_null_when_allow_null_is_true" , func (t * testing.T ) {
255+ bp := baseBP
256+ bp .Groups [0 ].Modules [0 ].Settings = config .NewDict (map [string ]cty.Value {
257+ "tier" : cty .NullVal (cty .String ),
258+ })
259+
260+ rule := modulereader.ValidationRule {
261+ Validator : "allowed_enum" ,
262+ Inputs : map [string ]interface {}{
263+ "vars" : []interface {}{"tier" },
264+ "allowed" : []interface {}{"BASIC_SSD" },
265+ "allow_null" : true ,
266+ },
267+ }
268+
269+ if err := validator .Validate (bp , bp .Groups [0 ].Modules [0 ], rule , bp .Groups [0 ], 0 ); err != nil {
270+ t .Fatalf ("unexpected error for null value when allow_null is true: %v" , err )
271+ }
272+ })
273+
274+ t .Run ("fails_with_missing_allowed_input" , func (t * testing.T ) {
275+ rule := modulereader.ValidationRule {
276+ Validator : "allowed_enum" ,
277+ Inputs : map [string ]interface {}{
278+ "vars" : []interface {}{"tier" },
279+ },
280+ }
281+
282+ err := validator .Validate (baseBP , baseBP .Groups [0 ].Modules [0 ], rule , baseBP .Groups [0 ], 0 )
283+ if err == nil {
284+ t .Fatal ("expected error for missing 'allowed' input, got nil" )
285+ }
286+ if ! strings .Contains (err .Error (), "missing an 'allowed' list" ) {
287+ t .Fatalf ("unexpected error message: %v" , err )
288+ }
289+ })
290+ }
0 commit comments