@@ -23,13 +23,162 @@ import (
2323 memory2 "k8s.io/client-go/discovery/cached/memory"
2424 "k8s.io/client-go/rest"
2525 "k8s.io/client-go/restmapper"
26+ "k8s.io/kube-openapi/pkg/validation/spec"
2627
2728 krov1alpha1 "github.com/kubernetes-sigs/kro/api/v1alpha1"
2829 "github.com/kubernetes-sigs/kro/pkg/graph/variable"
2930 "github.com/kubernetes-sigs/kro/pkg/testutil/generator"
3031 "github.com/kubernetes-sigs/kro/pkg/testutil/k8s"
3132)
3233
34+ func TestLookupSchemaAtField_AdditionalProperties (t * testing.T ) {
35+ tests := []struct {
36+ name string
37+ schema * spec.Schema
38+ field string
39+ expectNil bool
40+ expectedType string
41+ }{
42+ {
43+ name : "direct property lookup works" ,
44+ schema : & spec.Schema {
45+ SchemaProps : spec.SchemaProps {
46+ Type : []string {"object" },
47+ Properties : map [string ]spec.Schema {
48+ "name" : {SchemaProps : spec.SchemaProps {Type : []string {"string" }}},
49+ },
50+ },
51+ },
52+ field : "name" ,
53+ expectNil : false ,
54+ expectedType : "string" ,
55+ },
56+ {
57+ name : "additionalProperties lookup should return value schema" ,
58+ schema : & spec.Schema {
59+ SchemaProps : spec.SchemaProps {
60+ Type : []string {"object" },
61+ AdditionalProperties : & spec.SchemaOrBool {
62+ Allows : true ,
63+ Schema : & spec.Schema {
64+ SchemaProps : spec.SchemaProps {Type : []string {"string" }},
65+ },
66+ },
67+ },
68+ },
69+ field : "anyDynamicKey" ,
70+ expectNil : false ,
71+ expectedType : "string" ,
72+ },
73+ {
74+ name : "ConfigMap.data style schema" ,
75+ schema : & spec.Schema {
76+ SchemaProps : spec.SchemaProps {
77+ Type : []string {"object" },
78+ Properties : map [string ]spec.Schema {
79+ "data" : {
80+ SchemaProps : spec.SchemaProps {
81+ Type : []string {"object" },
82+ AdditionalProperties : & spec.SchemaOrBool {
83+ Allows : true ,
84+ Schema : & spec.Schema {
85+ SchemaProps : spec.SchemaProps {Type : []string {"string" }},
86+ },
87+ },
88+ },
89+ },
90+ },
91+ },
92+ },
93+ field : "data" ,
94+ expectNil : false ,
95+ expectedType : "object" ,
96+ },
97+ {
98+ name : "labels style map[string]string" ,
99+ schema : & spec.Schema {
100+ SchemaProps : spec.SchemaProps {
101+ Type : []string {"object" },
102+ AdditionalProperties : & spec.SchemaOrBool {
103+ Allows : true ,
104+ Schema : & spec.Schema {
105+ SchemaProps : spec.SchemaProps {Type : []string {"string" }},
106+ },
107+ },
108+ },
109+ },
110+ field : "app" ,
111+ expectNil : false ,
112+ expectedType : "string" ,
113+ },
114+ {
115+ name : "nested map - first level" ,
116+ schema : & spec.Schema {
117+ SchemaProps : spec.SchemaProps {
118+ Type : []string {"object" },
119+ AdditionalProperties : & spec.SchemaOrBool {
120+ Allows : true ,
121+ Schema : & spec.Schema {
122+ SchemaProps : spec.SchemaProps {
123+ Type : []string {"object" },
124+ AdditionalProperties : & spec.SchemaOrBool {
125+ Allows : true ,
126+ Schema : & spec.Schema {
127+ SchemaProps : spec.SchemaProps {Type : []string {"integer" }},
128+ },
129+ },
130+ },
131+ },
132+ },
133+ },
134+ },
135+ field : "outerKey" ,
136+ expectNil : false ,
137+ expectedType : "object" ,
138+ },
139+ {
140+ name : "array items with additionalProperties" ,
141+ schema : & spec.Schema {
142+ SchemaProps : spec.SchemaProps {
143+ Type : []string {"array" },
144+ Items : & spec.SchemaOrArray {
145+ Schema : & spec.Schema {
146+ SchemaProps : spec.SchemaProps {
147+ Type : []string {"object" },
148+ AdditionalProperties : & spec.SchemaOrBool {
149+ Allows : true ,
150+ Schema : & spec.Schema {
151+ SchemaProps : spec.SchemaProps {Type : []string {"string" }},
152+ },
153+ },
154+ },
155+ },
156+ },
157+ },
158+ },
159+ field : "dynamicKey" ,
160+ expectNil : false ,
161+ expectedType : "string" ,
162+ },
163+ }
164+
165+ for _ , tt := range tests {
166+ t .Run (tt .name , func (t * testing.T ) {
167+ result := lookupSchemaAtField (tt .schema , tt .field )
168+
169+ if tt .expectNil {
170+ assert .Nil (t , result , "expected nil schema" )
171+ return
172+ }
173+
174+ require .NotNil (t , result , "expected non-nil schema but got nil (AdditionalProperties not handled?)" )
175+ if tt .expectedType != "" && len (result .Type ) > 0 {
176+ assert .Equal (t , tt .expectedType , result .Type [0 ], "unexpected schema type" )
177+ }
178+ })
179+ }
180+ }
181+
33182func TestGraphBuilder_Validation (t * testing.T ) {
34183 fakeResolver , fakeDiscovery := k8s .NewFakeResolver ()
35184 restMapper := restmapper .NewDeferredDiscoveryRESTMapper (memory2 .NewMemCacheClient (fakeDiscovery ))
@@ -1873,6 +2022,66 @@ func TestGraphBuilder_CELTypeChecking(t *testing.T) {
18732022 },
18742023 wantErr : false ,
18752024 },
2025+ {
2026+ name : "ConfigMap.data type mismatch - list where string expected" ,
2027+ resourceGraphDefinitionOpts : []generator.ResourceGraphDefinitionOption {
2028+ generator .WithSchema (
2029+ "Test" , "v1alpha1" ,
2030+ map [string ]interface {}{
2031+ "items" : "[]string" ,
2032+ },
2033+ nil ,
2034+ ),
2035+ generator .WithResource ("configmap" , map [string ]interface {}{
2036+ "apiVersion" : "v1" ,
2037+ "kind" : "ConfigMap" ,
2038+ "metadata" : map [string ]interface {}{
2039+ "name" : "test-config" ,
2040+ },
2041+ "data" : map [string ]interface {}{
2042+ "items" : "${schema.spec.items}" ,
2043+ },
2044+ }, nil , nil ),
2045+ },
2046+ wantErr : true ,
2047+ errMsg : "type mismatch" ,
2048+ },
2049+ {
2050+ name : "ConfigMap.data type mismatch - map() result where string expected" ,
2051+ resourceGraphDefinitionOpts : []generator.ResourceGraphDefinitionOption {
2052+ generator .WithSchema (
2053+ "ForEachTest" , "v1alpha1" ,
2054+ map [string ]interface {}{
2055+ "queues" : "[]string" ,
2056+ },
2057+ nil ,
2058+ ),
2059+ generator .WithResourceCollection ("queues" , map [string ]interface {}{
2060+ "apiVersion" : "v1" ,
2061+ "kind" : "Pod" ,
2062+ "metadata" : map [string ]interface {}{
2063+ "name" : "${schema.metadata.name + '-' + queue}" ,
2064+ },
2065+ },
2066+ []krov1alpha1.ForEachDimension {
2067+ {"queue" : "${schema.spec.queues}" },
2068+ },
2069+ nil , nil ),
2070+ generator .WithResource ("configmap" , map [string ]interface {}{
2071+ "apiVersion" : "v1" ,
2072+ "kind" : "ConfigMap" ,
2073+ "metadata" : map [string ]interface {}{
2074+ "name" : "${schema.metadata.name}-output" ,
2075+ "namespace" : "${schema.metadata.namespace}" ,
2076+ },
2077+ "data" : map [string ]interface {}{
2078+ "queues" : "${queues.map(q, {\" name\" : q.metadata.name, \" queueARN\" : q.status.phase})}" ,
2079+ },
2080+ }, nil , nil ),
2081+ },
2082+ wantErr : true ,
2083+ errMsg : "type mismatch" ,
2084+ },
18762085 }
18772086
18782087 for _ , tt := range tests {
0 commit comments