-
Notifications
You must be signed in to change notification settings - Fork 6
/
httpfake.go
171 lines (144 loc) · 4.33 KB
/
httpfake.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
// Package httpfake provides a simple wrapper for httptest
// with a handful chainable API for setting up handlers to a fake server.
// This package is aimed to be used in tests where the original external server
// must not be reached. Instead is used in its place a fake server
// which can be configured to handle any request as desired.
package httpfake
import (
"fmt"
"net/http"
"net/http/httptest"
netURL "net/url"
"strings"
"testing"
)
// HTTPFake is the root struct for the fake server
type HTTPFake struct {
Server *httptest.Server
RequestHandlers []*Request
t testing.TB
}
// ServerOption provides a functional signature for providing configuration options to the fake server
type ServerOption func(opts *ServerOptions)
// ServerOptions a configuration object for the fake test server
type ServerOptions struct {
t testing.TB
}
// WithTesting returns a configuration function that allows you to configure the testing object on the fake server.
// The testing object is utilized for assertions set on the request object and will throw a testing error if an
// endpoint is not called.
func WithTesting(t testing.TB) ServerOption {
return func(opts *ServerOptions) {
opts.t = t
}
}
// New starts a httptest.Server as the fake server
// and sets up the initial configuration to this server's request handlers
func New(opts ...ServerOption) *HTTPFake {
fake := &HTTPFake{
RequestHandlers: []*Request{},
}
var serverOpts ServerOptions
for _, opt := range opts {
opt(&serverOpts)
}
fake.t = serverOpts.t
fake.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rh, err := fake.findHandler(r)
if err != nil {
printError(fmt.Sprintf("error finding handler: %v", err))
w.WriteHeader(http.StatusInternalServerError)
return
}
if rh == nil {
errMsg := fmt.Sprintf(
"not found request handler for [%s: %s]; registered handlers are:\n",
r.Method, r.URL,
)
for _, frh := range fake.RequestHandlers {
errMsg += fmt.Sprintf("* [%s: %s]\n", frh.Method, frh.URL.Path)
}
printError(errMsg)
w.WriteHeader(http.StatusNotFound)
return
}
rh.Lock()
rh.called++
rh.Unlock()
if rh.assertions != nil {
if fake.t == nil {
errMsg := fmt.Sprintf("setup error: \"WithTesting\" is required when assertions are set")
panic(errMsg)
}
rh.runAssertions(fake.t, r)
}
if rh.CustomHandle != nil {
rh.CustomHandle(w, r, rh)
return
}
Respond(w, r, rh)
}))
return fake
}
// NewHandler initializes the configuration for a new request handler
func (f *HTTPFake) NewHandler() *Request {
rh := NewRequest()
f.RequestHandlers = append(f.RequestHandlers, rh)
return rh
}
// ResolveURL resolves the full URL to the fake server for a given path
func (f *HTTPFake) ResolveURL(path string, args ...interface{}) string {
format := f.Server.URL + path
return fmt.Sprintf(format, args...)
}
// Reset wipes the request handlers definitions
func (f *HTTPFake) Reset() *HTTPFake {
f.RequestHandlers = []*Request{}
return f
}
// Close shuts down the HTTP Test server, this will block until all outstanding requests on the server have completed.
// If the WithTesting option was specified when setting up the server Close will assert that each http handler
// specified for this server was called
func (f *HTTPFake) Close() {
defer f.Server.Close()
if f.t != nil {
for _, reqHandler := range f.RequestHandlers {
if reqHandler.called == 0 {
f.t.Errorf("httpfake: request handler was specified but not called %s", reqHandler.URL.Path)
}
}
}
}
func (f *HTTPFake) findHandler(r *http.Request) (*Request, error) {
founds := []*Request{}
url := r.URL.String()
path := getURLPath(url)
for _, rh := range f.RequestHandlers {
if rh.Method != r.Method {
continue
}
rhURL, err := netURL.QueryUnescape(rh.URL.String())
if err != nil {
return nil, err
}
if rhURL == url {
return rh, nil
}
// fallback if the income request has query strings
// and there is handlers only for the path
if getURLPath(rhURL) == path {
founds = append(founds, rh)
}
}
// only use the fallback if could find only one match
if len(founds) == 1 {
return founds[0], nil
}
return nil, nil
}
func getURLPath(url string) string {
return strings.Split(url, "?")[0]
}
func printError(msg string) {
fmt.Println("\033[0;31mhttpfake: " + msg + "\033[0m")
}