1/*
2Copyright 2018 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package rules
18
19import (
20	"reflect"
21	"testing"
22
23	"k8s.io/gengo/types"
24)
25
26func TestNamesMatch(t *testing.T) {
27	tcs := []struct {
28		// name of test case
29		name string
30		t    *types.Type
31
32		// expected list of violation fields
33		expected []string
34	}{
35		// The comments are in format of {goName, jsonName, match},
36		// {"PodSpec", "podSpec", true},
37		{
38			name: "simple",
39			t: &types.Type{
40				Kind: types.Struct,
41				Members: []types.Member{
42					types.Member{
43						Name: "PodSpec",
44						Tags: `json:"podSpec"`,
45					},
46				},
47			},
48			expected: []string{},
49		},
50		// {"PodSpec", "podSpec", true},
51		{
52			name: "multiple_json_tags",
53			t: &types.Type{
54				Kind: types.Struct,
55				Members: []types.Member{
56					types.Member{
57						Name: "PodSpec",
58						Tags: `json:"podSpec,omitempty"`,
59					},
60				},
61			},
62			expected: []string{},
63		},
64		// {"PodSpec", "podSpec", true},
65		{
66			name: "protobuf_tag",
67			t: &types.Type{
68				Kind: types.Struct,
69				Members: []types.Member{
70					types.Member{
71						Name: "PodSpec",
72						Tags: `json:"podSpec,omitempty" protobuf:"bytes,1,opt,name=podSpec"`,
73					},
74				},
75			},
76			expected: []string{},
77		},
78		// {"", "podSpec", false},
79		{
80			name: "empty",
81			t: &types.Type{
82				Kind: types.Struct,
83				Members: []types.Member{
84					types.Member{
85						Name: "",
86						Tags: `json:"podSpec"`,
87					},
88				},
89			},
90			expected: []string{""},
91		},
92		// {"PodSpec", "PodSpec", false},
93		{
94			name: "CamelCase_CamelCase",
95			t: &types.Type{
96				Kind: types.Struct,
97				Members: []types.Member{
98					types.Member{
99						Name: "PodSpec",
100						Tags: `json:"PodSpec"`,
101					},
102				},
103			},
104			expected: []string{"PodSpec"},
105		},
106		// {"podSpec", "podSpec", false},
107		{
108			name: "camelCase_camelCase",
109			t: &types.Type{
110				Kind: types.Struct,
111				Members: []types.Member{
112					types.Member{
113						Name: "podSpec",
114						Tags: `json:"podSpec"`,
115					},
116				},
117			},
118			expected: []string{"podSpec"},
119		},
120		// {"PodSpec", "spec", false},
121		{
122			name: "short_json_name",
123			t: &types.Type{
124				Kind: types.Struct,
125				Members: []types.Member{
126					types.Member{
127						Name: "PodSpec",
128						Tags: `json:"spec"`,
129					},
130				},
131			},
132			expected: []string{"PodSpec"},
133		},
134		// {"Spec", "podSpec", false},
135		{
136			name: "long_json_name",
137			t: &types.Type{
138				Kind: types.Struct,
139				Members: []types.Member{
140					types.Member{
141						Name: "Spec",
142						Tags: `json:"podSpec"`,
143					},
144				},
145			},
146			expected: []string{"Spec"},
147		},
148		// {"JSONSpec", "jsonSpec", true},
149		{
150			name: "acronym",
151			t: &types.Type{
152				Kind: types.Struct,
153				Members: []types.Member{
154					types.Member{
155						Name: "JSONSpec",
156						Tags: `json:"jsonSpec"`,
157					},
158				},
159			},
160			expected: []string{},
161		},
162		// {"JSONSpec", "jsonspec", false},
163		{
164			name: "acronym_invalid",
165			t: &types.Type{
166				Kind: types.Struct,
167				Members: []types.Member{
168					types.Member{
169						Name: "JSONSpec",
170						Tags: `json:"jsonspec"`,
171					},
172				},
173			},
174			expected: []string{"JSONSpec"},
175		},
176		// {"HTTPJSONSpec", "httpJSONSpec", true},
177		{
178			name: "multiple_acronym",
179			t: &types.Type{
180				Kind: types.Struct,
181				Members: []types.Member{
182					types.Member{
183						Name: "HTTPJSONSpec",
184						Tags: `json:"httpJSONSpec"`,
185					},
186				},
187			},
188			expected: []string{},
189		},
190		// // NOTE: this validator cannot tell two sequential all-capital words from one word,
191		// // therefore the case below is also considered matched.
192		// {"HTTPJSONSpec", "httpjsonSpec", true},
193		{
194			name: "multiple_acronym_as_one",
195			t: &types.Type{
196				Kind: types.Struct,
197				Members: []types.Member{
198					types.Member{
199						Name: "HTTPJSONSpec",
200						Tags: `json:"httpjsonSpec"`,
201					},
202				},
203			},
204			expected: []string{},
205		},
206		// NOTE: JSON tags in jsonTagBlacklist should skip evaluation
207		{
208			name: "blacklist_tag_dash",
209			t: &types.Type{
210				Kind: types.Struct,
211				Members: []types.Member{
212					types.Member{
213						Name: "podSpec",
214						Tags: `json:"-"`,
215					},
216				},
217			},
218			expected: []string{},
219		},
220		// {"PodSpec", "-", false},
221		{
222			name: "invalid_json_name_dash",
223			t: &types.Type{
224				Kind: types.Struct,
225				Members: []types.Member{
226					types.Member{
227						Name: "PodSpec",
228						Tags: `json:"-,"`,
229					},
230				},
231			},
232			expected: []string{"PodSpec"},
233		},
234		// NOTE: JSON names in jsonNameBlacklist should skip evaluation
235		// {"", "", true},
236		{
237			name: "unspecified",
238			t: &types.Type{
239				Kind: types.Struct,
240				Members: []types.Member{
241					types.Member{
242						Name: "",
243						Tags: `json:""`,
244					},
245				},
246			},
247			expected: []string{},
248		},
249		// {"podSpec", "", true},
250		{
251			name: "blacklist_empty",
252			t: &types.Type{
253				Kind: types.Struct,
254				Members: []types.Member{
255					types.Member{
256						Name: "podSpec",
257						Tags: `json:""`,
258					},
259				},
260			},
261			expected: []string{},
262		},
263		// {"podSpec", "metadata", true},
264		{
265			name: "blacklist_metadata",
266			t: &types.Type{
267				Kind: types.Struct,
268				Members: []types.Member{
269					types.Member{
270						Name: "podSpec",
271						Tags: `json:"metadata"`,
272					},
273				},
274			},
275			expected: []string{},
276		},
277		{
278			name: "non_struct",
279			t: &types.Type{
280				Kind: types.Map,
281			},
282			expected: []string{},
283		},
284		{
285			name: "no_json_tag",
286			t: &types.Type{
287				Kind: types.Struct,
288				Members: []types.Member{
289					types.Member{
290						Name: "PodSpec",
291						Tags: `podSpec`,
292					},
293				},
294			},
295			expected: []string{"PodSpec"},
296		},
297		// NOTE: this is to expand test coverage
298		// {"S", "s", true},
299		{
300			name: "single_character",
301			t: &types.Type{
302				Kind: types.Struct,
303				Members: []types.Member{
304					types.Member{
305						Name: "S",
306						Tags: `json:"s"`,
307					},
308				},
309			},
310			expected: []string{},
311		},
312		// NOTE: names with disallowed substrings should fail evaluation
313		// {"Pod-Spec", "pod-Spec", false},
314		{
315			name: "disallowed_substring_dash",
316			t: &types.Type{
317				Kind: types.Struct,
318				Members: []types.Member{
319					types.Member{
320						Name: "Pod-Spec",
321						Tags: `json:"pod-Spec"`,
322					},
323				},
324			},
325			expected: []string{"Pod-Spec"},
326		},
327		// {"Pod_Spec", "pod_Spec", false},
328		{
329			name: "disallowed_substring_underscore",
330			t: &types.Type{
331				Kind: types.Struct,
332				Members: []types.Member{
333					types.Member{
334						Name: "Pod_Spec",
335						Tags: `json:"pod_Spec"`,
336					},
337				},
338			},
339			expected: []string{"Pod_Spec"},
340		},
341	}
342
343	n := &NamesMatch{}
344	for _, tc := range tcs {
345		if violations, _ := n.Validate(tc.t); !reflect.DeepEqual(violations, tc.expected) {
346			t.Errorf("unexpected validation result: test name %v, want: %v, got: %v",
347				tc.name, tc.expected, violations)
348		}
349	}
350}
351
352// TestRuleName tests the Name of API rule. This is to expand test coverage
353func TestRuleName(t *testing.T) {
354	ruleName := "names_match"
355	n := &NamesMatch{}
356	if n.Name() != ruleName {
357		t.Errorf("unexpected API rule name: want: %v, got: %v", ruleName, n.Name())
358	}
359}
360