-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathtester.go
283 lines (228 loc) · 6.11 KB
/
tester.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
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
package fire
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/256dpi/jsonapi/v2"
"github.com/256dpi/serve"
"github.com/256dpi/xo"
"go.mongodb.org/mongo-driver/bson"
"github.com/256dpi/fire/coal"
"github.com/256dpi/fire/stick"
)
// A Tester provides facilities to the test a fire API.
type Tester struct {
*coal.Tester
// The handler to be tested.
Handler http.Handler
// A path prefix e.g. 'api'.
Prefix string
// The header to be set on all requests and contexts.
Header map[string]string
// Context to be set on fake requests.
Context context.Context
}
// NewTester returns a new tester.
func NewTester(store *coal.Store, models ...coal.Model) *Tester {
return &Tester{
Tester: coal.NewTester(store, models...),
Header: make(map[string]string),
Context: context.Background(),
}
}
// Assign will create a controller group with the specified controllers and
// assign in to the Handler attribute of the tester. It will return the created
// group.
func (t *Tester) Assign(prefix string, controllers ...*Controller) *Group {
// create group
group := NewGroup(func(err error) {
panic(err)
})
// ensure store
for _, c := range controllers {
if c.Store == nil {
c.Store = t.Store
}
}
// add controllers
group.Add(controllers...)
// set handler
t.Handler = serve.Compose(xo.RootHandler(), group.Endpoint(prefix))
return group
}
// Clean will remove the collections of models that have been registered and
// reset the header map as well as the context.
func (t *Tester) Clean() {
// clean models
t.Tester.Clean()
// reset header
t.Header = make(map[string]string)
// reset context
t.Context = context.Background()
}
// Path returns a root prefixed path for the supplied path.
func (t *Tester) Path(path string) string {
// add root slash
path = "/" + strings.Trim(path, "/")
// add prefix if available
if t.Prefix != "" {
path = "/" + t.Prefix + path
}
return path
}
// RunCallback is a helper to test callbacks.
func (t *Tester) RunCallback(ctx *Context, cb *Callback) error {
return t.RunHandler(ctx, func(ctx *Context) error {
// force stage
ctx.Stage = Authorizer
if cb.Stage != 0 {
ctx.Stage = cb.Stage.Split()[0]
}
// check matcher
if !cb.Matcher(ctx) {
return fmt.Errorf("failed matcher")
}
return cb.Handler(ctx)
})
}
// RunAction is a helper to test actions.
func (t *Tester) RunAction(ctx *Context, action *Action) (*httptest.ResponseRecorder, error) {
// set default
if action.BodyLimit <= 0 {
action.BodyLimit = serve.MustByteSize("8M")
}
// get context
var rec *httptest.ResponseRecorder
err := t.RunHandler(ctx, func(ctx *Context) error {
// get response recorder
rec = ctx.ResponseWriter.(*httptest.ResponseRecorder)
// check method
if !stick.Contains(action.Methods, ctx.HTTPRequest.Method) {
rec.WriteHeader(http.StatusMethodNotAllowed)
return nil
}
// limit request
serve.LimitBody(rec, ctx.HTTPRequest, action.BodyLimit)
// run action
return action.Handler(ctx)
})
if err != nil {
rec.WriteHeader(http.StatusInternalServerError)
}
return rec, err
}
// RunHandler builds a context and runs the passed handler with it.
func (t *Tester) RunHandler(ctx *Context, h Handler) error {
return t.WithContext(ctx, func(ctx *Context) error {
return h(ctx)
})
}
// WithContext runs the given function with a prepared context.
func (t *Tester) WithContext(ctx *Context, fn func(ctx *Context) error) error {
// ensure context
if ctx == nil {
ctx = &Context{}
}
// ensure context
if ctx.Context == nil {
ctx.Context = t.Context
}
// ensure data
if ctx.Data == nil {
ctx.Data = stick.Map{}
}
// ensure stage
if ctx.Stage == 0 {
ctx.Stage = Authorizer
}
// ensure operation
if ctx.Operation == 0 {
ctx.Operation = List
}
// ensure selector
if ctx.Selector == nil {
ctx.Selector = bson.M{}
}
// ensure filters
if ctx.Filters == nil {
ctx.Filters = []bson.M{}
}
// ensure relationship filters
if ctx.RelationshipFilters == nil {
ctx.RelationshipFilters = map[string][]bson.M{}
}
// set store if unset
if ctx.Store == nil {
ctx.Store = t.Store
}
// set request
if ctx.HTTPRequest == nil {
// create request
req, err := http.NewRequest("GET", "", nil)
if err != nil {
panic(err)
}
// set headers
for key, value := range t.Header {
req.Header.Set(key, value)
}
// set context
ctx.HTTPRequest = req.WithContext(t.Context)
}
// set response writer
if ctx.ResponseWriter == nil {
ctx.ResponseWriter = httptest.NewRecorder()
}
// set json api request
if ctx.JSONAPIRequest == nil {
ctx.JSONAPIRequest = &jsonapi.Request{}
}
// create trace
ctx.Tracer, ctx.Context = xo.CreateTracer(ctx.Context, "fire/Tester.WithContext")
defer ctx.Tracer.End()
// yield context
if !ctx.Operation.Action() && ctx.Store != nil {
return ctx.Store.T(ctx.Context, false, func(tc context.Context) error {
return ctx.With(tc, func() error {
return fn(ctx)
})
})
}
return fn(ctx)
}
// Request will run the specified request against the registered handler. This
// function can be used to create custom testing facilities.
func (t *Tester) Request(method, path string, payload string, callback func(*httptest.ResponseRecorder, *http.Request)) {
// create request
request, err := http.NewRequest(method, t.Path(path), strings.NewReader(payload))
if err != nil {
panic(err)
}
// prepare recorder
recorder := httptest.NewRecorder()
// preset jsonapi accept header
request.Header.Set("Accept", jsonapi.MediaType)
// add content type if required
if method == "POST" || method == "PATCH" || method == "DELETE" {
request.Header.Set("Content-Type", jsonapi.MediaType)
}
// set custom headers
for k, v := range t.Header {
request.Header.Set(k, v)
}
// server request
t.Handler.ServeHTTP(recorder, request)
// run callback
callback(recorder, request)
}
// DebugRequest returns a string of information to debug requests.
func (t *Tester) DebugRequest(r *http.Request, rr *httptest.ResponseRecorder) string {
return fmt.Sprintf(`
URL: %s
Header: %s
Status: %d
Header: %v
Body: %v`, r.URL.String(), r.Header, rr.Code, rr.Result().Header, rr.Body.String())
}