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 }