1
16
17 package editor
18
19 import (
20 "reflect"
21 "strings"
22 "testing"
23
24 corev1 "k8s.io/api/core/v1"
25 "k8s.io/apimachinery/pkg/api/meta"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/runtime/schema"
30 "k8s.io/apimachinery/pkg/types"
31 "k8s.io/cli-runtime/pkg/genericiooptions"
32 "k8s.io/cli-runtime/pkg/resource"
33 )
34
35 func TestHashOnLineBreak(t *testing.T) {
36 tests := []struct {
37 original string
38 expected string
39 }{
40 {
41 original: "",
42 expected: "",
43 },
44 {
45 original: "\n",
46 expected: "\n",
47 },
48 {
49 original: "a\na\na\n",
50 expected: "a\n# a\n# a\n",
51 },
52 {
53 original: "a\n\n\na\n\n",
54 expected: "a\n# \n# \n# a\n# \n",
55 },
56 }
57 for _, test := range tests {
58 r := hashOnLineBreak(test.original)
59 if r != test.expected {
60 t.Errorf("expected: %s, saw: %s", test.expected, r)
61 }
62 }
63 }
64
65 func TestManagedFieldsExtractAndRestore(t *testing.T) {
66 tests := map[string]struct {
67 object runtime.Object
68 managedFields map[types.UID][]metav1.ManagedFieldsEntry
69 }{
70 "single object, empty managedFields": {
71 object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("12345")}},
72 managedFields: map[types.UID][]metav1.ManagedFieldsEntry{
73 types.UID("12345"): nil,
74 },
75 },
76 "multiple objects, empty managedFields": {
77 object: &unstructured.UnstructuredList{
78 Object: map[string]interface{}{
79 "kind": "List",
80 "apiVersion": "v1",
81 "metadata": map[string]interface{}{},
82 },
83 Items: []unstructured.Unstructured{
84 {
85 Object: map[string]interface{}{
86 "apiVersion": "v1",
87 "kind": "Pod",
88 "metadata": map[string]interface{}{
89 "uid": "12345",
90 },
91 "spec": map[string]interface{}{},
92 "status": map[string]interface{}{},
93 },
94 },
95 {
96 Object: map[string]interface{}{
97 "apiVersion": "v1",
98 "kind": "Pod",
99 "metadata": map[string]interface{}{
100 "uid": "98765",
101 },
102 "spec": map[string]interface{}{},
103 "status": map[string]interface{}{},
104 },
105 },
106 },
107 },
108 managedFields: map[types.UID][]metav1.ManagedFieldsEntry{
109 types.UID("12345"): nil,
110 types.UID("98765"): nil,
111 },
112 },
113 "single object, all managedFields": {
114 object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
115 UID: types.UID("12345"),
116 ManagedFields: []metav1.ManagedFieldsEntry{
117 {
118 Manager: "test",
119 Operation: metav1.ManagedFieldsOperationApply,
120 },
121 },
122 }},
123 managedFields: map[types.UID][]metav1.ManagedFieldsEntry{
124 types.UID("12345"): {
125 {
126 Manager: "test",
127 Operation: metav1.ManagedFieldsOperationApply,
128 },
129 },
130 },
131 },
132 "multiple objects, all managedFields": {
133 object: &unstructured.UnstructuredList{
134 Object: map[string]interface{}{
135 "kind": "List",
136 "apiVersion": "v1",
137 "metadata": map[string]interface{}{},
138 },
139 Items: []unstructured.Unstructured{
140 {
141 Object: map[string]interface{}{
142 "apiVersion": "v1",
143 "kind": "Pod",
144 "metadata": map[string]interface{}{
145 "uid": "12345",
146 "managedFields": []interface{}{
147 map[string]interface{}{
148 "manager": "test",
149 "operation": "Apply",
150 },
151 },
152 },
153 "spec": map[string]interface{}{},
154 "status": map[string]interface{}{},
155 },
156 },
157 {
158 Object: map[string]interface{}{
159 "apiVersion": "v1",
160 "kind": "Pod",
161 "metadata": map[string]interface{}{
162 "uid": "98765",
163 "managedFields": []interface{}{
164 map[string]interface{}{
165 "manager": "test",
166 "operation": "Update",
167 },
168 },
169 },
170 "spec": map[string]interface{}{},
171 "status": map[string]interface{}{},
172 },
173 },
174 },
175 },
176 managedFields: map[types.UID][]metav1.ManagedFieldsEntry{
177 types.UID("12345"): {
178 {
179 Manager: "test",
180 Operation: metav1.ManagedFieldsOperationApply,
181 },
182 },
183 types.UID("98765"): {
184 {
185 Manager: "test",
186 Operation: metav1.ManagedFieldsOperationUpdate,
187 },
188 },
189 },
190 },
191 "multiple objects, some managedFields": {
192 object: &unstructured.UnstructuredList{
193 Object: map[string]interface{}{
194 "kind": "List",
195 "apiVersion": "v1",
196 "metadata": map[string]interface{}{},
197 },
198 Items: []unstructured.Unstructured{
199 {
200 Object: map[string]interface{}{
201 "apiVersion": "v1",
202 "kind": "Pod",
203 "metadata": map[string]interface{}{
204 "uid": "12345",
205 "managedFields": []interface{}{
206 map[string]interface{}{
207 "manager": "test",
208 "operation": "Apply",
209 },
210 },
211 },
212 "spec": map[string]interface{}{},
213 "status": map[string]interface{}{},
214 },
215 },
216 {
217 Object: map[string]interface{}{
218 "apiVersion": "v1",
219 "kind": "Pod",
220 "metadata": map[string]interface{}{
221 "uid": "98765",
222 },
223 "spec": map[string]interface{}{},
224 "status": map[string]interface{}{},
225 },
226 },
227 },
228 },
229 managedFields: map[types.UID][]metav1.ManagedFieldsEntry{
230 types.UID("12345"): {
231 {
232 Manager: "test",
233 Operation: metav1.ManagedFieldsOperationApply,
234 },
235 },
236 types.UID("98765"): nil,
237 },
238 },
239 }
240
241 for name, test := range tests {
242 t.Run(name, func(t *testing.T) {
243
244 objCopy := test.object.DeepCopyObject()
245 var infos []*resource.Info
246 o := NewEditOptions(NormalEditMode, genericiooptions.NewTestIOStreamsDiscard())
247 err := o.extractManagedFields(objCopy)
248 if err != nil {
249 t.Errorf("unexpected extraction error %v", err)
250 }
251 if meta.IsListType(objCopy) {
252 infos = []*resource.Info{}
253 meta.EachListItem(objCopy, func(obj runtime.Object) error {
254 metaObjs, _ := meta.Accessor(obj)
255 if metaObjs.GetManagedFields() != nil {
256 t.Errorf("unexpected managedFileds after extraction")
257 }
258 infos = append(infos, &resource.Info{Object: obj})
259 return nil
260 })
261 } else {
262 metaObjs, _ := meta.Accessor(objCopy)
263 if metaObjs.GetManagedFields() != nil {
264 t.Errorf("unexpected managedFileds after extraction")
265 }
266 infos = []*resource.Info{{Object: objCopy}}
267 }
268
269 err = o.restoreManagedFields(infos)
270 if err != nil {
271 t.Errorf("unexpected restore error %v", err)
272 }
273 if !reflect.DeepEqual(test.object, objCopy) {
274 t.Errorf("mismatched object after extract and restore managedFields: %#v", objCopy)
275 }
276 if test.managedFields != nil && !reflect.DeepEqual(test.managedFields, o.managedFields) {
277 t.Errorf("mismatched managedFields %#v vs %#v", test.managedFields, o.managedFields)
278 }
279 })
280 }
281 }
282
283 type testVisitor struct {
284 updatedInfos []*resource.Info
285 }
286
287 func (tv *testVisitor) Visit(f resource.VisitorFunc) error {
288 var err error
289 for _, ui := range tv.updatedInfos {
290 err = f(ui, err)
291 }
292 return err
293 }
294
295 var unregMapping = &meta.RESTMapping{
296 Resource: schema.GroupVersionResource{
297 Group: "a",
298 Version: "b",
299 Resource: "c",
300 },
301 GroupVersionKind: schema.GroupVersionKind{
302 Group: "a",
303 Version: "b",
304 Kind: "d",
305 },
306 }
307
308 func TestEditOptions_visitToPatch(t *testing.T) {
309
310 expectedErr := func(err error) bool {
311 return err != nil && strings.Contains(err.Error(), "At least one of apiVersion, kind and name was changed")
312 }
313
314 type args struct {
315 originalInfos []*resource.Info
316 patchVisitor resource.Visitor
317 results *editResults
318 }
319 tests := []struct {
320 name string
321 args args
322 checkErr func(err error) bool
323 }{
324 {
325 name: "name-diff",
326 args: args{
327 originalInfos: []*resource.Info{
328 {
329 Namespace: "ns",
330 Name: "before",
331 Object: &unstructured.Unstructured{
332 Object: map[string]interface{}{
333 "apiVersion": "v1",
334 "kind": "Thingy",
335 "metadata": map[string]interface{}{
336 "uid": "12345",
337 "namespace": "ns",
338 "name": "before",
339 },
340 "spec": map[string]interface{}{},
341 },
342 },
343 Mapping: unregMapping,
344 },
345 },
346 patchVisitor: &testVisitor{
347 updatedInfos: []*resource.Info{
348 {
349 Namespace: "ns",
350 Name: "after",
351 Object: &unstructured.Unstructured{
352 Object: map[string]interface{}{
353 "apiVersion": "v1",
354 "kind": "Thingy",
355 "metadata": map[string]interface{}{
356 "uid": "12345",
357 "namespace": "ns",
358 "name": "after",
359 },
360 "spec": map[string]interface{}{},
361 },
362 },
363 Mapping: unregMapping,
364 },
365 },
366 },
367 results: &editResults{},
368 },
369 checkErr: expectedErr,
370 },
371 {
372 name: "kind-diff",
373 args: args{
374 originalInfos: []*resource.Info{
375 {
376 Namespace: "ns",
377 Name: "myname",
378 Object: &unstructured.Unstructured{
379 Object: map[string]interface{}{
380 "apiVersion": "v1",
381 "kind": "Thingy",
382 "metadata": map[string]interface{}{
383 "uid": "12345",
384 "namespace": "ns",
385 "name": "myname",
386 },
387 "spec": map[string]interface{}{},
388 },
389 },
390 Mapping: unregMapping,
391 },
392 },
393 patchVisitor: &testVisitor{
394 updatedInfos: []*resource.Info{
395 {
396 Namespace: "ns",
397 Name: "myname",
398 Object: &unstructured.Unstructured{
399 Object: map[string]interface{}{
400 "apiVersion": "v1",
401 "kind": "OtherThingy",
402 "metadata": map[string]interface{}{
403 "uid": "12345",
404 "namespace": "ns",
405 "name": "myname",
406 },
407 "spec": map[string]interface{}{},
408 },
409 },
410 Mapping: unregMapping,
411 },
412 },
413 },
414 results: &editResults{},
415 },
416 checkErr: expectedErr,
417 },
418 {
419 name: "apiver-diff",
420 args: args{
421 originalInfos: []*resource.Info{
422 {
423 Namespace: "ns",
424 Name: "myname",
425 Object: &unstructured.Unstructured{
426 Object: map[string]interface{}{
427 "apiVersion": "v1",
428 "kind": "Thingy",
429 "metadata": map[string]interface{}{
430 "uid": "12345",
431 "namespace": "ns",
432 "name": "myname",
433 },
434 "spec": map[string]interface{}{},
435 },
436 },
437 Mapping: unregMapping,
438 },
439 },
440 patchVisitor: &testVisitor{
441 updatedInfos: []*resource.Info{
442 {
443 Namespace: "ns",
444 Name: "myname",
445 Object: &unstructured.Unstructured{
446 Object: map[string]interface{}{
447 "apiVersion": "v1alpha1",
448 "kind": "Thingy",
449 "metadata": map[string]interface{}{
450 "uid": "12345",
451 "namespace": "ns",
452 "name": "myname",
453 },
454 "spec": map[string]interface{}{},
455 },
456 },
457 Mapping: unregMapping,
458 },
459 },
460 },
461 results: &editResults{},
462 },
463 checkErr: expectedErr,
464 },
465 }
466 for _, tt := range tests {
467 t.Run(tt.name, func(t *testing.T) {
468 o := &EditOptions{}
469 if err := o.visitToPatch(tt.args.originalInfos, tt.args.patchVisitor, tt.args.results); !tt.checkErr(err) {
470 t.Errorf("EditOptions.visitToPatch() %s error = %v", tt.name, err)
471 }
472 })
473 }
474 }
475
View as plain text