@marcel91200 / caddy Mirror · read-only / commits / 570d84f7
Fork 0

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