Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
refactored caddytest helpers (#3285)
* refactored caddytest helpers * added cookie jar support. Added support for more http verbs
Mark Sargent committed
Apr 27, 2020 at 13:23 UTC
570d84f7d3642b85f96906ceab6402678177949e
4 files changed
+227
-76
caddytest/caddytest.go
+177
-65
@@ -11,6 +11,7 @@ import (
11
"log"
12
"net"
13
"net/http"
14
+ "net/http/cookiejar"
15
"os"
16
"path"
17
"regexp"
@@ -46,6 +47,30 @@ var (
47
matchCert = regexp.MustCompile(`(/[\w\d\.]+\.crt)`)
48
)
49
50
+// Tester represents an instance of a test client.
51
+type Tester struct {
52
+ Client *http.Client
53
+ t *testing.T
54
+}
55
+
56
+// NewTester will create a new testing client with an attached cookie jar
57
+func NewTester(t *testing.T) *Tester {
58
+
59
+ jar, err := cookiejar.New(nil)
60
+ if err != nil {
61
+ t.Fatalf("failed to create cookiejar: %s", err)
62
+ }
63
+
64
+ return &Tester{
65
+ Client: &http.Client{
66
+ Transport: CreateTestingTransport(),
67
+ Jar: jar,
68
+ Timeout: 5 * time.Second,
69
+ },
70
+ t: t,
71
+ }
72
+}
73
+
74
type configLoadError struct {
75
Response string
76
}
@@ -59,41 +84,42 @@ func timeElapsed(start time.Time, name string) {
84
85
// InitServer this will configure the server with a configurion of a specific
86
// type. The configType must be either "json" or the adapter type.
62
-func InitServer(t *testing.T, rawConfig string, configType string) {
87
+func (tc *Tester) InitServer(rawConfig string, configType string) {
88
64
- if err := initServer(t, rawConfig, configType); err != nil {
65
- t.Logf("failed to load config: %s", err)
66
- t.Fail()
89
+ if err := tc.initServer(rawConfig, configType); err != nil {
90
+ tc.t.Logf("failed to load config: %s", err)
91
+ tc.t.Fail()
92
}
93
}
94
95
// InitServer this will configure the server with a configurion of a specific
96
// type. The configType must be either "json" or the adapter type.
72
-func initServer(t *testing.T, rawConfig string, configType string) error {
97
+func (tc *Tester) initServer(rawConfig string, configType string) error {
98
99
if testing.Short() {
75
- t.SkipNow()
100
+ tc.t.SkipNow()
101
return nil
102
}
103
104
err := validateTestPrerequisites()
105
if err != nil {
81
- t.Skipf("skipping tests as failed integration prerequisites. %s", err)
106
+ tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
107
return nil
108
}
109
85
- t.Cleanup(func() {
86
- if t.Failed() {
110
+ tc.t.Cleanup(func() {
111
+ if tc.t.Failed() {
112
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
113
if err != nil {
89
- t.Log("unable to read the current config")
114
+ tc.t.Log("unable to read the current config")
115
+ return
116
}
117
defer res.Body.Close()
118
body, err := ioutil.ReadAll(res.Body)
119
120
var out bytes.Buffer
121
json.Indent(&out, body, "", " ")
96
- t.Logf("----------- failed with config -----------\n%s", out.String())
122
+ tc.t.Logf("----------- failed with config -----------\n%s", out.String())
123
}
124
})
125
@@ -101,11 +127,10 @@ func initServer(t *testing.T, rawConfig string, configType string) error {
127
client := &http.Client{
128
Timeout: time.Second * 2,
129
}
104
-
130
start := time.Now()
131
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig))
132
if err != nil {
108
- t.Errorf("failed to create request. %s", err)
133
+ tc.t.Errorf("failed to create request. %s", err)
134
return err
135
}
136
@@ -117,7 +142,7 @@ func initServer(t *testing.T, rawConfig string, configType string) error {
142
143
res, err := client.Do(req)
144
if err != nil {
120
- t.Errorf("unable to contact caddy server. %s", err)
145
+ tc.t.Errorf("unable to contact caddy server. %s", err)
146
return err
147
}
148
timeElapsed(start, "caddytest: config load time")
@@ -125,7 +150,7 @@ func initServer(t *testing.T, rawConfig string, configType string) error {
150
defer res.Body.Close()
151
body, err := ioutil.ReadAll(res.Body)
152
if err != nil {
128
- t.Errorf("unable to read response. %s", err)
153
+ tc.t.Errorf("unable to read response. %s", err)
154
return err
155
}
156
@@ -213,8 +238,8 @@ func prependCaddyFilePath(rawConfig string) string {
238
return r
239
}
240
216
-// creates a testing transport that forces call dialing connections to happen locally
217
-func createTestingTransport() *http.Transport {
241
+// CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally
242
+func CreateTestingTransport() *http.Transport {
243
244
dialer := net.Dialer{
245
Timeout: 5 * time.Second,
@@ -243,78 +268,44 @@ func createTestingTransport() *http.Transport {
268
269
// AssertLoadError will load a config and expect an error
270
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
246
- err := initServer(t, rawConfig, configType)
247
- if !strings.Contains(err.Error(), expectedError) {
248
- t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
249
- }
250
-}
251
-
252
-// AssertGetResponse request a URI and assert the status code and the body contains a string
253
-func AssertGetResponse(t *testing.T, requestURI string, statusCode int, expectedBody string) (*http.Response, string) {
254
- resp, body := AssertGetResponseBody(t, requestURI, statusCode)
255
- if !strings.Contains(body, expectedBody) {
256
- t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", requestURI, expectedBody, body)
257
- }
258
- return resp, body
259
-}
260
-
261
-// AssertGetResponseBody request a URI and assert the status code matches
262
-func AssertGetResponseBody(t *testing.T, requestURI string, expectedStatusCode int) (*http.Response, string) {
271
264
- client := &http.Client{
265
- Transport: createTestingTransport(),
266
- }
267
-
268
- resp, err := client.Get(requestURI)
269
- if err != nil {
270
- t.Errorf("failed to call server %s", err)
271
- return nil, ""
272
- }
272
+ tc := NewTester(t)
273
274
- defer resp.Body.Close()
275
-
276
- if expectedStatusCode != resp.StatusCode {
277
- t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode)
278
- }
279
-
280
- body, err := ioutil.ReadAll(resp.Body)
281
- if err != nil {
282
- t.Errorf("unable to read the response body %s", err)
283
- return nil, ""
274
+ err := tc.initServer(rawConfig, configType)
275
+ if !strings.Contains(err.Error(), expectedError) {
276
+ t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
277
}
285
-
286
- return resp, string(body)
278
}
279
280
// AssertRedirect makes a request and asserts the redirection happens
290
-func AssertRedirect(t *testing.T, requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
281
+func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
282
283
redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
284
return http.ErrUseLastResponse
285
}
286
296
- client := &http.Client{
297
- CheckRedirect: redirectPolicyFunc,
298
- Transport: createTestingTransport(),
299
- }
287
+ // using the existing client, we override the check redirect policy for this test
288
+ old := tc.Client.CheckRedirect
289
+ tc.Client.CheckRedirect = redirectPolicyFunc
290
+ defer func() { tc.Client.CheckRedirect = old }()
291
301
- resp, err := client.Get(requestURI)
292
+ resp, err := tc.Client.Get(requestURI)
293
if err != nil {
303
- t.Errorf("failed to call server %s", err)
294
+ tc.t.Errorf("failed to call server %s", err)
295
return nil
296
}
297
298
if expectedStatusCode != resp.StatusCode {
308
- t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode)
299
+ tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode)
300
}
301
302
loc, err := resp.Location()
303
if err != nil {
313
- t.Errorf("requesting \"%s\" expected location: \"%s\" but got error: %s", requestURI, expectedToLocation, err)
304
+ tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got error: %s", requestURI, expectedToLocation, err)
305
}
306
307
if expectedToLocation != loc.String() {
317
- t.Errorf("requesting \"%s\" expected location: \"%s\" but got \"%s\"", requestURI, expectedToLocation, loc.String())
308
+ tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got \"%s\"", requestURI, expectedToLocation, loc.String())
309
}
310
311
return resp
@@ -371,3 +362,124 @@ func AssertAdapt(t *testing.T, rawConfig string, adapterName string, expectedRes
362
t.Fail()
363
}
364
}
365
+
366
+// Generic request functions
367
+
368
+func applyHeaders(t *testing.T, req *http.Request, requestHeaders []string) {
369
+ requestContentType := ""
370
+ for _, requestHeader := range requestHeaders {
371
+ arr := strings.SplitAfterN(requestHeader, ":", 2)
372
+ k := strings.TrimRight(arr[0], ":")
373
+ v := strings.TrimSpace(arr[1])
374
+ if k == "Content-Type" {
375
+ requestContentType = v
376
+ }
377
+ t.Logf("Request header: %s => %s", k, v)
378
+ req.Header.Set(k, v)
379
+ }
380
+
381
+ if requestContentType == "" {
382
+ t.Logf("Content-Type header not provided")
383
+ }
384
+}
385
+
386
+// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions
387
+func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response {
388
+
389
+ resp, err := tc.Client.Do(req)
390
+ if err != nil {
391
+ tc.t.Fatalf("failed to call server %s", err)
392
+ }
393
+
394
+ if expectedStatusCode != resp.StatusCode {
395
+ tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.RequestURI, expectedStatusCode, resp.StatusCode)
396
+ }
397
+
398
+ return resp
399
+}
400
+
401
+// AssertResponse request a URI and assert the status code and the body contains a string
402
+func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
403
+
404
+ resp := tc.AssertResponseCode(req, expectedStatusCode)
405
+
406
+ defer resp.Body.Close()
407
+ bytes, err := ioutil.ReadAll(resp.Body)
408
+ if err != nil {
409
+ tc.t.Fatalf("unable to read the response body %s", err)
410
+ }
411
+
412
+ body := string(bytes)
413
+
414
+ if !strings.Contains(body, expectedBody) {
415
+ tc.t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
416
+ }
417
+
418
+ return resp, body
419
+}
420
+
421
+// Verb specific test functions
422
+
423
+// AssertGetResponse GET a URI and expect a statusCode and body text
424
+func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
425
+
426
+ req, err := http.NewRequest("GET", requestURI, nil)
427
+ if err != nil {
428
+ tc.t.Fatalf("unable to create request %s", err)
429
+ }
430
+
431
+ return tc.AssertResponse(req, expectedStatusCode, expectedBody)
432
+}
433
+
434
+// AssertDeleteResponse request a URI and expect a statusCode and body text
435
+func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
436
+
437
+ req, err := http.NewRequest("DELETE", requestURI, nil)
438
+ if err != nil {
439
+ tc.t.Fatalf("unable to create request %s", err)
440
+ }
441
+
442
+ return tc.AssertResponse(req, expectedStatusCode, expectedBody)
443
+}
444
+
445
+// AssertPostResponseBody POST to a URI and assert the response code and body
446
+func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
447
+
448
+ req, err := http.NewRequest("POST", requestURI, requestBody)
449
+ if err != nil {
450
+ tc.t.Errorf("failed to create request %s", err)
451
+ return nil, ""
452
+ }
453
+
454
+ applyHeaders(tc.t, req, requestHeaders)
455
+
456
+ return tc.AssertResponse(req, expectedStatusCode, expectedBody)
457
+}
458
+
459
+// AssertPutResponseBody PUT to a URI and assert the response code and body
460
+func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
461
+
462
+ req, err := http.NewRequest("PUT", requestURI, requestBody)
463
+ if err != nil {
464
+ tc.t.Errorf("failed to create request %s", err)
465
+ return nil, ""
466
+ }
467
+
468
+ applyHeaders(tc.t, req, requestHeaders)
469
+
470
+ return tc.AssertResponse(req, expectedStatusCode, expectedBody)
471
+}
472
+
473
+// AssertPatchResponseBody PATCH to a URI and assert the response code and body
474
+func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
475
+
476
+ req, err := http.NewRequest("PATCH", requestURI, requestBody)
477
+ if err != nil {
478
+ tc.t.Errorf("failed to create request %s", err)
479
+ return nil, ""
480
+ }
481
+
482
+ applyHeaders(tc.t, req, requestHeaders)
483
+
484
+ return tc.AssertResponse(req, expectedStatusCode, expectedBody)
485
+}
caddytest/integration/caddyfile_test.go
+40
-5
@@ -1,6 +1,8 @@
1
package integration
2
3
import (
4
+ "net/http"
5
+ "net/url"
6
"testing"
7
8
"github.com/caddyserver/caddy/v2/caddytest"
@@ -9,7 +11,8 @@ import (
11
func TestRespond(t *testing.T) {
12
13
// arrange
12
- caddytest.InitServer(t, `
14
+ tester := caddytest.NewTester(t)
15
+ tester.InitServer(`
16
{
17
http_port 9080
18
https_port 9443
@@ -23,13 +26,14 @@ func TestRespond(t *testing.T) {
26
`, "caddyfile")
27
28
// act and assert
26
- caddytest.AssertGetResponse(t, "http://localhost:9080/version", 200, "hello from localhost")
29
+ tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost")
30
}
31
32
func TestRedirect(t *testing.T) {
33
34
// arrange
32
- caddytest.InitServer(t, `
35
+ tester := caddytest.NewTester(t)
36
+ tester.InitServer(`
37
{
38
http_port 9080
39
https_port 9443
@@ -46,10 +50,10 @@ func TestRedirect(t *testing.T) {
50
`, "caddyfile")
51
52
// act and assert
49
- caddytest.AssertRedirect(t, "http://localhost:9080/", "http://localhost:9080/hello", 301)
53
+ tester.AssertRedirect("http://localhost:9080/", "http://localhost:9080/hello", 301)
54
55
// follow redirect
52
- caddytest.AssertGetResponse(t, "http://localhost:9080/", 200, "hello from localhost")
56
+ tester.AssertGetResponse("http://localhost:9080/", 200, "hello from localhost")
57
}
58
59
func TestDuplicateHosts(t *testing.T) {
@@ -66,3 +70,34 @@ func TestDuplicateHosts(t *testing.T) {
70
"caddyfile",
71
"duplicate site address not allowed")
72
}
73
+
74
+func TestReadCookie(t *testing.T) {
75
+
76
+ localhost, _ := url.Parse("http://localhost")
77
+ cookie := http.Cookie{
78
+ Name: "clientname",
79
+ Value: "caddytest",
80
+ }
81
+
82
+ // arrange
83
+ tester := caddytest.NewTester(t)
84
+ tester.Client.Jar.SetCookies(localhost, []*http.Cookie{&cookie})
85
+ tester.InitServer(`
86
+ {
87
+ http_port 9080
88
+ https_port 9443
89
+ }
90
+
91
+ localhost:9080 {
92
+ templates {
93
+ root testdata
94
+ }
95
+ file_server {
96
+ root testdata
97
+ }
98
+ }
99
+ `, "caddyfile")
100
+
101
+ // act and assert
102
+ tester.AssertGetResponse("http://localhost:9080/cookie.html", 200, "<h2>Cookie.ClientName caddytest</h2>")
103
+}
caddytest/integration/sni_test.go
+9
-6
@@ -9,7 +9,8 @@ import (
9
func TestDefaultSNI(t *testing.T) {
10
11
// arrange
12
- caddytest.InitServer(t, `{
12
+ tester := caddytest.NewTester(t)
13
+ tester.InitServer(`{
14
"apps": {
15
"http": {
16
"http_port": 9080,
@@ -98,13 +99,14 @@ func TestDefaultSNI(t *testing.T) {
99
100
// act and assert
101
// makes a request with no sni
101
- caddytest.AssertGetResponse(t, "https://127.0.0.1:9443/version", 200, "hello from a")
102
+ tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
103
}
104
105
func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
106
107
// arrange
107
- caddytest.InitServer(t, `
108
+ tester := caddytest.NewTester(t)
109
+ tester.InitServer(`
110
{
111
"apps": {
112
"http": {
@@ -198,13 +200,14 @@ func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
200
201
// act and assert
202
// makes a request with no sni
201
- caddytest.AssertGetResponse(t, "https://127.0.0.1:9443/version", 200, "hello from a")
203
+ tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
204
}
205
206
func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
207
208
// arrange
207
- caddytest.InitServer(t, `
209
+ tester := caddytest.NewTester(t)
210
+ tester.InitServer(`
211
{
212
"apps": {
213
"http": {
@@ -270,7 +273,7 @@ func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
273
274
// act and assert
275
// makes a request with no sni
273
- caddytest.AssertGetResponse(t, "https://127.0.0.1:9443/version", 200, "hello from a")
276
+ tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
277
}
278
279
func TestHttpOnlyOnDomainWithSNI(t *testing.T) {
caddytest/integration/testdata/cookie.html
new
+1
@@ -0,0 +1 @@
1
+<h2>Cookie.ClientName {{.Cookie "clientname"}}</h2>
\ No newline at end of file