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 }