cuelang.org/go@v0.13.0/pkg/tool/http/http_test.go (about) 1 // Copyright 2020 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package http 16 17 import ( 18 "encoding/pem" 19 "fmt" 20 "io" 21 "log" 22 "net/http" 23 "net/http/httptest" 24 "strings" 25 "testing" 26 27 "cuelang.org/go/cue" 28 "cuelang.org/go/cue/parser" 29 "cuelang.org/go/internal/task" 30 "cuelang.org/go/internal/value" 31 "cuelang.org/go/pkg/internal" 32 ) 33 34 func newTLSServer() *httptest.Server { 35 server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 36 resp := `{"foo": "bar"}` 37 w.Write([]byte(resp)) 38 })) 39 // The TLS errors produced by TestTLS would otherwise print noise to stderr. 40 server.Config.ErrorLog = log.New(io.Discard, "", 0) 41 server.StartTLS() 42 return server 43 } 44 45 func parse(t *testing.T, kind, expr string) cue.Value { 46 t.Helper() 47 48 x, err := parser.ParseExpr("test", expr) 49 if err != nil { 50 t.Fatal(err) 51 } 52 v := internal.NewContext().BuildExpr(x) 53 if err := v.Err(); err != nil { 54 t.Fatal(err) 55 } 56 return value.UnifyBuiltin(v, kind) 57 } 58 59 func TestTLS(t *testing.T) { 60 s := newTLSServer() 61 t.Cleanup(s.Close) 62 63 v1 := parse(t, "tool/http.Get", fmt.Sprintf(`{url: "%s"}`, s.URL)) 64 _, err := (*httpCmd).Run(nil, &task.Context{Obj: v1}) 65 if err == nil { 66 t.Fatal("http call should have failed") 67 } 68 69 v2 := parse(t, "tool/http.Get", fmt.Sprintf(`{url: "%s", tls: verify: false}`, s.URL)) 70 _, err = (*httpCmd).Run(nil, &task.Context{Obj: v2}) 71 if err != nil { 72 t.Fatal(err) 73 } 74 75 publicKeyBlock := pem.Block{ 76 Type: "PUBLIC KEY", 77 Bytes: s.Certificate().Raw, 78 } 79 publicKeyPem := pem.EncodeToMemory(&publicKeyBlock) 80 81 v3 := parse(t, "tool/http.Get", fmt.Sprintf(` 82 { 83 url: "%s" 84 tls: caCert: ''' 85 %s 86 ''' 87 }`, s.URL, publicKeyPem)) 88 89 _, err = (*httpCmd).Run(nil, &task.Context{Obj: v3}) 90 if err != nil { 91 t.Fatal(err) 92 } 93 } 94 95 func TestParseHeaders(t *testing.T) { 96 req := ` 97 header: { 98 "Accept-Language": "en,nl" 99 } 100 trailer: { 101 "Accept-Language": "en,nl" 102 User: "foo" 103 } 104 ` 105 testCases := []struct { 106 req string 107 field string 108 out string 109 }{{ 110 field: "header", 111 out: "nil", 112 }, { 113 req: req, 114 field: "non-exist", 115 out: "nil", 116 }, { 117 req: req, 118 field: "header", 119 out: "Accept-Language: en,nl\r\n", 120 }, { 121 req: req, 122 field: "trailer", 123 out: "Accept-Language: en,nl\r\nUser: foo\r\n", 124 }, { 125 req: ` 126 header: { 127 "1": 'a' 128 } 129 `, 130 field: "header", 131 out: "header.\"1\": cannot use value 'a' (type bytes) as string", 132 }, { 133 req: ` 134 header: 1 135 `, 136 field: "header", 137 out: "header: cannot use value 1 (type int) as struct", 138 }} 139 for _, tc := range testCases { 140 t.Run("", func(t *testing.T) { 141 ctx := internal.NewContext() 142 v := ctx.CompileString(tc.req, cue.Filename("http headers")) 143 if err := v.Err(); err != nil { 144 t.Fatal(err) 145 } 146 147 h, err := parseHeaders(v, tc.field) 148 149 b := &strings.Builder{} 150 switch { 151 case err != nil: 152 fmt.Fprint(b, err) 153 case h == nil: 154 b.WriteString("nil") 155 default: 156 _ = h.Write(b) 157 } 158 159 got := b.String() 160 if got != tc.out { 161 t.Errorf("got %q; want %q", got, tc.out) 162 } 163 }) 164 } 165 } 166 167 // TestRedirect exercises the followRedirects configuration on an http.Do request 168 func TestRedirect(t *testing.T) { 169 mux := http.NewServeMux() 170 171 // In this test server, /a redirects to /b. /b serves "hello" 172 mux.Handle("/a", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 173 http.Redirect(w, r, "/b", http.StatusFound) 174 })) 175 mux.Handle("/b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 176 fmt.Fprint(w, "hello") 177 })) 178 179 server := httptest.NewUnstartedServer(mux) 180 server.Start() 181 t.Cleanup(server.Close) 182 183 testCases := []struct { 184 name string 185 path string 186 statusCode int 187 followRedirects *bool 188 body *string 189 }{ 190 { 191 name: "/a silent on redirects", 192 path: "/a", 193 statusCode: 200, 194 body: ref("hello"), 195 }, 196 { 197 name: "/a with explicit followRedirects: true", 198 path: "/a", 199 statusCode: 200, 200 followRedirects: ref(true), 201 body: ref("hello"), 202 }, 203 { 204 name: "/a with explicit followRedirects: false", 205 path: "/a", 206 statusCode: 302, 207 followRedirects: ref(false), 208 }, 209 { 210 name: "/b silent on redirects", 211 path: "/b", 212 statusCode: 200, 213 body: ref("hello"), 214 }, 215 { 216 name: "/b with explicit followRedirects: true", 217 path: "/b", 218 statusCode: 200, 219 followRedirects: ref(true), 220 body: ref("hello"), 221 }, 222 { 223 name: "/b with explicit followRedirects: false", 224 path: "/b", 225 statusCode: 200, 226 followRedirects: ref(true), 227 body: ref("hello"), 228 }, 229 } 230 231 for _, tc := range testCases { 232 t.Run(tc.name, func(t *testing.T) { 233 v3 := parse(t, "tool/http.Get", fmt.Sprintf(` 234 { 235 url: "%s%s" 236 }`, server.URL, tc.path)) 237 238 if tc.followRedirects != nil { 239 v3 = v3.FillPath(cue.ParsePath("followRedirects"), *tc.followRedirects) 240 } 241 242 resp, err := (*httpCmd).Run(nil, &task.Context{Obj: v3}) 243 if err != nil { 244 t.Fatal(err) 245 } 246 247 // grab the response 248 response := resp.(map[string]any)["response"].(map[string]any) 249 250 if got := response["statusCode"]; got != tc.statusCode { 251 t.Fatalf("status not as expected: wanted %d, got %d", got, tc.statusCode) 252 } 253 254 if tc.body != nil { 255 want := *tc.body 256 if got := response["body"]; got != want { 257 t.Fatalf("body not as expected; wanted %q, got %q", got, want) 258 } 259 } 260 }) 261 } 262 } 263 264 func ref[T any](v T) *T { 265 return &v 266 }