@@ -281,3 +281,118 @@ func TestImageUrlPattern(t *testing.T) {
281281 })
282282 }
283283}
284+
285+
286+ func TestReferenceField (t * testing.T ) {
287+ tests := []struct {
288+ name string
289+ reference string
290+ wantValid bool
291+ omitField bool // true if the field should be omitted entirely
292+ }{
293+ // Valid cases
294+ {"URL" , "https://issues.redhat.com/browse/EC-1246" , true , false },
295+ {"Multiline URL" , "https://issues.redhat.com/browse/EC-1246\n https://issues.redhat.com/browse/EC-1101" , true , false },
296+ {"With whitespaces" , "string with whitespaces" , true , false },
297+ {"Long String" , strings .Repeat ("A" , 1000 ), true , false },
298+ {"Empty String" , "" , true , false },
299+ {"Omitted field" , "" , true , true }, // Field is omitted entirely
300+ }
301+
302+ for _ , tt := range tests {
303+ t .Run (tt .name , func (t * testing.T ) {
304+ // Create a policy with the test URL
305+ policy := EnterpriseContractPolicy {
306+ Spec : EnterpriseContractPolicySpec {
307+ Sources : []Source {
308+ {
309+ VolatileConfig : & VolatileSourceConfig {
310+ Exclude : []VolatileCriteria {
311+ {
312+ Value : "test-rule" ,
313+ },
314+ },
315+ },
316+ },
317+ },
318+ },
319+ }
320+ if ! tt .omitField {
321+ policy .Spec .Sources [0 ].VolatileConfig .Exclude [0 ].Reference = tt .reference
322+ }
323+
324+ // Create a CRD validation schema
325+ crd := v1.CustomResourceDefinition {}
326+ bytes , err := os .ReadFile ("../../config/crd/bases/appstudio.redhat.com_enterprisecontractpolicies.yaml" )
327+ if err != nil {
328+ t .Fatalf ("unexpected error reading CRD: %s" , err )
329+ }
330+ if err := yaml .Unmarshal (bytes , & crd ); err != nil {
331+ t .Fatalf ("unexpected error when decoding schema: %s" , err )
332+ }
333+
334+ crdv := apiextensions.CustomResourceValidation {}
335+ if err := v1 .Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation (crd .Spec .Versions [0 ].Schema , & crdv , nil ); err != nil {
336+ t .Fatalf ("failed in CRD validation conversion: %s" , err )
337+ }
338+
339+ s , err := schema .NewStructural (crdv .OpenAPIV3Schema )
340+ if err != nil {
341+ t .Fatalf ("unexpected error when creating structural: %s" , err )
342+ }
343+
344+ v := validation .NewSchemaValidatorFromOpenAPI (s .ToKubeOpenAPI ())
345+
346+ // Convert policy to unstructured for validation
347+ obj := unstructured.Unstructured {}
348+ obj .SetUnstructuredContent (map [string ]interface {}{
349+ "apiVersion" : "appstudio.redhat.com/v1alpha1" ,
350+ "kind" : "EnterpriseContractPolicy" ,
351+ "spec" : map [string ]interface {}{
352+ "sources" : []interface {}{
353+ map [string ]interface {}{
354+ "volatileConfig" : map [string ]interface {}{
355+ "exclude" : []interface {}{
356+ func () map [string ]interface {} {
357+ m := map [string ]interface {}{
358+ "value" : "test-rule" ,
359+ }
360+ if ! tt .omitField {
361+ m ["reference" ] = tt .reference
362+ }
363+ return m
364+ }(),
365+ },
366+ },
367+ },
368+ },
369+ },
370+ })
371+
372+ // Validate the object
373+ result := v .Validate (& obj )
374+ isValid := result .IsValid ()
375+
376+ if isValid != tt .wantValid {
377+ t .Errorf ("Validation for %q = %v, want %v. Errors: %v" , tt .reference , isValid , tt .wantValid , result .Errors )
378+ }
379+
380+ // Also validate the actual policy object
381+ policyObj := unstructured.Unstructured {}
382+ policyBytes , err := json .Marshal (policy )
383+ if err != nil {
384+ t .Fatalf ("unexpected error marshaling policy: %s" , err )
385+ }
386+ if err := json .Unmarshal (policyBytes , & policyObj .Object ); err != nil {
387+ t .Fatalf ("unexpected error unmarshaling policy: %s" , err )
388+ }
389+
390+ policyResult := v .Validate (& policyObj )
391+ policyIsValid := policyResult .IsValid ()
392+
393+ if policyIsValid != tt .wantValid {
394+ t .Errorf ("Policy validation for %q = %v, want %v. Errors: %v" , tt .reference , policyIsValid , tt .wantValid , policyResult .Errors )
395+ }
396+ })
397+ }
398+ }
0 commit comments