-
Notifications
You must be signed in to change notification settings - Fork 76
/
requests.go
151 lines (133 loc) · 3.82 KB
/
requests.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package sharedtest
import (
"bytes"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"
"github.com/launchdarkly/ld-relay/v8/internal/credential"
helpers "github.com/launchdarkly/go-test-helpers/v3"
"github.com/stretchr/testify/require"
)
// BuildRequest is a simple shortcut for creating a request that may or may not have a body.
func BuildRequest(method, url string, body []byte, headers http.Header) *http.Request {
var bodyBuffer io.Reader
if body != nil {
bodyBuffer = bytes.NewBuffer(body)
}
r, err := http.NewRequest(method, url, bodyBuffer)
if err != nil {
panic(err)
}
if headers != nil {
r.Header = headers
}
return r
}
// BuildRequestWithAuth creates a GET request with an Authorization header.
func BuildRequestWithAuth(method, url string, authKey credential.SDKCredential, body []byte) *http.Request {
h := make(http.Header)
if authKey != nil {
h.Add("Authorization", authKey.GetAuthorizationHeaderValue())
}
return BuildRequest(method, url, body, h)
}
// AddQueryParam is a shortcut for concatenating a query string to a URL that may or may not have one.
func AddQueryParam(url, query string) string {
if strings.Contains(url, "?") {
return url + "&" + query
}
return url + "?" + query
}
// DoRequest is a shortcut for executing an endpoint handler against a request and getting the response.
func DoRequest(req *http.Request, handler http.Handler) (*http.Response, []byte) {
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
result := w.Result()
var body []byte
if result.Body != nil {
body, _ = io.ReadAll(result.Body)
_ = result.Body.Close()
}
return result, body
}
// CallHandlerAndAwaitStatus calls an HTTP handler directly with a request and then blocks
// until the handler has started a response, returning the response status (and cancelling
// the request). We use this when we don't need to wait for a complete response (or when there's
// no such thing as a complete response, as in the case of streaming endpoints). It raises a
// fatal test failure if the timeout elapses before receiving a status.
func CallHandlerAndAwaitStatus(t *testing.T, handler http.Handler, req *http.Request, timeout time.Duration) int {
var s simpleResponseSink
go handler.ServeHTTP(&s, req)
require.True(t, s.awaitResponseStarted(timeout), "timed out waiting for HTTP handler to start response")
return s.getStatus()
}
// simpleResponseSink is used by CallHandlerAndAwaitStatus instead of httptest.ResponseRecorder
// because ResponseRecorder requires you to wait for a complete response.
type simpleResponseSink struct {
status int
header http.Header
body []byte
started bool
startedCh chan struct{}
lock sync.Mutex
}
func (s *simpleResponseSink) awaitResponseStarted(timeout time.Duration) bool {
s.lock.Lock()
if s.started {
s.lock.Unlock()
return true
}
if s.startedCh == nil {
s.startedCh = make(chan struct{}, 1)
}
ch := s.startedCh
s.lock.Unlock()
_, ok, _ := helpers.TryReceive(ch, timeout)
return ok
}
func (s *simpleResponseSink) getStatus() int {
s.lock.Lock()
defer s.lock.Unlock()
return s.status
}
func (s *simpleResponseSink) Header() http.Header {
s.lock.Lock()
defer s.lock.Unlock()
if s.header == nil {
s.header = make(http.Header)
}
return s.header
}
func (s *simpleResponseSink) Write(data []byte) (int, error) {
s.start()
s.lock.Lock()
defer s.lock.Unlock()
s.body = append(s.body, data...)
return len(data), nil
}
func (s *simpleResponseSink) WriteHeader(status int) {
s.lock.Lock()
s.status = status
s.lock.Unlock()
s.start()
}
func (s *simpleResponseSink) Flush() {}
func (s *simpleResponseSink) start() {
s.lock.Lock()
defer s.lock.Unlock()
if s.started {
return
}
if s.status == 0 {
s.status = 200
}
s.started = true
if s.startedCh == nil {
s.startedCh = make(chan struct{}, 1)
}
s.startedCh <- struct{}{}
}