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  // -----------------------------------------------------------------------------