github.com/grpc-ecosystem/grpc-gateway/v2@v2.19.1/runtime/pattern_test.go (about)

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