github.com/goplus/yap@v0.8.1/ytest/request.go (about) 1 /* 2 * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package ytest 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "io" 23 "log" 24 "net/http" 25 "net/url" 26 "strings" 27 28 "github.com/goplus/yap/test" 29 "github.com/goplus/yap/ytest/auth" 30 ) 31 32 type RequestBody interface { 33 io.Reader 34 io.Seeker 35 Size() int64 36 } 37 38 type Request struct { 39 method string 40 url string 41 header http.Header 42 auth auth.RTComposer 43 bodyType string 44 body RequestBody 45 resp *Response 46 ctx *Case 47 } 48 49 func newRequest(ctx *Case, method, url string) *Request { 50 return &Request{ 51 method: method, 52 url: url, 53 header: make(http.Header), 54 ctx: ctx, 55 } 56 } 57 58 func (p *Request) t() CaseT { 59 return p.ctx.CaseT 60 } 61 62 // Auth sets an Authorization for this request. 63 func (p *Request) Auth(auth auth.RTComposer) *Request { 64 p.auth = auth 65 return p 66 } 67 68 // ----------------------------------------------------------------------------- 69 70 // WithHeader sets a Header for this request. 71 func (p *Request) WithHeader(key string, value any) *Request { 72 switch v := value.(type) { 73 case string: 74 p.header.Set(key, v) 75 case []string: 76 p.header[key] = v 77 case *test.Var__0[string]: 78 p.header.Set(key, v.Val()) 79 case *test.Var__3[[]string]: 80 p.header[key] = v.Val() 81 default: 82 test.Fatalf("set header failed! unexpected value type: %T\n", value) 83 } 84 return p 85 } 86 87 // Header sets a Header for this request (if request is not sended), or matches 88 // a Header for response of this request (after response is returned). 89 // Here value can be: string, []string, Var(string), Var([]string). 90 func (p *Request) Header__0(key string, value any) *Request { 91 if p.resp == nil { 92 return p.WithHeader(key, value) 93 } 94 t := p.t() 95 t.Helper() 96 p.resp.matchHeader(t, key, value) 97 return p 98 } 99 100 // Header returns header of current request. 101 func (p *Request) Header__1() http.Header { 102 return p.header 103 } 104 105 // ----------------------------------------------------------------------------- 106 107 // Body sets request body for this request (if request is not sended), or matches 108 // response body of this request (after response is returned). 109 // Here body can be: string, Var(string), []byte, RequestBody. 110 func (p *Request) Body(bodyType string, body any) *Request { 111 if p.resp == nil { 112 return p.WithBodyEx(bodyType, body) 113 } 114 t := p.t() 115 t.Helper() 116 p.resp.matchBody(t, bodyType, body) 117 return p 118 } 119 120 func (p *Request) WithBodyEx(bodyType string, body any) *Request { 121 switch v := body.(type) { 122 case string: 123 return p.WithText(bodyType, v) 124 case *test.Var__0[string]: 125 return p.WithText(bodyType, v.Val()) 126 case []byte: 127 return p.WithBinary(bodyType, v) 128 case RequestBody: 129 return p.WithBody(bodyType, v) 130 default: 131 test.Fatalf("set body failed! unexpected value type: %T\n", body) 132 } 133 return p 134 } 135 136 func (p *Request) WithText(bodyType, body string) *Request { 137 return p.WithBody(bodyType, strings.NewReader(body)) 138 } 139 140 func (p *Request) WithBinary(bodyType string, body []byte) *Request { 141 return p.WithBody(bodyType, bytes.NewReader(body)) 142 } 143 144 func (p *Request) WithBody(bodyType string, body RequestBody) *Request { 145 p.bodyType = mimeType(bodyType) 146 p.body = body 147 return p 148 } 149 150 const ( 151 mimeNone = "" 152 mimeForm = "application/x-www-form-urlencoded" 153 mimeJson = "application/json" 154 mimeBinary = "application/octet-stream" 155 mimeText = "text/plain" 156 ) 157 158 func mimeType(ct string) string { 159 if strings.Contains(ct, "/") { 160 return ct 161 } 162 switch ct { 163 case "form": 164 return mimeForm 165 case "binary": 166 return mimeBinary 167 } 168 return "application/" + ct 169 } 170 171 func (p *Request) Binary(body any) *Request { 172 p.t().Helper() 173 return p.Body(mimeBinary, body) 174 } 175 176 func (p *Request) Text(body any) *Request { 177 p.t().Helper() 178 return p.Body(mimeText, body) 179 } 180 181 // ----------------------------------------------------------------------------- 182 183 // body can be: 184 // - map[string]any, Var(map[string]any), []any, Var([]any), 185 // - []string, Var([]string), string, Var(string), int, Var(int), 186 // - bool, Var(bool), float64, Var(float64). 187 func (p *Request) Json(body any) *Request { 188 if p.resp == nil { 189 return p.WithJson(body) 190 } 191 t := p.t() 192 t.Helper() 193 p.resp.matchBody(t, mimeJson, body) 194 return p 195 } 196 197 func (p *Request) WithJson(body any) *Request { 198 b, err := json.Marshal(body) 199 if err != nil { 200 test.Fatal("json.Marshal failed:", err) 201 } 202 return p.WithBinary(mimeJson, b) 203 } 204 205 // ----------------------------------------------------------------------------- 206 207 func (p *Request) Form(body any) *Request { 208 if p.resp == nil { 209 return p.WithFormEx(body) 210 } 211 t := p.t() 212 t.Helper() 213 p.resp.matchBody(t, mimeForm, body) 214 return p 215 } 216 217 func (p *Request) WithForm(body url.Values) *Request { 218 return p.WithText(mimeForm, body.Encode()) 219 } 220 221 func (p *Request) WithFormEx(body any) *Request { 222 var vals url.Values 223 switch v := body.(type) { 224 case map[string]any: 225 vals = Form(v) 226 case *test.Var__1[map[string]any]: 227 vals = Form(v.Val()) 228 case url.Values: 229 vals = v 230 default: 231 test.Fatalf("request with form: unexpected type %T\n", body) 232 } 233 return p.WithText(mimeForm, vals.Encode()) 234 } 235 236 // ----------------------------------------------------------------------------- 237 238 func mergeHeader(to, from http.Header) { 239 for k, v := range from { 240 to[k] = v 241 } 242 } 243 244 func (p *Request) doSend() (resp *http.Response, err error) { 245 body := p.body 246 req, err := p.ctx.app.newRequest(p.method, p.url, body) 247 if err != nil { 248 log.Fatalf("newRequest(%s, %s) failed: %v\n", p.method, p.url, err) 249 } 250 251 mergeHeader(req.Header, p.ctx.DefaultHeader) 252 mergeHeader(req.Header, p.header) 253 254 if body != nil { 255 if p.bodyType != "" { 256 req.Header.Set("Content-Type", p.bodyType) 257 } 258 req.ContentLength = body.Size() 259 } 260 tr := p.ctx.app.transport 261 if p.auth != nil { 262 tr = p.auth.Compose(tr) 263 } 264 c := &http.Client{Transport: tr} 265 return c.Do(req) 266 } 267 268 const ( 269 Gopo_Request_Ret = ".Send,.RetWith" 270 ) 271 272 func (p *Request) Send() *Request { 273 resp, err := p.doSend() 274 if err != nil { 275 test.Fatalf("sendRequest(%v, %v) failed: %v\n", p.method, p.url, err) 276 } 277 defer resp.Body.Close() 278 p.resp = newResponse(resp) 279 return p 280 } 281 282 func (p *Request) RetWith(code any) *Request { 283 t := p.t() 284 t.Helper() 285 p.Send().resp.matchCode(t, code) 286 return p 287 } 288 289 // ----------------------------------------------------------------------------- 290 291 func (p *Request) Resp() *Response { 292 return p.resp 293 } 294 295 // -----------------------------------------------------------------------------