1package structs
2
3import (
4	"sort"
5	"strings"
6	"testing"
7
8	"github.com/stretchr/testify/assert"
9)
10
11func TestIntentionGetACLPrefix(t *testing.T) {
12	cases := []struct {
13		Name     string
14		Input    *Intention
15		Expected string
16	}{
17		{
18			"unset name",
19			&Intention{DestinationName: ""},
20			"",
21		},
22
23		{
24			"set name",
25			&Intention{DestinationName: "fo"},
26			"fo",
27		},
28	}
29
30	for _, tc := range cases {
31		t.Run(tc.Name, func(t *testing.T) {
32			actual, ok := tc.Input.GetACLPrefix()
33			if tc.Expected == "" {
34				if !ok {
35					return
36				}
37
38				t.Fatal("should not be ok")
39			}
40
41			if actual != tc.Expected {
42				t.Fatalf("bad: %q", actual)
43			}
44		})
45	}
46}
47
48func TestIntentionValidate(t *testing.T) {
49	cases := []struct {
50		Name   string
51		Modify func(*Intention)
52		Err    string
53	}{
54		{
55			"long description",
56			func(x *Intention) {
57				x.Description = strings.Repeat("x", metaValueMaxLength+1)
58			},
59			"description exceeds",
60		},
61
62		{
63			"no action set",
64			func(x *Intention) { x.Action = "" },
65			"action must be set",
66		},
67
68		{
69			"no SourceNS",
70			func(x *Intention) { x.SourceNS = "" },
71			"SourceNS must be set",
72		},
73
74		{
75			"no SourceName",
76			func(x *Intention) { x.SourceName = "" },
77			"SourceName must be set",
78		},
79
80		{
81			"no DestinationNS",
82			func(x *Intention) { x.DestinationNS = "" },
83			"DestinationNS must be set",
84		},
85
86		{
87			"no DestinationName",
88			func(x *Intention) { x.DestinationName = "" },
89			"DestinationName must be set",
90		},
91
92		{
93			"SourceNS partial wildcard",
94			func(x *Intention) { x.SourceNS = "foo*" },
95			"partial value",
96		},
97
98		{
99			"SourceName partial wildcard",
100			func(x *Intention) { x.SourceName = "foo*" },
101			"partial value",
102		},
103
104		{
105			"SourceName exact following wildcard",
106			func(x *Intention) {
107				x.SourceNS = "*"
108				x.SourceName = "foo"
109			},
110			"follow wildcard",
111		},
112
113		{
114			"DestinationNS partial wildcard",
115			func(x *Intention) { x.DestinationNS = "foo*" },
116			"partial value",
117		},
118
119		{
120			"DestinationName partial wildcard",
121			func(x *Intention) { x.DestinationName = "foo*" },
122			"partial value",
123		},
124
125		{
126			"DestinationName exact following wildcard",
127			func(x *Intention) {
128				x.DestinationNS = "*"
129				x.DestinationName = "foo"
130			},
131			"follow wildcard",
132		},
133
134		{
135			"SourceType is not set",
136			func(x *Intention) { x.SourceType = "" },
137			"SourceType must",
138		},
139
140		{
141			"SourceType is other",
142			func(x *Intention) { x.SourceType = IntentionSourceType("other") },
143			"SourceType must",
144		},
145	}
146
147	for _, tc := range cases {
148		t.Run(tc.Name, func(t *testing.T) {
149			assert := assert.New(t)
150			ixn := TestIntention(t)
151			tc.Modify(ixn)
152
153			err := ixn.Validate()
154			assert.Equal(err != nil, tc.Err != "", err)
155			if err == nil {
156				return
157			}
158
159			assert.Contains(strings.ToLower(err.Error()), strings.ToLower(tc.Err))
160		})
161	}
162}
163
164func TestIntentionPrecedenceSorter(t *testing.T) {
165	cases := []struct {
166		Name     string
167		Input    [][]string // SrcNS, SrcN, DstNS, DstN
168		Expected [][]string // Same structure as Input
169	}{
170		{
171			"exhaustive list",
172			[][]string{
173				{"*", "*", "exact", "*"},
174				{"*", "*", "*", "*"},
175				{"exact", "*", "exact", "exact"},
176				{"*", "*", "exact", "exact"},
177				{"exact", "exact", "*", "*"},
178				{"exact", "exact", "exact", "exact"},
179				{"exact", "exact", "exact", "*"},
180				{"exact", "*", "exact", "*"},
181				{"exact", "*", "*", "*"},
182			},
183			[][]string{
184				{"exact", "exact", "exact", "exact"},
185				{"exact", "*", "exact", "exact"},
186				{"*", "*", "exact", "exact"},
187				{"exact", "exact", "exact", "*"},
188				{"exact", "*", "exact", "*"},
189				{"*", "*", "exact", "*"},
190				{"exact", "exact", "*", "*"},
191				{"exact", "*", "*", "*"},
192				{"*", "*", "*", "*"},
193			},
194		},
195		{
196			"tiebreak deterministically",
197			[][]string{
198				{"a", "*", "a", "b"},
199				{"a", "*", "a", "a"},
200				{"b", "a", "a", "a"},
201				{"a", "b", "a", "a"},
202				{"a", "a", "b", "a"},
203				{"a", "a", "a", "b"},
204				{"a", "a", "a", "a"},
205			},
206			[][]string{
207				// Exact matches first in lexicographical order (arbitrary but
208				// deterministic)
209				{"a", "a", "a", "a"},
210				{"a", "a", "a", "b"},
211				{"a", "a", "b", "a"},
212				{"a", "b", "a", "a"},
213				{"b", "a", "a", "a"},
214				// Wildcards next, lexicographical
215				{"a", "*", "a", "a"},
216				{"a", "*", "a", "b"},
217			},
218		},
219	}
220
221	for _, tc := range cases {
222		t.Run(tc.Name, func(t *testing.T) {
223			assert := assert.New(t)
224
225			var input Intentions
226			for _, v := range tc.Input {
227				input = append(input, &Intention{
228					SourceNS:        v[0],
229					SourceName:      v[1],
230					DestinationNS:   v[2],
231					DestinationName: v[3],
232				})
233			}
234
235			// Set all the precedence values
236			for _, ixn := range input {
237				ixn.UpdatePrecedence()
238			}
239
240			// Sort
241			sort.Sort(IntentionPrecedenceSorter(input))
242
243			// Get back into a comparable form
244			var actual [][]string
245			for _, v := range input {
246				actual = append(actual, []string{
247					v.SourceNS,
248					v.SourceName,
249					v.DestinationNS,
250					v.DestinationName,
251				})
252			}
253			assert.Equal(tc.Expected, actual)
254		})
255	}
256}
257