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 }