1package runtime
2
3import (
4	"fmt"
5	"reflect"
6	"strings"
7	"testing"
8
9	"github.com/grpc-ecosystem/grpc-gateway/utilities"
10)
11
12const (
13	validVersion = 1
14	anything     = 0
15)
16
17func TestNewPattern(t *testing.T) {
18	for _, spec := range []struct {
19		ops  []int
20		pool []string
21		verb string
22
23		stackSizeWant, tailLenWant int
24	}{
25		{},
26		{
27			ops:           []int{int(utilities.OpNop), anything},
28			stackSizeWant: 0,
29			tailLenWant:   0,
30		},
31		{
32			ops:           []int{int(utilities.OpPush), anything},
33			stackSizeWant: 1,
34			tailLenWant:   0,
35		},
36		{
37			ops:           []int{int(utilities.OpLitPush), 0},
38			pool:          []string{"abc"},
39			stackSizeWant: 1,
40			tailLenWant:   0,
41		},
42		{
43			ops:           []int{int(utilities.OpPushM), anything},
44			stackSizeWant: 1,
45			tailLenWant:   0,
46		},
47		{
48			ops: []int{
49				int(utilities.OpPush), anything,
50				int(utilities.OpConcatN), 1,
51			},
52			stackSizeWant: 1,
53			tailLenWant:   0,
54		},
55		{
56			ops: []int{
57				int(utilities.OpPush), anything,
58				int(utilities.OpConcatN), 1,
59				int(utilities.OpCapture), 0,
60			},
61			pool:          []string{"abc"},
62			stackSizeWant: 1,
63			tailLenWant:   0,
64		},
65		{
66			ops: []int{
67				int(utilities.OpPush), anything,
68				int(utilities.OpLitPush), 0,
69				int(utilities.OpLitPush), 1,
70				int(utilities.OpPushM), anything,
71				int(utilities.OpConcatN), 2,
72				int(utilities.OpCapture), 2,
73			},
74			pool:          []string{"lit1", "lit2", "var1"},
75			stackSizeWant: 4,
76			tailLenWant:   0,
77		},
78		{
79			ops: []int{
80				int(utilities.OpPushM), anything,
81				int(utilities.OpConcatN), 1,
82				int(utilities.OpCapture), 2,
83				int(utilities.OpLitPush), 0,
84				int(utilities.OpLitPush), 1,
85			},
86			pool:          []string{"lit1", "lit2", "var1"},
87			stackSizeWant: 2,
88			tailLenWant:   2,
89		},
90		{
91			ops: []int{
92				int(utilities.OpLitPush), 0,
93				int(utilities.OpLitPush), 1,
94				int(utilities.OpPushM), anything,
95				int(utilities.OpLitPush), 2,
96				int(utilities.OpConcatN), 3,
97				int(utilities.OpLitPush), 3,
98				int(utilities.OpCapture), 4,
99			},
100			pool:          []string{"lit1", "lit2", "lit3", "lit4", "var1"},
101			stackSizeWant: 4,
102			tailLenWant:   2,
103		},
104		{
105			ops:           []int{int(utilities.OpLitPush), 0},
106			pool:          []string{"abc"},
107			verb:          "LOCK",
108			stackSizeWant: 1,
109			tailLenWant:   0,
110		},
111	} {
112		pat, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb)
113		if err != nil {
114			t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, spec.verb, err)
115			continue
116		}
117		if got, want := pat.stacksize, spec.stackSizeWant; got != want {
118			t.Errorf("pat.stacksize = %d; want %d", got, want)
119		}
120		if got, want := pat.tailLen, spec.tailLenWant; got != want {
121			t.Errorf("pat.stacksize = %d; want %d", got, want)
122		}
123	}
124}
125
126func TestNewPatternWithWrongOp(t *testing.T) {
127	for _, spec := range []struct {
128		ops  []int
129		pool []string
130		verb string
131	}{
132		{
133			// op code out of bound
134			ops: []int{-1, anything},
135		},
136		{
137			// op code out of bound
138			ops: []int{int(utilities.OpEnd), 0},
139		},
140		{
141			// odd number of items
142			ops: []int{int(utilities.OpPush)},
143		},
144		{
145			// negative index
146			ops:  []int{int(utilities.OpLitPush), -1},
147			pool: []string{"abc"},
148		},
149		{
150			// index out of bound
151			ops:  []int{int(utilities.OpLitPush), 1},
152			pool: []string{"abc"},
153		},
154		{
155			// negative # of segments
156			ops:  []int{int(utilities.OpConcatN), -1},
157			pool: []string{"abc"},
158		},
159		{
160			// negative index
161			ops:  []int{int(utilities.OpCapture), -1},
162			pool: []string{"abc"},
163		},
164		{
165			// index out of bound
166			ops:  []int{int(utilities.OpCapture), 1},
167			pool: []string{"abc"},
168		},
169		{
170			// pushM appears twice
171			ops: []int{
172				int(utilities.OpPushM), anything,
173				int(utilities.OpLitPush), 0,
174				int(utilities.OpPushM), anything,
175			},
176			pool: []string{"abc"},
177		},
178	} {
179		_, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb)
180		if err == nil {
181			t.Errorf("NewPattern(%d, %v, %q, %q) succeeded; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, ErrInvalidPattern)
182			continue
183		}
184		if err != ErrInvalidPattern {
185			t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, err, ErrInvalidPattern)
186			continue
187		}
188	}
189}
190
191func TestNewPatternWithStackUnderflow(t *testing.T) {
192	for _, spec := range []struct {
193		ops  []int
194		pool []string
195		verb string
196	}{
197		{
198			ops: []int{int(utilities.OpConcatN), 1},
199		},
200		{
201			ops:  []int{int(utilities.OpCapture), 0},
202			pool: []string{"abc"},
203		},
204	} {
205		_, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb)
206		if err == nil {
207			t.Errorf("NewPattern(%d, %v, %q, %q) succeeded; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, ErrInvalidPattern)
208			continue
209		}
210		if err != ErrInvalidPattern {
211			t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, err, ErrInvalidPattern)
212			continue
213		}
214	}
215}
216
217func TestMatch(t *testing.T) {
218	for _, spec := range []struct {
219		ops  []int
220		pool []string
221		verb string
222
223		match    []string
224		notMatch []string
225	}{
226		{
227			match:    []string{""},
228			notMatch: []string{"example"},
229		},
230		{
231			ops:      []int{int(utilities.OpNop), anything},
232			match:    []string{""},
233			notMatch: []string{"example", "path/to/example"},
234		},
235		{
236			ops:      []int{int(utilities.OpPush), anything},
237			match:    []string{"abc", "def"},
238			notMatch: []string{"", "abc/def"},
239		},
240		{
241			ops:      []int{int(utilities.OpLitPush), 0},
242			pool:     []string{"v1"},
243			match:    []string{"v1"},
244			notMatch: []string{"", "v2"},
245		},
246		{
247			ops:   []int{int(utilities.OpPushM), anything},
248			match: []string{"", "abc", "abc/def", "abc/def/ghi"},
249		},
250		{
251			ops: []int{
252				int(utilities.OpPushM), anything,
253				int(utilities.OpLitPush), 0,
254			},
255			pool:  []string{"tail"},
256			match: []string{"tail", "abc/tail", "abc/def/tail"},
257			notMatch: []string{
258				"", "abc", "abc/def",
259				"tail/extra", "abc/tail/extra", "abc/def/tail/extra",
260			},
261		},
262		{
263			ops: []int{
264				int(utilities.OpLitPush), 0,
265				int(utilities.OpLitPush), 1,
266				int(utilities.OpPush), anything,
267				int(utilities.OpConcatN), 1,
268				int(utilities.OpCapture), 2,
269			},
270			pool:  []string{"v1", "bucket", "name"},
271			match: []string{"v1/bucket/my-bucket", "v1/bucket/our-bucket"},
272			notMatch: []string{
273				"",
274				"v1",
275				"v1/bucket",
276				"v2/bucket/my-bucket",
277				"v1/pubsub/my-topic",
278			},
279		},
280		{
281			ops: []int{
282				int(utilities.OpLitPush), 0,
283				int(utilities.OpLitPush), 1,
284				int(utilities.OpPushM), anything,
285				int(utilities.OpConcatN), 2,
286				int(utilities.OpCapture), 2,
287			},
288			pool: []string{"v1", "o", "name"},
289			match: []string{
290				"v1/o",
291				"v1/o/my-bucket",
292				"v1/o/our-bucket",
293				"v1/o/my-bucket/dir",
294				"v1/o/my-bucket/dir/dir2",
295				"v1/o/my-bucket/dir/dir2/obj",
296			},
297			notMatch: []string{
298				"",
299				"v1",
300				"v2/o/my-bucket",
301				"v1/b/my-bucket",
302			},
303		},
304		{
305			ops: []int{
306				int(utilities.OpLitPush), 0,
307				int(utilities.OpLitPush), 1,
308				int(utilities.OpPush), anything,
309				int(utilities.OpConcatN), 2,
310				int(utilities.OpCapture), 2,
311				int(utilities.OpLitPush), 3,
312				int(utilities.OpPush), anything,
313				int(utilities.OpConcatN), 1,
314				int(utilities.OpCapture), 4,
315			},
316			pool: []string{"v2", "b", "name", "o", "oname"},
317			match: []string{
318				"v2/b/my-bucket/o/obj",
319				"v2/b/our-bucket/o/obj",
320				"v2/b/my-bucket/o/dir",
321			},
322			notMatch: []string{
323				"",
324				"v2",
325				"v2/b",
326				"v2/b/my-bucket",
327				"v2/b/my-bucket/o",
328			},
329		},
330		{
331			ops:      []int{int(utilities.OpLitPush), 0},
332			pool:     []string{"v1"},
333			verb:     "LOCK",
334			match:    []string{"v1:LOCK"},
335			notMatch: []string{"v1", "LOCK"},
336		},
337	} {
338		pat, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb)
339		if err != nil {
340			t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, spec.verb, err)
341			continue
342		}
343
344		for _, path := range spec.match {
345			_, err = pat.Match(segments(path))
346			if err != nil {
347				t.Errorf("pat.Match(%q) failed with %v; want success; pattern = (%v, %q)", path, err, spec.ops, spec.pool)
348			}
349		}
350
351		for _, path := range spec.notMatch {
352			_, err = pat.Match(segments(path))
353			if err == nil {
354				t.Errorf("pat.Match(%q) succeeded; want failure with %v; pattern = (%v, %q)", path, ErrNotMatch, spec.ops, spec.pool)
355				continue
356			}
357			if err != ErrNotMatch {
358				t.Errorf("pat.Match(%q) failed with %v; want failure with %v; pattern = (%v, %q)", spec.notMatch, err, ErrNotMatch, spec.ops, spec.pool)
359			}
360		}
361	}
362}
363
364func TestMatchWithBinding(t *testing.T) {
365	for _, spec := range []struct {
366		ops  []int
367		pool []string
368		path string
369		verb string
370
371		want map[string]string
372	}{
373		{
374			want: make(map[string]string),
375		},
376		{
377			ops:  []int{int(utilities.OpNop), anything},
378			want: make(map[string]string),
379		},
380		{
381			ops:  []int{int(utilities.OpPush), anything},
382			path: "abc",
383			want: make(map[string]string),
384		},
385		{
386			ops:  []int{int(utilities.OpPush), anything},
387			verb: "LOCK",
388			path: "abc:LOCK",
389			want: make(map[string]string),
390		},
391		{
392			ops:  []int{int(utilities.OpLitPush), 0},
393			pool: []string{"endpoint"},
394			path: "endpoint",
395			want: make(map[string]string),
396		},
397		{
398			ops:  []int{int(utilities.OpPushM), anything},
399			path: "abc/def/ghi",
400			want: make(map[string]string),
401		},
402		{
403			ops: []int{
404				int(utilities.OpLitPush), 0,
405				int(utilities.OpLitPush), 1,
406				int(utilities.OpPush), anything,
407				int(utilities.OpConcatN), 1,
408				int(utilities.OpCapture), 2,
409			},
410			pool: []string{"v1", "bucket", "name"},
411			path: "v1/bucket/my-bucket",
412			want: map[string]string{
413				"name": "my-bucket",
414			},
415		},
416		{
417			ops: []int{
418				int(utilities.OpLitPush), 0,
419				int(utilities.OpLitPush), 1,
420				int(utilities.OpPush), anything,
421				int(utilities.OpConcatN), 1,
422				int(utilities.OpCapture), 2,
423			},
424			pool: []string{"v1", "bucket", "name"},
425			verb: "LOCK",
426			path: "v1/bucket/my-bucket:LOCK",
427			want: map[string]string{
428				"name": "my-bucket",
429			},
430		},
431		{
432			ops: []int{
433				int(utilities.OpLitPush), 0,
434				int(utilities.OpLitPush), 1,
435				int(utilities.OpPushM), anything,
436				int(utilities.OpConcatN), 2,
437				int(utilities.OpCapture), 2,
438			},
439			pool: []string{"v1", "o", "name"},
440			path: "v1/o/my-bucket/dir/dir2/obj",
441			want: map[string]string{
442				"name": "o/my-bucket/dir/dir2/obj",
443			},
444		},
445		{
446			ops: []int{
447				int(utilities.OpLitPush), 0,
448				int(utilities.OpLitPush), 1,
449				int(utilities.OpPushM), anything,
450				int(utilities.OpLitPush), 2,
451				int(utilities.OpConcatN), 3,
452				int(utilities.OpCapture), 4,
453				int(utilities.OpLitPush), 3,
454			},
455			pool: []string{"v1", "o", ".ext", "tail", "name"},
456			path: "v1/o/my-bucket/dir/dir2/obj/.ext/tail",
457			want: map[string]string{
458				"name": "o/my-bucket/dir/dir2/obj/.ext",
459			},
460		},
461		{
462			ops: []int{
463				int(utilities.OpLitPush), 0,
464				int(utilities.OpLitPush), 1,
465				int(utilities.OpPush), anything,
466				int(utilities.OpConcatN), 2,
467				int(utilities.OpCapture), 2,
468				int(utilities.OpLitPush), 3,
469				int(utilities.OpPush), anything,
470				int(utilities.OpConcatN), 1,
471				int(utilities.OpCapture), 4,
472			},
473			pool: []string{"v2", "b", "name", "o", "oname"},
474			path: "v2/b/my-bucket/o/obj",
475			want: map[string]string{
476				"name":  "b/my-bucket",
477				"oname": "obj",
478			},
479		},
480	} {
481		pat, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb)
482		if err != nil {
483			t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, spec.verb, err)
484			continue
485		}
486
487		got, err := pat.Match(segments(spec.path))
488		if err != nil {
489			t.Errorf("pat.Match(%q) failed with %v; want success; pattern = (%v, %q)", spec.path, err, spec.ops, spec.pool)
490		}
491		if !reflect.DeepEqual(got, spec.want) {
492			t.Errorf("pat.Match(%q) = %q; want %q; pattern = (%v, %q)", spec.path, got, spec.want, spec.ops, spec.pool)
493		}
494	}
495}
496
497func segments(path string) (components []string, verb string) {
498	if path == "" {
499		return nil, ""
500	}
501	components = strings.Split(path, "/")
502	l := len(components)
503	c := components[l-1]
504	if idx := strings.LastIndex(c, ":"); idx >= 0 {
505		components[l-1], verb = c[:idx], c[idx+1:]
506	}
507	return components, verb
508}
509
510func TestPatternString(t *testing.T) {
511	for _, spec := range []struct {
512		ops  []int
513		pool []string
514
515		want string
516	}{
517		{
518			want: "/",
519		},
520		{
521			ops:  []int{int(utilities.OpNop), anything},
522			want: "/",
523		},
524		{
525			ops:  []int{int(utilities.OpPush), anything},
526			want: "/*",
527		},
528		{
529			ops:  []int{int(utilities.OpLitPush), 0},
530			pool: []string{"endpoint"},
531			want: "/endpoint",
532		},
533		{
534			ops:  []int{int(utilities.OpPushM), anything},
535			want: "/**",
536		},
537		{
538			ops: []int{
539				int(utilities.OpPush), anything,
540				int(utilities.OpConcatN), 1,
541			},
542			want: "/*",
543		},
544		{
545			ops: []int{
546				int(utilities.OpPush), anything,
547				int(utilities.OpConcatN), 1,
548				int(utilities.OpCapture), 0,
549			},
550			pool: []string{"name"},
551			want: "/{name=*}",
552		},
553		{
554			ops: []int{
555				int(utilities.OpLitPush), 0,
556				int(utilities.OpLitPush), 1,
557				int(utilities.OpPush), anything,
558				int(utilities.OpConcatN), 2,
559				int(utilities.OpCapture), 2,
560				int(utilities.OpLitPush), 3,
561				int(utilities.OpPushM), anything,
562				int(utilities.OpLitPush), 4,
563				int(utilities.OpConcatN), 3,
564				int(utilities.OpCapture), 6,
565				int(utilities.OpLitPush), 5,
566			},
567			pool: []string{"v1", "buckets", "bucket_name", "objects", ".ext", "tail", "name"},
568			want: "/v1/{bucket_name=buckets/*}/{name=objects/**/.ext}/tail",
569		},
570	} {
571		p, err := NewPattern(validVersion, spec.ops, spec.pool, "")
572		if err != nil {
573			t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, "", err)
574			continue
575		}
576		if got, want := p.String(), spec.want; got != want {
577			t.Errorf("%#v.String() = %q; want %q", p, got, want)
578		}
579
580		verb := "LOCK"
581		p, err = NewPattern(validVersion, spec.ops, spec.pool, verb)
582		if err != nil {
583			t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, verb, err)
584			continue
585		}
586		if got, want := p.String(), fmt.Sprintf("%s:%s", spec.want, verb); got != want {
587			t.Errorf("%#v.String() = %q; want %q", p, got, want)
588		}
589	}
590}
591