-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
http_test.go
219 lines (197 loc) · 6.67 KB
/
http_test.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
package tinygeoip
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
// these are currently somewhat derivative of the test case constants in
// db_test, but they are intentionally hardcoded here as strings to keep
// separation of methodologies.
const testIPv4Path1 = "/89.160.20.112"
const testIPv4Path2 = "/81.2.69.142"
const testIPv6Path1 = "/2001:218:85a3:0000:0000:8a2e:0370:7334"
const testIPv6Path2 = "/2001:220::1337"
const testIPv4Body1 = `{"country":{"iso_code":"SE"},"location":{"latitude":58.4167,"longitude":15.6167,"accuracy_radius":76}}`
const testIPv4Body2 = `{"country":{"iso_code":"GB"},"location":{"latitude":51.5142,"longitude":-0.0931,"accuracy_radius":10}}`
const testIPv6Body1 = `{"country":{"iso_code":"JP"},"location":{"latitude":35.68536,"longitude":139.75309,"accuracy_radius":100}}`
const testIPv6Body2 = `{"country":{"iso_code":"KR"},"location":{"latitude":37,"longitude":127.5,"accuracy_radius":100}}`
func TestHTTPLookup(t *testing.T) {
var httpCases = []struct {
name string
path string
expectedStatus int
expectedType string
expectedBody string
hasLastModified bool
}{
{
name: "happy1 IPv4",
path: testIPv4Path1,
expectedStatus: http.StatusOK,
expectedType: "application/json",
expectedBody: testIPv4Body1,
hasLastModified: true,
},
{
name: "happy2 IPv4",
path: testIPv4Path2,
expectedStatus: http.StatusOK,
expectedType: "application/json",
expectedBody: testIPv4Body2,
hasLastModified: true,
},
{
name: "happy1 IPv6",
path: testIPv6Path1,
expectedStatus: http.StatusOK,
expectedType: "application/json",
expectedBody: testIPv6Body1,
hasLastModified: true,
},
{
name: "happy2 IPv6",
path: testIPv6Path2,
expectedStatus: http.StatusOK,
expectedType: "application/json",
expectedBody: testIPv6Body2,
hasLastModified: true,
},
{
// re-request the first valid path after other path requests, in
// order to make certain about exercising cache validity.
//
// UPDATE: we no longer have a cache, so this is redundant, but
// it's probably a good idea to leave it here anyhow in case someone
// adds something in the future that could cause a cache/ordering issue.
name: "happy1 IPv4 repeated",
path: testIPv4Path1,
expectedStatus: http.StatusOK,
expectedType: "application/json",
expectedBody: testIPv4Body1,
hasLastModified: true,
},
{
name: "request empty",
path: "",
expectedStatus: http.StatusBadRequest,
expectedType: "application/json",
expectedBody: `{"error": "missing IP query in path, try /192.168.1.1"}`,
hasLastModified: false,
},
{
name: "IP empty",
path: "/",
expectedStatus: http.StatusBadRequest,
expectedType: "application/json",
expectedBody: `{"error": "missing IP query in path, try /192.168.1.1"}`,
hasLastModified: false,
},
{
name: "IP malformed",
path: "/192.168.a.b.c",
expectedStatus: http.StatusBadRequest,
expectedType: "application/json",
expectedBody: `{"error": "could not parse invalid IP address"}`,
hasLastModified: false,
},
{
name: "IP not found",
path: "/127.0.0.1",
expectedStatus: http.StatusInternalServerError,
expectedType: "application/json",
expectedBody: `{"error": "no match for 127.0.0.1 found in database"}`,
hasLastModified: false,
},
}
db := newTestDB(t)
defer db.Close()
handler := NewHTTPHandler(db)
for _, tc := range httpCases {
t.Run(tc.name, func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, tc.path, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
// check the status code is what we expect
if status := rr.Code; status != tc.expectedStatus {
t.Errorf("handler returned wrong status code: got %v want %v",
status, tc.expectedStatus)
}
// check content type is what we expect
if ct := rr.Header().Get("content-type"); ct != tc.expectedType {
t.Errorf("handler returned wrong content-type: got %v want %v",
ct, tc.expectedType)
}
// check the response body is valid json
if bytes := rr.Body.Bytes(); !json.Valid(bytes) {
t.Errorf("json response did not validate! %s", bytes)
}
// check if a last modified header is present when we expect it
// (and that it isn't when we dont), and that it's a valid format
_, ok := rr.Header()["Last-Modified"]
if ok != tc.hasLastModified {
t.Errorf("presence of last-modified header: want %v got %v",
tc.hasLastModified, ok)
}
// check the response body is what we expect
if body := rr.Body.String(); body != tc.expectedBody {
t.Errorf("handler returned unexpected body: got %v want %v",
body, tc.expectedBody)
}
})
}
}
func TestOriginPolicy(t *testing.T) {
db := newTestDB(t)
defer db.Close()
var originCases = []string{"*", "http://foo.example", ""}
for _, op := range originCases {
handler := NewHTTPHandler(db).SetOriginPolicy(op)
req, _ := http.NewRequest(http.MethodGet, "/", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
_, present := (rr.Header())["Access-Control-Allow-Origin"]
if op == "" && present {
t.Errorf("Expected no CORS header but one was present")
} else if val := rr.Header().Get("Access-Control-Allow-Origin"); val != op {
t.Errorf("Unexpected CORS header, want %v got %v", op, val)
}
}
}
// // Below is a leftover test struct I was using instead of httptest.ResponseRecorder,
// // thinking that it would reduce perf overhead in benchmarking, but it didnt seem to
// // make a big difference, so leaving out for now but preserving for future thoughts.
// type NullResponseWriter struct{}
// func (n NullResponseWriter) Header() http.Header {
// return http.Header{}
// }
// func (n NullResponseWriter) Write([]byte) (int, error) {
// return 0, nil
// }
// func (n NullResponseWriter) WriteHeader(statusCode int) {}
func BenchmarkHTTPRequest(b *testing.B) {
db := newTestDB(b)
defer db.Close()
handler := NewHTTPHandler(db)
req, _ := http.NewRequest(http.MethodGet, testIPv4Path1, nil)
rr := httptest.NewRecorder()
b.ResetTimer()
for n := 0; n < b.N; n++ {
handler.ServeHTTP(rr, req)
}
}
func BenchmarkHTTPRequestPar(b *testing.B) {
db := newTestDB(b)
defer db.Close()
handler := NewHTTPHandler(db)
b.RunParallel(func(pb *testing.PB) {
req, _ := http.NewRequest(http.MethodGet, testIPv4Path1, nil)
rr := httptest.NewRecorder()
for pb.Next() {
handler.ServeHTTP(rr, req)
}
})
}