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  }