-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtransport_helpers_test.go
More file actions
235 lines (187 loc) · 4.88 KB
/
Copy pathtransport_helpers_test.go
File metadata and controls
235 lines (187 loc) · 4.88 KB
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package rcpx
import (
"errors"
"fmt"
"io"
"net/http"
"strings"
"sync"
"testing"
"time"
)
type trackingBody struct {
mu sync.Mutex
closed bool
r io.Reader
}
func (b *trackingBody) Read(p []byte) (int, error) { return b.r.Read(p) }
func (b *trackingBody) Close() error {
b.mu.Lock()
b.closed = true
b.mu.Unlock()
return nil
}
func (b *trackingBody) Closed() bool {
b.mu.Lock()
defer b.mu.Unlock()
return b.closed
}
type unreadableReader struct{}
func (unreadableReader) Read([]byte) (int, error) { return 0, errors.New("read fail") }
type rtResult struct {
resp *http.Response
err error
}
type scriptRT struct {
mu sync.Mutex
calls []string
// keyed by full URL string
results map[string][]rtResult
}
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
type policyRecorder struct {
calls int
ret bool
}
func (s *scriptRT) RoundTrip(req *http.Request) (*http.Response, error) {
s.mu.Lock()
s.calls = append(s.calls, req.URL.String())
queue := s.results[req.URL.String()]
if len(queue) == 0 {
s.mu.Unlock()
return nil, errors.New("unexpected call: " + req.URL.String())
}
res := queue[0]
s.results[req.URL.String()] = queue[1:]
s.mu.Unlock()
if res.resp != nil {
res.resp.Request = req
if res.resp.Header == nil {
res.resp.Header = make(http.Header)
}
}
return res.resp, res.err
}
func (s *scriptRT) Calls() []string {
s.mu.Lock()
defer s.mu.Unlock()
out := make([]string, len(s.calls))
copy(out, s.calls)
return out
}
// Builders
func newTrackingBody(s string) *trackingBody {
return &trackingBody{r: strings.NewReader(s)}
}
func newPolicyRecorder(ret bool) (*policyRecorder, func(AttemptOutcome) bool) {
pr := &policyRecorder{ret: ret}
return pr, func(out AttemptOutcome) bool {
pr.calls++
return pr.ret
}
}
func newRPCRequest(t *testing.T, url, method string) *http.Request {
t.Helper()
body := fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"method":%q,"params":[]}`, method)
return newJSONRequest(t, url, body)
}
func newJSONRequest(t *testing.T, url, body string) *http.Request {
t.Helper()
req, err := http.NewRequest("POST", url, io.NopCloser(strings.NewReader(body)))
if err != nil {
t.Fatalf("http.NewRequest: %v", err)
}
return req
}
func newHTTPResp(code int, body string) *http.Response {
return &http.Response{
StatusCode: code,
Body: io.NopCloser(strings.NewReader(body)),
}
}
// httpResp is kept to minimize churn across tests. Prefer newHTTPResp in new code.
func httpResp(code int, body string) *http.Response { return newHTTPResp(code, body) }
// Transport constructor
func mustNewTransport(t *testing.T, cfg Config) *transport {
t.Helper()
rt, err := NewRoundTripper(cfg)
if err != nil {
t.Fatalf("NewRoundTripper error: %v", err)
}
tr, ok := rt.(*transport)
if !ok {
t.Fatalf("expected *transport, got %T", rt)
}
// Keep time deterministic for any internal cooldown bookkeeping.
tr.now = func() time.Time { return time.Unix(1, 0) }
return tr
}
// Assertions
func assertStatus(t *testing.T, resp *http.Response, want int) {
t.Helper()
if resp == nil {
t.Fatalf("expected HTTP %d, got nil response", want)
}
if resp.StatusCode != want {
t.Fatalf("expected HTTP %d, got %d", want, resp.StatusCode)
}
}
func assertCalls(t *testing.T, base *scriptRT, want ...string) {
t.Helper()
got := base.Calls()
if len(got) != len(want) {
t.Fatalf("unexpected base call count: got=%v want=%v", got, want)
}
for i := range got {
if got[i] != want[i] {
t.Fatalf("unexpected base calls: got=%v want=%v", got, want)
}
}
}
func assertNonIdempotentBlocked(t *testing.T, err error, cause error) {
t.Helper()
var be *NonIdempotentBlockedError
if !errors.As(err, &be) {
t.Fatalf("expected NonIdempotentBlockedError, got %v", err)
}
if !errors.Is(err, cause) {
t.Fatalf("expected underlying cause %v, got %v", cause, err)
}
}
func assertPolicyCalls(t *testing.T, pr *policyRecorder, want int) {
t.Helper()
if pr.calls != want {
t.Fatalf("unexpected policy call count: got=%d want=%d", pr.calls, want)
}
}
// Must helpers
func mustRoundTrip(t *testing.T, rt http.RoundTripper, req *http.Request) *http.Response {
t.Helper()
resp, err := rt.RoundTrip(req)
if err != nil {
t.Fatalf("RoundTrip error: %v", err)
}
if resp == nil {
t.Fatalf("expected non nil response")
}
t.Cleanup(func() { resp.Body.Close() })
return resp
}
func mustRoundTripCode(t *testing.T, rt http.RoundTripper, req *http.Request, want int) {
t.Helper()
resp, err := rt.RoundTrip(req)
if err != nil {
t.Fatalf("RoundTrip error: %v", err)
}
assertStatus(t, resp, want)
resp.Body.Close()
}
func mustAsAllUpstreamsFailed(t *testing.T, err error) *AllUpstreamsFailedError {
t.Helper()
var ae *AllUpstreamsFailedError
if !errors.As(err, &ae) {
t.Fatalf("expected AllUpstreamsFailedError, got %v", err)
}
return ae
}