mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 13:43:47 +03:00
refactored caddytest helpers (#3285)
* refactored caddytest helpers * added cookie jar support. Added support for more http verbs
This commit is contained in:
parent
a6761153cb
commit
570d84f7d3
4 changed files with 227 additions and 76 deletions
|
@ -11,6 +11,7 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
|
@ -46,6 +47,30 @@ var (
|
|||
matchCert = regexp.MustCompile(`(/[\w\d\.]+\.crt)`)
|
||||
)
|
||||
|
||||
// Tester represents an instance of a test client.
|
||||
type Tester struct {
|
||||
Client *http.Client
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
// NewTester will create a new testing client with an attached cookie jar
|
||||
func NewTester(t *testing.T) *Tester {
|
||||
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create cookiejar: %s", err)
|
||||
}
|
||||
|
||||
return &Tester{
|
||||
Client: &http.Client{
|
||||
Transport: CreateTestingTransport(),
|
||||
Jar: jar,
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
type configLoadError struct {
|
||||
Response string
|
||||
}
|
||||
|
@ -59,41 +84,42 @@ func timeElapsed(start time.Time, name string) {
|
|||
|
||||
// InitServer this will configure the server with a configurion of a specific
|
||||
// type. The configType must be either "json" or the adapter type.
|
||||
func InitServer(t *testing.T, rawConfig string, configType string) {
|
||||
func (tc *Tester) InitServer(rawConfig string, configType string) {
|
||||
|
||||
if err := initServer(t, rawConfig, configType); err != nil {
|
||||
t.Logf("failed to load config: %s", err)
|
||||
t.Fail()
|
||||
if err := tc.initServer(rawConfig, configType); err != nil {
|
||||
tc.t.Logf("failed to load config: %s", err)
|
||||
tc.t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// InitServer this will configure the server with a configurion of a specific
|
||||
// type. The configType must be either "json" or the adapter type.
|
||||
func initServer(t *testing.T, rawConfig string, configType string) error {
|
||||
func (tc *Tester) initServer(rawConfig string, configType string) error {
|
||||
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
tc.t.SkipNow()
|
||||
return nil
|
||||
}
|
||||
|
||||
err := validateTestPrerequisites()
|
||||
if err != nil {
|
||||
t.Skipf("skipping tests as failed integration prerequisites. %s", err)
|
||||
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
if t.Failed() {
|
||||
tc.t.Cleanup(func() {
|
||||
if tc.t.Failed() {
|
||||
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
|
||||
if err != nil {
|
||||
t.Log("unable to read the current config")
|
||||
tc.t.Log("unable to read the current config")
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
|
||||
var out bytes.Buffer
|
||||
json.Indent(&out, body, "", " ")
|
||||
t.Logf("----------- failed with config -----------\n%s", out.String())
|
||||
tc.t.Logf("----------- failed with config -----------\n%s", out.String())
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -101,11 +127,10 @@ func initServer(t *testing.T, rawConfig string, configType string) error {
|
|||
client := &http.Client{
|
||||
Timeout: time.Second * 2,
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig))
|
||||
if err != nil {
|
||||
t.Errorf("failed to create request. %s", err)
|
||||
tc.t.Errorf("failed to create request. %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -117,7 +142,7 @@ func initServer(t *testing.T, rawConfig string, configType string) error {
|
|||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("unable to contact caddy server. %s", err)
|
||||
tc.t.Errorf("unable to contact caddy server. %s", err)
|
||||
return err
|
||||
}
|
||||
timeElapsed(start, "caddytest: config load time")
|
||||
|
@ -125,7 +150,7 @@ func initServer(t *testing.T, rawConfig string, configType string) error {
|
|||
defer res.Body.Close()
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Errorf("unable to read response. %s", err)
|
||||
tc.t.Errorf("unable to read response. %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -213,8 +238,8 @@ func prependCaddyFilePath(rawConfig string) string {
|
|||
return r
|
||||
}
|
||||
|
||||
// creates a testing transport that forces call dialing connections to happen locally
|
||||
func createTestingTransport() *http.Transport {
|
||||
// CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally
|
||||
func CreateTestingTransport() *http.Transport {
|
||||
|
||||
dialer := net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
|
@ -243,78 +268,44 @@ func createTestingTransport() *http.Transport {
|
|||
|
||||
// AssertLoadError will load a config and expect an error
|
||||
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
|
||||
err := initServer(t, rawConfig, configType)
|
||||
|
||||
tc := NewTester(t)
|
||||
|
||||
err := tc.initServer(rawConfig, configType)
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// AssertGetResponse request a URI and assert the status code and the body contains a string
|
||||
func AssertGetResponse(t *testing.T, requestURI string, statusCode int, expectedBody string) (*http.Response, string) {
|
||||
resp, body := AssertGetResponseBody(t, requestURI, statusCode)
|
||||
if !strings.Contains(body, expectedBody) {
|
||||
t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", requestURI, expectedBody, body)
|
||||
}
|
||||
return resp, body
|
||||
}
|
||||
|
||||
// AssertGetResponseBody request a URI and assert the status code matches
|
||||
func AssertGetResponseBody(t *testing.T, requestURI string, expectedStatusCode int) (*http.Response, string) {
|
||||
|
||||
client := &http.Client{
|
||||
Transport: createTestingTransport(),
|
||||
}
|
||||
|
||||
resp, err := client.Get(requestURI)
|
||||
if err != nil {
|
||||
t.Errorf("failed to call server %s", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if expectedStatusCode != resp.StatusCode {
|
||||
t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("unable to read the response body %s", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
return resp, string(body)
|
||||
}
|
||||
|
||||
// AssertRedirect makes a request and asserts the redirection happens
|
||||
func AssertRedirect(t *testing.T, requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
|
||||
func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
|
||||
|
||||
redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
CheckRedirect: redirectPolicyFunc,
|
||||
Transport: createTestingTransport(),
|
||||
}
|
||||
// using the existing client, we override the check redirect policy for this test
|
||||
old := tc.Client.CheckRedirect
|
||||
tc.Client.CheckRedirect = redirectPolicyFunc
|
||||
defer func() { tc.Client.CheckRedirect = old }()
|
||||
|
||||
resp, err := client.Get(requestURI)
|
||||
resp, err := tc.Client.Get(requestURI)
|
||||
if err != nil {
|
||||
t.Errorf("failed to call server %s", err)
|
||||
tc.t.Errorf("failed to call server %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if expectedStatusCode != resp.StatusCode {
|
||||
t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode)
|
||||
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode)
|
||||
}
|
||||
|
||||
loc, err := resp.Location()
|
||||
if err != nil {
|
||||
t.Errorf("requesting \"%s\" expected location: \"%s\" but got error: %s", requestURI, expectedToLocation, err)
|
||||
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got error: %s", requestURI, expectedToLocation, err)
|
||||
}
|
||||
|
||||
if expectedToLocation != loc.String() {
|
||||
t.Errorf("requesting \"%s\" expected location: \"%s\" but got \"%s\"", requestURI, expectedToLocation, loc.String())
|
||||
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got \"%s\"", requestURI, expectedToLocation, loc.String())
|
||||
}
|
||||
|
||||
return resp
|
||||
|
@ -371,3 +362,124 @@ func AssertAdapt(t *testing.T, rawConfig string, adapterName string, expectedRes
|
|||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// Generic request functions
|
||||
|
||||
func applyHeaders(t *testing.T, req *http.Request, requestHeaders []string) {
|
||||
requestContentType := ""
|
||||
for _, requestHeader := range requestHeaders {
|
||||
arr := strings.SplitAfterN(requestHeader, ":", 2)
|
||||
k := strings.TrimRight(arr[0], ":")
|
||||
v := strings.TrimSpace(arr[1])
|
||||
if k == "Content-Type" {
|
||||
requestContentType = v
|
||||
}
|
||||
t.Logf("Request header: %s => %s", k, v)
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
if requestContentType == "" {
|
||||
t.Logf("Content-Type header not provided")
|
||||
}
|
||||
}
|
||||
|
||||
// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions
|
||||
func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response {
|
||||
|
||||
resp, err := tc.Client.Do(req)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("failed to call server %s", err)
|
||||
}
|
||||
|
||||
if expectedStatusCode != resp.StatusCode {
|
||||
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.RequestURI, expectedStatusCode, resp.StatusCode)
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// AssertResponse request a URI and assert the status code and the body contains a string
|
||||
func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
resp := tc.AssertResponseCode(req, expectedStatusCode)
|
||||
|
||||
defer resp.Body.Close()
|
||||
bytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("unable to read the response body %s", err)
|
||||
}
|
||||
|
||||
body := string(bytes)
|
||||
|
||||
if !strings.Contains(body, expectedBody) {
|
||||
tc.t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
||||
}
|
||||
|
||||
return resp, body
|
||||
}
|
||||
|
||||
// Verb specific test functions
|
||||
|
||||
// AssertGetResponse GET a URI and expect a statusCode and body text
|
||||
func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("GET", requestURI, nil)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("unable to create request %s", err)
|
||||
}
|
||||
|
||||
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
|
||||
}
|
||||
|
||||
// AssertDeleteResponse request a URI and expect a statusCode and body text
|
||||
func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("DELETE", requestURI, nil)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("unable to create request %s", err)
|
||||
}
|
||||
|
||||
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
|
||||
}
|
||||
|
||||
// AssertPostResponseBody POST to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("POST", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
applyHeaders(tc.t, req, requestHeaders)
|
||||
|
||||
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
|
||||
}
|
||||
|
||||
// AssertPutResponseBody PUT to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("PUT", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
applyHeaders(tc.t, req, requestHeaders)
|
||||
|
||||
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
|
||||
}
|
||||
|
||||
// AssertPatchResponseBody PATCH to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("PATCH", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
applyHeaders(tc.t, req, requestHeaders)
|
||||
|
||||
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
|
@ -9,7 +11,8 @@ import (
|
|||
func TestRespond(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
caddytest.InitServer(t, `
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
|
@ -23,13 +26,14 @@ func TestRespond(t *testing.T) {
|
|||
`, "caddyfile")
|
||||
|
||||
// act and assert
|
||||
caddytest.AssertGetResponse(t, "http://localhost:9080/version", 200, "hello from localhost")
|
||||
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost")
|
||||
}
|
||||
|
||||
func TestRedirect(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
caddytest.InitServer(t, `
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
|
@ -46,10 +50,10 @@ func TestRedirect(t *testing.T) {
|
|||
`, "caddyfile")
|
||||
|
||||
// act and assert
|
||||
caddytest.AssertRedirect(t, "http://localhost:9080/", "http://localhost:9080/hello", 301)
|
||||
tester.AssertRedirect("http://localhost:9080/", "http://localhost:9080/hello", 301)
|
||||
|
||||
// follow redirect
|
||||
caddytest.AssertGetResponse(t, "http://localhost:9080/", 200, "hello from localhost")
|
||||
tester.AssertGetResponse("http://localhost:9080/", 200, "hello from localhost")
|
||||
}
|
||||
|
||||
func TestDuplicateHosts(t *testing.T) {
|
||||
|
@ -66,3 +70,34 @@ func TestDuplicateHosts(t *testing.T) {
|
|||
"caddyfile",
|
||||
"duplicate site address not allowed")
|
||||
}
|
||||
|
||||
func TestReadCookie(t *testing.T) {
|
||||
|
||||
localhost, _ := url.Parse("http://localhost")
|
||||
cookie := http.Cookie{
|
||||
Name: "clientname",
|
||||
Value: "caddytest",
|
||||
}
|
||||
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.Client.Jar.SetCookies(localhost, []*http.Cookie{&cookie})
|
||||
tester.InitServer(`
|
||||
{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
}
|
||||
|
||||
localhost:9080 {
|
||||
templates {
|
||||
root testdata
|
||||
}
|
||||
file_server {
|
||||
root testdata
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// act and assert
|
||||
tester.AssertGetResponse("http://localhost:9080/cookie.html", 200, "<h2>Cookie.ClientName caddytest</h2>")
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ import (
|
|||
func TestDefaultSNI(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
caddytest.InitServer(t, `{
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
|
@ -98,13 +99,14 @@ func TestDefaultSNI(t *testing.T) {
|
|||
|
||||
// act and assert
|
||||
// makes a request with no sni
|
||||
caddytest.AssertGetResponse(t, "https://127.0.0.1:9443/version", 200, "hello from a")
|
||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
|
||||
}
|
||||
|
||||
func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
caddytest.InitServer(t, `
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
|
@ -198,13 +200,14 @@ func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
|
|||
|
||||
// act and assert
|
||||
// makes a request with no sni
|
||||
caddytest.AssertGetResponse(t, "https://127.0.0.1:9443/version", 200, "hello from a")
|
||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
|
||||
}
|
||||
|
||||
func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
caddytest.InitServer(t, `
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
|
@ -270,7 +273,7 @@ func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
|
|||
|
||||
// act and assert
|
||||
// makes a request with no sni
|
||||
caddytest.AssertGetResponse(t, "https://127.0.0.1:9443/version", 200, "hello from a")
|
||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
|
||||
}
|
||||
|
||||
func TestHttpOnlyOnDomainWithSNI(t *testing.T) {
|
||||
|
|
1
caddytest/integration/testdata/cookie.html
vendored
Normal file
1
caddytest/integration/testdata/cookie.html
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<h2>Cookie.ClientName {{.Cookie "clientname"}}</h2>
|
Loading…
Reference in a new issue