@@ -31,7 +31,7 @@ func strPtr(s string) *string { return &s }
3131
3232// celSubjectRule is the CEL expression used in the +kubebuilder:validation:XValidation
3333// marker on ApisixRouteHTTPMatchExprSubject.
34- const celSubjectRule = `self.scope == 'Path' || self.name != '' `
34+ const celSubjectRule = `self.scope == 'Path' || size( self.name) > 0 `
3535
3636func evalCELSubjectRule (t * testing.T , scope , name string ) bool {
3737 t .Helper ()
@@ -74,15 +74,53 @@ func TestCEL_SubjectRule_InCRD(t *testing.T) {
7474 var crd map [string ]any
7575 require .NoError (t , yaml .Unmarshal (data , & crd ))
7676
77+ // Ensure no typographic/smart quotes crept in anywhere in the file.
7778 raw := string (data )
78- // The CEL rule must appear with ASCII single-quotes only.
79- assert .Contains (t , raw , `self.scope == 'Path' || self.name != ''` ,
80- "CRD should contain the XValidation rule with ASCII quotes" )
81- // Ensure no typographic/smart quotes crept in.
8279 assert .NotContains (t , raw , "\u2018 " , "CRD must not contain left single quotation mark \u2018 " )
8380 assert .NotContains (t , raw , "\u2019 " , "CRD must not contain right single quotation mark \u2019 " )
8481 assert .NotContains (t , raw , "\u201c " , "CRD must not contain left double quotation mark \u201c " )
8582 assert .NotContains (t , raw , "\u201d " , "CRD must not contain right double quotation mark \u201d " )
83+
84+ // Walk the parsed CRD to extract the x-kubernetes-validations rule string directly,
85+ // which is more robust than substring matching against the raw YAML (line-wrapping safe).
86+ rule := extractXValidationRule (t , crd )
87+ assert .Equal (t , celSubjectRule , rule ,
88+ "XValidation rule in CRD must match the expected CEL expression" )
89+ }
90+
91+ // extractXValidationRule walks the parsed CRD map to find the first
92+ // x-kubernetes-validations rule on the subject property of HTTP match exprs.
93+ func extractXValidationRule (t * testing.T , crd map [string ]any ) string {
94+ t .Helper ()
95+ // Path: spec.versions[0].schema.openAPIV3Schema
96+ // .properties.spec.properties.http.items
97+ // .properties.match.properties.exprs.items
98+ // .properties.subject.x-kubernetes-validations[0].rule
99+ get := func (m map [string ]any , key string ) map [string ]any {
100+ v , ok := m [key ]
101+ require .True (t , ok , "key %q not found" , key )
102+ mv , ok := v .(map [string ]any )
103+ require .True (t , ok , "key %q is not a map" , key )
104+ return mv
105+ }
106+ spec := get (crd , "spec" )
107+ versions := spec ["versions" ].([]any )
108+ require .NotEmpty (t , versions )
109+ schema := get (versions [0 ].(map [string ]any ), "schema" )
110+ root := get (schema , "openAPIV3Schema" )
111+ props := get (root , "properties" )
112+ specProps := get (get (props , "spec" ), "properties" )
113+ httpItems := get (get (specProps , "http" ), "items" )
114+ matchProps := get (get (get (httpItems , "properties" ), "match" ), "properties" )
115+ exprsItems := get (get (matchProps , "exprs" ), "items" )
116+ subject := get (get (exprsItems , "properties" ), "subject" )
117+ validations , ok := subject ["x-kubernetes-validations" ].([]any )
118+ require .True (t , ok , "x-kubernetes-validations not found or not a list" )
119+ require .NotEmpty (t , validations )
120+ first := validations [0 ].(map [string ]any )
121+ rule , ok := first ["rule" ].(string )
122+ require .True (t , ok , "rule field not found or not a string" )
123+ return rule
86124}
87125
88126func TestToVars_ScopeBody_SimpleField (t * testing.T ) {
0 commit comments