github.com/avenga/couper@v1.12.2/config/configload/load_test.go (about) 1 package configload_test 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "strings" 8 "testing" 9 10 "github.com/avenga/couper/cache" 11 "github.com/avenga/couper/config/configload" 12 "github.com/avenga/couper/config/runtime" 13 "github.com/avenga/couper/errors" 14 "github.com/avenga/couper/internal/test" 15 ) 16 17 func TestPrepareBackendRefineAttributes(t *testing.T) { 18 config := `server { 19 endpoint "/" { 20 request { 21 backend "ref" { 22 %s = env.VAR 23 } 24 } 25 } 26 } 27 28 definitions { 29 backend "ref" { 30 origin = "http://localhost" 31 } 32 }` 33 34 for _, attribute := range []string{ 35 "disable_certificate_validation", 36 "disable_connection_reuse", 37 "http2", 38 "max_connections", 39 } { 40 _, err := configload.LoadBytes([]byte(fmt.Sprintf(config, attribute)), "test.hcl") 41 if err == nil { 42 t.Fatal("expected an error") 43 } 44 45 if !strings.HasSuffix(err.Error(), 46 fmt.Sprintf("backend reference: refinement for %q is not permitted; ", attribute)) { 47 t.Error(err) 48 } 49 } 50 } 51 52 func TestPrepareBackendRefineBlocks(t *testing.T) { 53 config := `server { 54 endpoint "/" { 55 request { 56 backend "ref" { 57 %s 58 } 59 } 60 } 61 } 62 63 definitions { 64 backend "ref" { 65 origin = "http://localhost" 66 } 67 }` 68 69 tests := []struct { 70 name string 71 hcl string 72 }{ 73 { 74 "openapi", 75 `openapi { file = ""}`, 76 }, 77 { 78 "oauth2", 79 `oauth2 { 80 grant_type = "client_credentials" 81 token_endpoint = "" 82 client_id = "asdf" 83 client_secret = "asdf" 84 }`, 85 }, 86 { 87 "beta_health", 88 `beta_health { 89 }`, 90 }, 91 { 92 "beta_token_request", 93 `beta_token_request { 94 token = "asdf" 95 ttl = "1s" 96 }`, 97 }, 98 { 99 "beta_token_request", 100 `beta_token_request "name" { 101 token = "asdf" 102 ttl = "1s" 103 }`, 104 }, 105 { 106 "beta_rate_limit", 107 `beta_rate_limit { 108 per_period = 10 109 period = "10s" 110 }`, 111 }, 112 } 113 for _, tt := range tests { 114 t.Run(tt.name, func(subT *testing.T) { 115 _, err := configload.LoadBytes([]byte(fmt.Sprintf(config, tt.hcl)), "test.hcl") 116 if err == nil { 117 subT.Error("expected an error") 118 return 119 } 120 121 if !strings.HasSuffix(err.Error(), 122 fmt.Sprintf("backend reference: refinement for %q is not permitted; ", tt.name)) { 123 subT.Error(err) 124 } 125 }) 126 } 127 } 128 129 func TestHealthCheck(t *testing.T) { 130 tests := []struct { 131 name string 132 hcl string 133 error string 134 }{ 135 { 136 "Bad interval", 137 `interval = "10sec"`, 138 `configuration error: foo: time: unknown unit "sec" in duration "10sec"`, 139 }, 140 { 141 "Bad timeout", 142 `timeout = 1`, 143 `configuration error: foo: time: missing unit in duration "1"`, 144 }, 145 { 146 "Bad threshold", 147 `failure_threshold = -1`, 148 "couper.hcl:13,29-30: Unsuitable value type; Unsuitable value: value must be a whole number, between 0 and 18446744073709551615 inclusive", 149 }, 150 { 151 "Bad expected status", 152 `expected_status = 200`, 153 "couper.hcl:13,27-30: Unsuitable value type; Unsuitable value: list of number required", 154 }, 155 { 156 "OK", 157 `failure_threshold = 3 158 timeout = "3s" 159 interval = "5s" 160 expected_text = 123 161 expected_status = [200, 418]`, 162 "", 163 }, 164 } 165 166 logger, _ := test.NewLogger() 167 log := logger.WithContext(context.TODO()) 168 169 template := ` 170 server { 171 endpoint "/" { 172 proxy { 173 backend = "foo" 174 } 175 } 176 } 177 definitions { 178 backend "foo" { 179 origin = "..." 180 beta_health { 181 %% 182 } 183 } 184 }` 185 186 for _, tt := range tests { 187 t.Run(tt.name, func(subT *testing.T) { 188 conf, err := configload.LoadBytes([]byte(strings.Replace(template, "%%", tt.hcl, -1)), "couper.hcl") 189 190 closeCh := make(chan struct{}) 191 defer close(closeCh) 192 memStore := cache.New(log, closeCh) 193 194 if conf != nil { 195 ctx, cancel := context.WithCancel(conf.Context) 196 conf.Context = ctx 197 defer cancel() 198 199 _, err = runtime.NewServerConfiguration(conf, log, memStore) 200 } 201 202 var errorMsg = "" 203 if err != nil { 204 if gErr, ok := err.(errors.GoError); ok { 205 errorMsg = gErr.LogError() 206 } else { 207 errorMsg = err.Error() 208 } 209 } 210 211 if tt.error != errorMsg { 212 subT.Errorf("%q: Unexpected configuration error:\n\tWant: %q\n\tGot: %q", tt.name, tt.error, errorMsg) 213 } 214 }) 215 } 216 } 217 218 func TestRateLimit(t *testing.T) { 219 tests := []struct { 220 name string 221 hcl string 222 error string 223 }{ 224 { 225 "missing per_period", 226 ``, 227 `Missing required argument; The argument "per_period" is required`, 228 }, 229 { 230 "missing period", 231 `per_period = 10`, 232 `Missing required argument; The argument "period" is required`, 233 }, 234 { 235 "OK", 236 `period = "1m" 237 per_period = 10`, 238 "", 239 }, 240 } 241 242 logger, _ := test.NewLogger() 243 log := logger.WithContext(context.TODO()) 244 245 template := ` 246 server {} 247 definitions { 248 backend "foo" { 249 beta_rate_limit { 250 %s 251 } 252 } 253 }` 254 255 for _, tt := range tests { 256 t.Run(tt.name, func(subT *testing.T) { 257 conf, err := configload.LoadBytes([]byte(fmt.Sprintf(template, tt.hcl)), "couper.hcl") 258 259 closeCh := make(chan struct{}) 260 defer close(closeCh) 261 memStore := cache.New(log, closeCh) 262 263 if conf != nil { 264 ctx, cancel := context.WithCancel(conf.Context) 265 conf.Context = ctx 266 defer cancel() 267 268 _, err = runtime.NewServerConfiguration(conf, log, memStore) 269 } 270 271 var errorMsg = "" 272 if err != nil { 273 errorMsg = err.Error() 274 } 275 276 if !strings.Contains(errorMsg, tt.error) { 277 subT.Errorf("%q: Unexpected configuration error:\n\tWant: %q\n\tGot: %q", tt.name, tt.error, errorMsg) 278 } 279 }) 280 } 281 } 282 283 func TestEndpointPaths(t *testing.T) { 284 tests := []struct { 285 name string 286 serverBase string 287 apiBase string 288 endpoint string 289 expected string 290 }{ 291 {"only /", "", "", "/", "/"}, 292 {"simple path", "", "", "/pa/th", "/pa/th"}, 293 {"trailing /", "", "", "/pa/th/", "/pa/th/"}, 294 {"double /", "", "", "//", "//"}, 295 {"double /", "", "", "//path", "//path"}, 296 {"double /", "", "", "/pa//th", "/pa//th"}, 297 298 {"param", "", "", "/{param}", "/{param}"}, 299 300 {"server base_path /", "/", "", "/", "/"}, 301 {"server base_path /", "/", "", "/path", "/path"}, 302 {"server base_path", "/server", "", "/path", "/server/path"}, 303 {"server base_path with / endpoint", "/server", "", "/", "/server"}, 304 {"server base_path missing /", "server", "", "/path", "/server/path"}, 305 {"server base_path trailing /", "/server/", "", "/path", "/server/path"}, 306 {"server base_path double /", "/server", "", "//path", "/server//path"}, 307 {"server base_path trailing + double /", "/server/", "", "//path", "/server//path"}, 308 309 {"api base_path /", "", "/", "/", "/"}, 310 {"api base_path /", "", "/", "/path", "/path"}, 311 {"api base_path", "", "/api", "/path", "/api/path"}, 312 {"api base_path with / endpoint", "", "/api", "/", "/api"}, 313 {"api base_path missing /", "", "api", "/path", "/api/path"}, 314 {"api base_path trailing /", "", "/api/", "/path", "/api/path"}, 315 {"api base_path double /", "", "/api", "//path", "/api//path"}, 316 {"api base_path trailing + double /", "/api/", "", "//path", "/api//path"}, 317 318 {"server + api base_path /", "/", "/", "/", "/"}, 319 {"server + api base_path", "/server", "/api", "/", "/server/api"}, 320 {"server + api base_path", "/server", "/api", "/path", "/server/api/path"}, 321 {"server + api base_path missing /", "server", "api", "/", "/server/api"}, 322 } 323 324 logger, _ := test.NewLogger() 325 log := logger.WithContext(context.TODO()) 326 327 template := ` 328 server { 329 base_path = "%s" 330 api { 331 base_path = "%s" 332 endpoint "%s" { 333 response {} 334 } 335 } 336 }` 337 338 for _, tt := range tests { 339 t.Run(tt.name, func(subT *testing.T) { 340 configBytes := []byte(fmt.Sprintf(template, tt.serverBase, tt.apiBase, tt.endpoint)) 341 config, err := configload.LoadBytes(configBytes, "couper.hcl") 342 343 closeCh := make(chan struct{}) 344 defer close(closeCh) 345 memStore := cache.New(log, closeCh) 346 347 var serverConfig runtime.ServerConfiguration 348 if err == nil { 349 serverConfig, err = runtime.NewServerConfiguration(config, log, memStore) 350 } 351 352 if err != nil { 353 subT.Errorf("%q: Unexpected configuration error:\n\tWant: <nil>\n\tGot: %q", tt.name, err) 354 return 355 } 356 357 var pattern string 358 for key := range serverConfig[8080]["*"].EndpointRoutes { 359 pattern = key 360 break 361 } 362 363 if pattern != tt.expected { 364 subT.Errorf("%q: Unexpected endpoint path:\n\tWant: %q\n\tGot: %q", tt.name, tt.expected, pattern) 365 } 366 }) 367 } 368 } 369 370 func TestEnvironmentBlocksWithoutEnvironment(t *testing.T) { 371 tests := []struct { 372 name string 373 hcl string 374 env string 375 want string 376 }{ 377 { 378 "no environment block, no setting", 379 ` 380 definitions {} 381 `, 382 "", 383 "configuration error: missing 'server' block", 384 }, 385 386 { 387 "environment block, but no setting", 388 ` 389 environment "foo" {} 390 server {} 391 `, 392 "", 393 `"environment" blocks found, but "COUPER_ENVIRONMENT" setting is missing`, 394 }, 395 { 396 "environment block & setting", 397 ` 398 environment "foo" { 399 server {} 400 } 401 `, 402 "bar", 403 "configuration error: missing 'server' block", 404 }, 405 { 406 "environment block & setting", 407 ` 408 server { 409 environment "foo" { 410 endpoint "/" {} 411 } 412 } 413 `, 414 "foo", 415 "missing 'default' proxy or request block, or a response definition", 416 }, 417 { 418 "environment block & default setting", 419 ` 420 environment "foo" { 421 server {} 422 } 423 settings { 424 environment = "bar" 425 } 426 `, 427 "", 428 "configuration error: missing 'server' block", 429 }, 430 } 431 432 helper := test.New(t) 433 434 file, err := os.CreateTemp("", "tmpfile-") 435 helper.Must(err) 436 defer file.Close() 437 defer os.Remove(file.Name()) 438 439 for _, tt := range tests { 440 t.Run(tt.name, func(subT *testing.T) { 441 config := []byte(tt.hcl) 442 err := os.Truncate(file.Name(), 0) 443 helper.Must(err) 444 _, err = file.Seek(0, 0) 445 helper.Must(err) 446 _, err = file.Write(config) 447 helper.Must(err) 448 449 _, err = configload.LoadFile(file.Name(), tt.env) 450 if err == nil && tt.want != "" { 451 subT.Errorf("Missing expected error:\nWant:\t%q\nGot:\tnil", tt.want) 452 return 453 } 454 455 if err != nil { 456 message := err.Error() 457 if !strings.Contains(message, tt.want) { 458 subT.Errorf("Unexpected error message:\nWant:\t%q\nGot:\t%q", tt.want, message) 459 return 460 } 461 } 462 }) 463 } 464 } 465 466 func TestConfigErrors(t *testing.T) { 467 tests := []struct { 468 name string 469 hcl string 470 error string 471 }{ 472 { 473 "websockets attribute and block", 474 `server { 475 endpoint "/" { 476 proxy { 477 backend = "foo" 478 websockets = true 479 websockets {} 480 } 481 } 482 }`, 483 "couper.hcl:5,10-27: either websockets attribute or block is allowed; ", 484 }, 485 { 486 "unlabeled proxy and request", 487 `server { 488 endpoint "/" { 489 proxy { 490 backend {} 491 } 492 request { 493 url = "http://foo/bar" 494 backend {} 495 } 496 } 497 }`, 498 `couper.hcl:6,16-9,9: proxy and request names (either default or explicitly set via label) must be unique: "default"; `, 499 }, 500 { 501 "unlabeled proxy and default labeled request", 502 `server { 503 endpoint "/" { 504 proxy { 505 backend {} 506 } 507 request "default" { 508 url = "http://foo/bar" 509 backend {} 510 } 511 } 512 }`, 513 `couper.hcl:6,26-9,9: proxy and request names (either default or explicitly set via label) must be unique: "default"; `, 514 }, 515 { 516 "default labeled proxy and unlabeled request", 517 `server { 518 endpoint "/" { 519 proxy "default" { 520 backend {} 521 } 522 request { 523 url = "http://foo/bar" 524 backend {} 525 } 526 } 527 }`, 528 `couper.hcl:6,16-9,9: proxy and request names (either default or explicitly set via label) must be unique: "default"; `, 529 }, 530 { 531 "labeled proxy and request", 532 `server { 533 endpoint "/" { 534 proxy "foo" { 535 backend {} 536 } 537 request "foo" { 538 url = "http://foo/bar" 539 backend {} 540 } 541 } 542 }`, 543 `couper.hcl:6,22-9,9: proxy and request names (either default or explicitly set via label) must be unique: "foo"; `, 544 }, 545 { 546 "undefined referenced proxy backend", 547 `server { 548 endpoint "/" { 549 proxy "foo" { 550 backend = "rs" 551 } 552 } 553 }`, 554 `couper.hcl:3,20-5,9: referenced backend "rs" is not defined; `, 555 }, 556 { 557 "undefined refined proxy backend", 558 `server { 559 endpoint "/" { 560 proxy "foo" { 561 backend "rs" {} 562 } 563 } 564 }`, 565 `couper.hcl:4,23-25: referenced backend "rs" is not defined; `, 566 }, 567 { 568 "undefined referenced oauth2 backend", 569 `server {} 570 definitions { 571 backend "foo" { 572 oauth2 { 573 token_endpoint = "https://as/token" 574 backend = "as" 575 grant_type = "client_credentials" 576 client_id = "asdf" 577 client_secret = "asdf" 578 } 579 } 580 }`, 581 `configuration error: referenced backend "as" is not defined`, 582 }, 583 { 584 "undefined refined oauth2 backend", 585 `server {} 586 definitions { 587 backend "foo" { 588 oauth2 { 589 token_endpoint = "https://as/token" 590 backend "as" {} 591 grant_type = "client_credentials" 592 client_id = "asdf" 593 client_secret = "asdf" 594 } 595 } 596 }`, 597 `configuration error: referenced backend "as" is not defined`, 598 }, 599 { 600 "undefined referenced token request backend", 601 `server {} 602 definitions { 603 backend "foo" { 604 beta_token_request { 605 url = "https://as/token" 606 backend = "as" 607 token = "asdf" 608 ttl = "1s" 609 } 610 } 611 }`, 612 `configuration error: referenced backend "as" is not defined`, 613 }, 614 { 615 "undefined refined token request backend", 616 `server {} 617 definitions { 618 backend "foo" { 619 beta_token_request { 620 url = "https://as/token" 621 backend = "as" 622 token = "asdf" 623 ttl = "1s" 624 } 625 } 626 }`, 627 `configuration error: referenced backend "as" is not defined`, 628 }, 629 { 630 "wrong environment_variables type", 631 `server {} 632 defaults { 633 environment_variables = "val" 634 }`, 635 "couper.hcl:3,30-35: environment_variables must be object type; ", 636 }, 637 { 638 "unsupported key scope traversal expression", 639 `server {} 640 defaults { 641 environment_variables = { 642 env.FOO = "val" 643 } 644 }`, 645 "couper.hcl:4,8-15: unsupported key scope traversal expression; ", 646 }, 647 { 648 "unsupported key template expression", 649 `server {} 650 defaults { 651 environment_variables = { 652 "key${1 + 0}" = "val" 653 } 654 }`, 655 "couper.hcl:4,8-21: unsupported key template expression; ", 656 }, 657 { 658 "unsupported key expression", 659 `server {} 660 defaults { 661 environment_variables = { 662 to_upper("key") = "val" 663 } 664 }`, 665 "couper.hcl:4,8-23: unsupported key expression; ", 666 }, 667 } 668 669 for _, tt := range tests { 670 t.Run(tt.name, func(subT *testing.T) { 671 _, err := configload.LoadBytes([]byte(tt.hcl), "couper.hcl") 672 673 var errorMsg = "" 674 if err != nil { 675 if gErr, ok := err.(errors.GoError); ok { 676 errorMsg = gErr.LogError() 677 } else { 678 errorMsg = err.Error() 679 } 680 } 681 682 if tt.error != errorMsg { 683 subT.Errorf("%q: Unexpected configuration error:\n\tWant: %q\n\tGot: %q", tt.name, tt.error, errorMsg) 684 } 685 }) 686 } 687 }