master
go 116 lines 2.78 KB
Raw
1 package harness
2
3 import (
4 "io"
5 "net/http"
6 "strings"
7 "text/template"
8 "time"
9 )
10
11 // HTTPClient is an HTTP client with some conveniences for testing.
12 // URLs are constructed from a base URL.
13 // The response body is buffered into a string.
14 // Internal errors cause panics so that tests don't need to check errors.
15 // The paths are evaluated as Go templates for readable string interpolation.
16 type HTTPClient struct {
17 Client *http.Client
18 BaseURL string
19
20 Timeout time.Duration
21 TemplateData any
22 }
23
24 type HTTPResponse struct {
25 Body string
26 StatusCode int
27 Headers http.Header
28
29 // The raw response. The body will be closed on this response.
30 Resp *http.Response
31 }
32
33 func (c *HTTPClient) WithHeader(k, v string) func(h *http.Request) {
34 return func(h *http.Request) {
35 h.Header.Add(k, v)
36 }
37 }
38
39 func (c *HTTPClient) DisableRedirects() *HTTPClient {
40 c.Client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
41 return http.ErrUseLastResponse
42 }
43 return c
44 }
45
46 // Do executes the request unchanged.
47 func (c *HTTPClient) Do(req *http.Request) *HTTPResponse {
48 log.Debugf("making HTTP req %s to %q with headers %+v", req.Method, req.URL.String(), req.Header)
49 resp, err := c.Client.Do(req)
50 if resp != nil && resp.Body != nil {
51 defer resp.Body.Close()
52 }
53 if err != nil {
54 panic(err)
55 }
56 bodyStr, err := io.ReadAll(resp.Body)
57 if err != nil {
58 panic(err)
59 }
60
61 return &HTTPResponse{
62 Body: string(bodyStr),
63 StatusCode: resp.StatusCode,
64 Headers: resp.Header,
65 Resp: resp,
66 }
67 }
68
69 // BuildURL constructs a request URL from the given path by interpolating the string and then appending it to the base URL.
70 func (c *HTTPClient) BuildURL(urlPath string) string {
71 sb := &strings.Builder{}
72 err := template.Must(template.New("test").Parse(urlPath)).Execute(sb, c.TemplateData)
73 if err != nil {
74 panic(err)
75 }
76 renderedPath := sb.String()
77 return c.BaseURL + renderedPath
78 }
79
80 func (c *HTTPClient) Get(urlPath string, opts ...func(*http.Request)) *HTTPResponse {
81 req, err := http.NewRequest(http.MethodGet, c.BuildURL(urlPath), nil)
82 if err != nil {
83 panic(err)
84 }
85 for _, o := range opts {
86 o(req)
87 }
88 return c.Do(req)
89 }
90
91 func (c *HTTPClient) Post(urlPath string, body io.Reader, opts ...func(*http.Request)) *HTTPResponse {
92 req, err := http.NewRequest(http.MethodPost, c.BuildURL(urlPath), body)
93 if err != nil {
94 panic(err)
95 }
96 for _, o := range opts {
97 o(req)
98 }
99 return c.Do(req)
100 }
101
102 func (c *HTTPClient) PostStr(urlpath, body string, opts ...func(*http.Request)) *HTTPResponse {
103 r := strings.NewReader(body)
104 return c.Post(urlpath, r, opts...)
105 }
106
107 func (c *HTTPClient) Head(urlPath string, opts ...func(*http.Request)) *HTTPResponse {
108 req, err := http.NewRequest(http.MethodHead, c.BuildURL(urlPath), nil)
109 if err != nil {
110 panic(err)
111 }
112 for _, o := range opts {
113 o(req)
114 }
115 return c.Do(req)
116 }