github.com/erda-project/erda-infra@v1.0.9/pkg/transport/http/runtime/pattern_test.go (about)

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