github.com/avenga/couper@v1.12.2/eval/context_test.go (about)

     1  package eval_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"io"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"reflect"
    10  	"testing"
    11  
    12  	"github.com/hashicorp/hcl/v2/hclsimple"
    13  	"github.com/zclconf/go-cty/cty"
    14  
    15  	"github.com/avenga/couper/config/configload"
    16  	"github.com/avenga/couper/config/request"
    17  	"github.com/avenga/couper/eval"
    18  	"github.com/avenga/couper/eval/buffer"
    19  	"github.com/avenga/couper/internal/seetie"
    20  	"github.com/avenga/couper/internal/test"
    21  	"github.com/avenga/couper/utils"
    22  )
    23  
    24  func TestNewHTTPContext(t *testing.T) {
    25  	newBeresp := func(br *http.Request) *http.Response {
    26  		return &http.Response{
    27  			StatusCode: http.StatusOK,
    28  			Status:     "OK",
    29  			Request:    br,
    30  			Proto:      br.Proto,
    31  			ProtoMajor: br.ProtoMajor,
    32  			ProtoMinor: br.ProtoMinor,
    33  		}
    34  	}
    35  
    36  	baseCtx := eval.NewDefaultContext()
    37  
    38  	tests := []struct {
    39  		name      string
    40  		reqMethod string
    41  		reqHeader http.Header
    42  		body      io.Reader
    43  		query     string
    44  		baseCtx   *eval.Context
    45  		hcl       string
    46  		want      http.Header
    47  	}{
    48  		{"Variables / POST", http.MethodPost, http.Header{"Content-Type": {"application/x-www-form-urlencoded"}}, bytes.NewBufferString(`user=hans`), "", baseCtx, `
    49  					post = request.form_body.user[0]
    50  					method = request.method
    51  		`, http.Header{"post": {"hans"}, "method": {http.MethodPost}}},
    52  		{"Variables / Query", http.MethodGet, http.Header{"User-Agent": {"test/v1"}}, nil, "?name=peter", baseCtx, `
    53  					query = request.query.name[0]
    54  					method = request.method
    55  					ua = request.headers.user-agent
    56  				`, http.Header{"query": {"peter"}, "method": {http.MethodGet}, "ua": {"test/v1"}}},
    57  		{"Variables / Headers", http.MethodGet, http.Header{"User-Agent": {"test/v1"}}, nil, "", baseCtx, `
    58  					ua = request.headers.user-agent
    59  					method = request.method
    60  				`, http.Header{"ua": {"test/v1"}, "method": {http.MethodGet}}},
    61  		{"Variables / PATCH /w json body", http.MethodPatch, http.Header{"Content-Type": {"application/json;charset=utf-8"}}, bytes.NewBufferString(`{
    62  			"obj_slice": [
    63  				{"no_title": 123},
    64  				{"title": "success"}
    65  			]}`), "", baseCtx, `
    66  			method = request.method
    67  			title = request.json_body.obj_slice[1].title
    68  		`, http.Header{"title": {"success"}, "method": {http.MethodPatch}}},
    69  		{"Variables / PATCH /w json body /wo CT header", http.MethodPatch, nil, bytes.NewBufferString(`{"slice": [1, 2, 3]}`), "", baseCtx, `
    70  			method = request.method
    71  			title = request.json_body.obj_slice
    72  		`, http.Header{"method": {http.MethodPatch}}},
    73  		{"Variables / GET /w json body", http.MethodGet, http.Header{"Content-Type": {"application/json"}}, bytes.NewBufferString(`{"slice": [1, 2, 3]}`), "", baseCtx, `
    74  			method = request.method
    75  			title = request.json_body.slice
    76  		`, http.Header{"title": {"1", "2", "3"}, "method": {http.MethodGet}}},
    77  		{"Variables / GET /w json body & missing attribute", http.MethodGet, http.Header{"Content-Type": {"application/json"}}, bytes.NewBufferString(`{"slice": [1, 2, 3]}`), "", baseCtx, `
    78  			method = request.method
    79  			title = request.json_body.slice.foo
    80  		`, http.Header{"method": {http.MethodGet}}},
    81  		{"Variables / GET /w json body & null value", http.MethodGet, http.Header{"Content-Type": {"application/json"}}, bytes.NewBufferString(`{"json": null}`), "", baseCtx, `
    82  			method = request.method
    83  			title = request.json_body.json
    84  		`, http.Header{"method": {http.MethodGet}, "title": nil}},
    85  	}
    86  
    87  	for _, tt := range tests {
    88  		t.Run(tt.name, func(subT *testing.T) {
    89  			helper := test.New(subT)
    90  
    91  			req := httptest.NewRequest(tt.reqMethod, "https://couper.io/"+tt.query, tt.body)
    92  			*req = *req.Clone(context.WithValue(req.Context(), request.Endpoint, "couper-proxy"))
    93  
    94  			for k, v := range tt.reqHeader {
    95  				req.Header[k] = v
    96  			}
    97  
    98  			bereq := req.Clone(context.Background())
    99  			beresp := newBeresp(bereq)
   100  
   101  			helper.Must(eval.SetGetBody(req, buffer.Request, 512))
   102  
   103  			ctx, _, _, _ := baseCtx.WithClientRequest(req).WithBeresp(beresp, cty.NilVal)
   104  			hclCtx := ctx.HCLContext()
   105  			hclCtx.Functions = nil // we are not interested in a functions test
   106  
   107  			var resultMap map[string]cty.Value
   108  			_ = hclsimple.Decode(tt.name+".hcl", []byte(tt.hcl), hclCtx, &resultMap)
   109  
   110  			for k, v := range tt.want {
   111  				cv, ok := resultMap[k]
   112  				if !ok {
   113  					subT.Errorf("Expected value for %q, got nothing", k)
   114  				}
   115  
   116  				result := seetie.ValueToStringSlice(cv)
   117  				if !reflect.DeepEqual(v, result) {
   118  					subT.Errorf("Expected %q, got: %#v, type: %#v", v, result, cv)
   119  				}
   120  			}
   121  		})
   122  	}
   123  }
   124  
   125  func TestDefaultEnvVariables(t *testing.T) {
   126  	tests := []struct {
   127  		name string
   128  		hcl  string
   129  		want map[string]cty.Value
   130  	}{
   131  		{
   132  			"test",
   133  			`
   134  			server "test" {
   135  			  endpoint "/" {
   136  				proxy {
   137  				  backend {
   138  					origin = env.ORIGIN
   139  					timeout = env.TIMEOUT
   140  				  }
   141  				}
   142  			  }
   143  			}
   144  
   145  			defaults {
   146  				environment_variables = {
   147  					ORIGIN = "FOO"
   148  					TIMEOUT = "42"
   149  					IGNORED = "bar"
   150  				}
   151  			}
   152  			`,
   153  			map[string]cty.Value{"ORIGIN": cty.StringVal("FOO"), "TIMEOUT": cty.StringVal("42")},
   154  		},
   155  		{
   156  			"no-environment_variables-block",
   157  			`
   158  			server "test" {
   159  			  endpoint "/" {
   160  				proxy {
   161  				  backend {
   162  					origin = env.ORIGIN
   163  					timeout = env.TIMEOUT
   164  				  }
   165  				}
   166  			  }
   167  			}
   168  
   169  			defaults {}
   170  			`,
   171  			map[string]cty.Value{"ORIGIN": cty.StringVal(""), "TIMEOUT": cty.StringVal("")},
   172  		},
   173  		{
   174  			"no-defaults-block",
   175  			`
   176  			server "test" {
   177  			  endpoint "/" {
   178  				proxy {
   179  				  backend {
   180  					origin = env.ORIGIN
   181  					timeout = env.TIMEOUT
   182  				  }
   183  				}
   184  			  }
   185  			}
   186  			`,
   187  			map[string]cty.Value{"ORIGIN": cty.StringVal(""), "TIMEOUT": cty.StringVal("")},
   188  		},
   189  	}
   190  
   191  	for _, tt := range tests {
   192  		t.Run(tt.name, func(subT *testing.T) {
   193  			cf, err := configload.LoadBytes([]byte(tt.hcl), "couper.hcl")
   194  			if err != nil {
   195  				subT.Error(err)
   196  				return
   197  			}
   198  
   199  			hclContext := cf.Context.(*eval.Context).HCLContext()
   200  
   201  			envVars := hclContext.Variables["env"].AsValueMap()
   202  			for key, expectedValue := range tt.want {
   203  				value, isset := envVars[key]
   204  				if !isset && expectedValue != cty.NilVal {
   205  					subT.Errorf("Missing evironment variable %q:\nWant:\t%s=%q\nGot:", key, key, expectedValue)
   206  				} else if value != expectedValue {
   207  					subT.Errorf("Unexpected value for evironment variable %q:\nWant:\t%s=%q\nGot:\t%s=%q", key, key, expectedValue, key, value)
   208  				}
   209  			}
   210  		})
   211  	}
   212  }
   213  
   214  func TestCouperVariables(t *testing.T) {
   215  	tests := []struct {
   216  		name string
   217  		hcl  string
   218  		env  string
   219  		want map[string]string
   220  	}{
   221  		{
   222  			"test",
   223  			`
   224  			server "test" {}
   225  			`,
   226  			"",
   227  			map[string]string{"version": utils.VersionName, "environment": ""},
   228  		},
   229  		{
   230  			"environment",
   231  			`
   232  			server {}
   233  			`,
   234  			"bar",
   235  			map[string]string{"version": utils.VersionName, "environment": "bar"},
   236  		},
   237  	}
   238  
   239  	for _, tt := range tests {
   240  		t.Run(tt.name, func(subT *testing.T) {
   241  			cf, err := configload.LoadBytesEnv([]byte(tt.hcl), "couper.hcl", tt.env)
   242  			if err != nil {
   243  				subT.Error(err)
   244  				return
   245  			}
   246  
   247  			hclContext := cf.Context.Value(request.ContextType).(*eval.Context).HCLContext()
   248  
   249  			couperVars := seetie.ValueToMap(hclContext.Variables["couper"])
   250  
   251  			if len(couperVars) != len(tt.want) {
   252  				subT.Errorf("Unexpected 'couper' variables:\nWant:\t%q\nGot:\t%q", tt.want, couperVars)
   253  			}
   254  			for key, expectedValue := range tt.want {
   255  				value := couperVars[key]
   256  				if value != expectedValue {
   257  					subT.Errorf("Unexpected value for variable:\nWant:\tcouper.%s=%q\nGot:\tcouper.%s=%q", key, expectedValue, key, value)
   258  				}
   259  			}
   260  		})
   261  	}
   262  }