1/*
2Copyright 2019 The Kubernetes Authors.
3 Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6     http://www.apache.org/licenses/LICENSE-2.0
7 Unless required by applicable law or agreed to in writing, software
8distributed under the License is distributed on an "AS IS" BASIS,
9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10See the License for the specific language governing permissions and
11limitations under the License.
12*/
13
14package merge_test
15
16import (
17	"testing"
18
19	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
20	. "sigs.k8s.io/structured-merge-diff/v4/internal/fixture"
21	"sigs.k8s.io/structured-merge-diff/v4/merge"
22	"sigs.k8s.io/structured-merge-diff/v4/typed"
23)
24
25// portListParser sets the default value of key "protocol" to "TCP"
26var portListParser = func() *typed.Parser {
27	parser, err := typed.NewParser(`types:
28- name: v1
29  map:
30    fields:
31      - name: containerPorts
32        type:
33          list:
34            elementType:
35              map:
36                fields:
37                - name: port
38                  type:
39                    scalar: numeric
40                - name: protocol
41                  default: "TCP"
42                  type:
43                    scalar: string
44                - name: name
45                  type:
46                    scalar: string
47            elementRelationship: associative
48            keys:
49            - port
50            - protocol
51`)
52	if err != nil {
53		panic(err)
54	}
55	return parser
56}()
57
58func TestDefaultKeysFlat(t *testing.T) {
59	tests := map[string]TestCase{
60		"apply_missing_defaulted_key_A": {
61			Ops: []Operation{
62				Apply{
63					Manager:    "default",
64					APIVersion: "v1",
65					Object: `
66						containerPorts:
67						- port: 80
68					`,
69				},
70			},
71			APIVersion: "v1",
72			Object: `
73				containerPorts:
74				- port: 80
75			`,
76			Managed: fieldpath.ManagedFields{
77				"default": fieldpath.NewVersionedSet(
78					_NS(
79						_P("containerPorts", _KBF("port", 80, "protocol", "TCP")),
80						_P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "port"),
81					),
82					"v1",
83					false,
84				),
85			},
86		},
87		"apply_missing_defaulted_key_B": {
88			Ops: []Operation{
89				Apply{
90					Manager:    "default",
91					APIVersion: "v1",
92					Object: `
93						containerPorts:
94						- port: 80
95						- port: 80
96						  protocol: UDP
97					`,
98				},
99			},
100			APIVersion: "v1",
101			Object: `
102				containerPorts:
103				- port: 80
104				- port: 80
105				  protocol: UDP
106			`,
107			Managed: fieldpath.ManagedFields{
108				"default": fieldpath.NewVersionedSet(
109					_NS(
110						_P("containerPorts", _KBF("port", 80, "protocol", "TCP")),
111						_P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "port"),
112						_P("containerPorts", _KBF("port", 80, "protocol", "UDP")),
113						_P("containerPorts", _KBF("port", 80, "protocol", "UDP"), "port"),
114						_P("containerPorts", _KBF("port", 80, "protocol", "UDP"), "protocol"),
115					),
116					"v1",
117					false,
118				),
119			},
120		},
121		"apply_missing_defaulted_key_with_conflict": {
122			Ops: []Operation{
123				Apply{
124					Manager:    "apply-one",
125					APIVersion: "v1",
126					Object: `
127						containerPorts:
128						- port: 80
129						  protocol: TCP
130						  name: foo
131					`,
132				},
133				Apply{
134					Manager:    "apply-two",
135					APIVersion: "v1",
136					Object: `
137						containerPorts:
138						- port: 80
139						  name: bar
140					`,
141					Conflicts: merge.Conflicts{
142						merge.Conflict{Manager: "apply-one", Path: _P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "name")},
143					},
144				},
145			},
146			APIVersion: "v1",
147			Object: `
148				containerPorts:
149				- port: 80
150				  protocol: TCP
151				  name: foo
152			`,
153			Managed: fieldpath.ManagedFields{
154				"apply-one": fieldpath.NewVersionedSet(
155					_NS(
156						_P("containerPorts", _KBF("port", 80, "protocol", "TCP")),
157						_P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "port"),
158						_P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "protocol"),
159						_P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "name"),
160					),
161					"v1",
162					false,
163				),
164			},
165		},
166	}
167
168	for name, test := range tests {
169		t.Run(name, func(t *testing.T) {
170			if err := test.Test(portListParser); err != nil {
171				t.Fatal(err)
172			}
173		})
174	}
175}
176
177func TestDefaultKeysFlatErrors(t *testing.T) {
178	tests := map[string]TestCase{
179		"apply_missing_undefaulted_defaulted_key": {
180			Ops: []Operation{
181				Apply{
182					Manager:    "default",
183					APIVersion: "v1",
184					Object: `
185						containerPorts:
186						- protocol: TCP
187					`,
188				},
189			},
190		},
191		"apply_missing_defaulted_key_ambiguous_A": {
192			Ops: []Operation{
193				Apply{
194					Manager:    "default",
195					APIVersion: "v1",
196					Object: `
197						containerPorts:
198						- port: 80
199						- port: 80
200					`,
201				},
202			},
203		},
204		"apply_missing_defaulted_key_ambiguous_B": {
205			Ops: []Operation{
206				Apply{
207					Manager:    "default",
208					APIVersion: "v1",
209					Object: `
210						containerPorts:
211						- port: 80
212						- port: 80
213						  protocol: TCP
214					`,
215				},
216			},
217		},
218	}
219	for name, test := range tests {
220		t.Run(name, func(t *testing.T) {
221			if test.Test(portListParser) == nil {
222				t.Fatal("Should fail")
223			}
224		})
225	}
226}
227
228// bookParser sets the default value of key:
229// * "chapter" to 1
230// * "section" to "A"
231// * "page" to 2,
232// * "line" to 3,
233var bookParser = func() *typed.Parser {
234	parser, err := typed.NewParser(`types:
235- name: v1
236  map:
237    fields:
238      - name: book
239        type:
240          list:
241            elementType:
242              map:
243                fields:
244                - name: chapter
245                  default: 1
246                  type:
247                    scalar: numeric
248                - name: section
249                  default: "A"
250                  type:
251                    scalar: string
252                - name: sentences
253                  type:
254                    list:
255                      elementType:
256                        map:
257                          fields:
258                          - name: page
259                            default: 2.0
260                            type:
261                              scalar: numeric
262                          - name: line
263                            default: 3
264                            type:
265                              scalar: numeric
266                          - name: text
267                            type:
268                              scalar: string
269                      elementRelationship: associative
270                      keys:
271                      - page
272                      - line
273            elementRelationship: associative
274            keys:
275            - chapter
276            - section
277`)
278	if err != nil {
279		panic(err)
280	}
281	return parser
282}()
283
284func TestDefaultKeysNested(t *testing.T) {
285	tests := map[string]TestCase{
286		"apply_missing_every_key_nested": {
287			Ops: []Operation{
288				Apply{
289					Manager:    "default",
290					APIVersion: "v1",
291					Object: `
292						book:
293						- sentences:
294						  - text: blah
295					`,
296				},
297			},
298			APIVersion: "v1",
299			Object: `
300				book:
301				- sentences:
302				  - text: blah
303			`,
304			Managed: fieldpath.ManagedFields{
305				"default": fieldpath.NewVersionedSet(
306					_NS(
307						_P(
308							"book", _KBF("chapter", 1, "section", "A"),
309						),
310						_P(
311							"book", _KBF("chapter", 1, "section", "A"),
312							"sentences", _KBF("page", 2, "line", 3),
313						),
314						_P(
315							"book", _KBF("chapter", 1, "section", "A"),
316							"sentences", _KBF("page", 2, "line", 3),
317							"text",
318						),
319					),
320					"v1",
321					false,
322				),
323			},
324		},
325		"apply_integer_key_with_float_default": {
326			Ops: []Operation{
327				Apply{
328					Manager:    "default",
329					APIVersion: "v1",
330					Object: `
331						book:
332						- sentences:
333						  - text: blah
334					`,
335				},
336				Apply{
337					Manager:    "default",
338					APIVersion: "v1",
339					Object: `
340						book:
341						- sentences:
342						  - text: blah
343						    page: 2
344					`,
345				},
346			},
347			APIVersion: "v1",
348			Object: `
349				book:
350				- sentences:
351				  - text: blah
352				    page: 2
353			`,
354			Managed: fieldpath.ManagedFields{
355				"default": fieldpath.NewVersionedSet(
356					_NS(
357						_P(
358							"book", _KBF("chapter", 1, "section", "A"),
359						),
360						_P(
361							"book", _KBF("chapter", 1, "section", "A"),
362							"sentences", _KBF("page", 2, "line", 3),
363						),
364						_P(
365							"book", _KBF("chapter", 1, "section", "A"),
366							"sentences", _KBF("page", 2, "line", 3),
367							"text",
368						),
369						_P(
370							"book", _KBF("chapter", 1, "section", "A"),
371							"sentences", _KBF("page", 2, "line", 3),
372							"page",
373						),
374					),
375					"v1",
376					false,
377				),
378			},
379		},
380	}
381
382	for name, test := range tests {
383		t.Run(name, func(t *testing.T) {
384			if err := test.Test(bookParser); err != nil {
385				t.Fatal(err)
386			}
387		})
388	}
389}
390