-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathendpoint.go
163 lines (131 loc) · 3.7 KB
/
endpoint.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
152
153
154
155
156
157
158
159
160
161
162
163
package mockhttp
import (
"net/http"
"sync/atomic"
"testing"
)
// Scenario is a mock case for a specific endpoint.
type Scenario struct {
executionCount int64
times int
builders []Responder
matchers []Matcher
}
func newScenario(matchers []Matcher) *Scenario {
return &Scenario{
matchers: matchers,
times: 1,
}
}
// Match verifies if request matches expectations.
func (s *Scenario) Match(t *testing.T, r *http.Request) {
t.Helper()
atomic.AddInt64(&s.executionCount, 1)
for _, m := range s.matchers {
m(t, r)
}
}
// Times sets the how many requests it is expected to be received by this endpoint.
func (s *Scenario) Times(n int) *Scenario {
s.times = n
return s
}
// TimesCalled return how many times this Scenario was executed.
func (s *Scenario) TimesCalled() int {
return int(atomic.LoadInt64(&s.executionCount))
}
// Respond set up a collection of Responders.
func (s *Scenario) Respond(builders ...Responder) *Scenario {
s.builders = builders
return s
}
func (s *Scenario) respondTo(w http.ResponseWriter) {
mw := newMemoryResponseWriter()
for _, b := range s.builders {
b(mw)
}
mw.flush(w)
}
// Endpoint defines an HTTP method and path that have
// multiple mocked scenarios to produce responses.
type Endpoint struct {
method string
path string
requestCount int64
scenarios []*Scenario
}
func newEndpoint(method, path string) *Endpoint {
return &Endpoint{method: method, path: path}
}
// Handler create an HTTP handler that executes each scenario in the order
// they were defined. If a scenario defines a Times expectation, the scenario
// is executed the number of times it's defined.
func (e *Endpoint) Handler(t *testing.T) http.HandlerFunc {
t.Helper()
var responsePlan []int
for index, s := range e.scenarios {
for i := 0; i < s.times; i++ {
responsePlan = append(responsePlan, index)
}
}
return func(w http.ResponseWriter, r *http.Request) {
plan := atomic.LoadInt64(&e.requestCount)
if plan >= int64(len(responsePlan)) {
// if endpoint called more times than planned
// just use the last scenario for response
plan = int64(len(responsePlan) - 1)
}
currentScenarioIndex := responsePlan[plan]
scenario := e.scenarios[currentScenarioIndex]
scenario.Match(t, r)
scenario.respondTo(w)
atomic.AddInt64(&e.requestCount, 1)
}
}
// Name returns the endpoint name (method + path) that this Returner represents.
func (e *Endpoint) Name() string {
return endpointName(e.method, e.path)
}
// AddScenario appends a scenario to the endpoint.
func (e *Endpoint) AddScenario(s *Scenario) {
e.scenarios = append(e.scenarios, s)
}
// memoryResponseWriter accumulates all response builders
// mutations such that the order they are used in test does not matter.
//
// This is necessary because if ResponseStatusCode is used after JSONResponseBody, the
// status will be fixed at 200 by the Write call to http.ResponseWriter.
type memoryResponseWriter struct {
headers http.Header
body []byte
statusCode int
}
func newMemoryResponseWriter() *memoryResponseWriter {
return &memoryResponseWriter{headers: make(http.Header)}
}
func (m *memoryResponseWriter) Header() http.Header {
return m.headers
}
func (m *memoryResponseWriter) Write(bytes []byte) (int, error) {
m.body = bytes
return len(bytes), nil
}
func (m *memoryResponseWriter) WriteHeader(statusCode int) {
m.statusCode = statusCode
}
func (m *memoryResponseWriter) flush(w http.ResponseWriter) {
for k, values := range m.headers {
for _, v := range values {
w.Header().Add(k, v)
}
}
if m.statusCode > 0 {
w.WriteHeader(m.statusCode)
}
if len(m.body) > 0 {
w.Write(m.body) //nolint:errcheck // test helper
}
}
func endpointName(m, p string) string {
return m + " " + p
}