mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 21:53:48 +03:00
241 lines
7.2 KiB
Go
241 lines
7.2 KiB
Go
package caddytest
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"path"
|
|
"regexp"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// use the convention to replace /[certificatename].[crt|key] with the full path
|
|
// this helps reduce the noise in test configurations and also allow this
|
|
// to run in any path
|
|
func prependCaddyFilePath(rawConfig string) string {
|
|
r := matchKey.ReplaceAllString(rawConfig, getIntegrationDir()+"$1")
|
|
r = matchCert.ReplaceAllString(r, getIntegrationDir()+"$1")
|
|
return r
|
|
}
|
|
|
|
func getIntegrationDir() string {
|
|
_, filename, _, ok := runtime.Caller(1)
|
|
if !ok {
|
|
panic("unable to determine the current file path")
|
|
}
|
|
|
|
return path.Dir(filename)
|
|
}
|
|
|
|
var (
|
|
matchKey = regexp.MustCompile(`(/[\w\d\.]+\.key)`)
|
|
matchCert = regexp.MustCompile(`(/[\w\d\.]+\.crt)`)
|
|
)
|
|
|
|
type TestHarness struct {
|
|
t testing.TB
|
|
|
|
tester *Tester
|
|
}
|
|
|
|
// StartHarness creates and starts a test harness environment which spans the lifetime a single caddy instance
|
|
// This is used for the integration tests
|
|
func StartHarness(t *testing.T) *TestHarness {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
return nil
|
|
}
|
|
o := &TestHarness{t: t}
|
|
o.init()
|
|
return o
|
|
}
|
|
|
|
func (tc *TestHarness) Tester() *Tester {
|
|
return tc.tester
|
|
}
|
|
|
|
func (tc *TestHarness) Client() *http.Client {
|
|
return tc.tester.Client
|
|
}
|
|
|
|
func (tc *TestHarness) LoadConfig(rawConfig, configType string) {
|
|
rawConfig = prependCaddyFilePath(rawConfig)
|
|
err := tc.tester.LoadConfig(rawConfig, configType)
|
|
require.NoError(tc.t, err)
|
|
}
|
|
|
|
func (tc *TestHarness) init() {
|
|
// start the server
|
|
tester, err := NewTester()
|
|
if err != nil {
|
|
tc.t.Errorf("Failed to create caddy tester: %s", err)
|
|
return
|
|
}
|
|
tc.tester = tester
|
|
err = tc.tester.LaunchCaddy()
|
|
if err != nil {
|
|
tc.t.Errorf("Failed to launch caddy server: %s", err)
|
|
tc.t.FailNow()
|
|
return
|
|
}
|
|
// cleanup
|
|
tc.t.Cleanup(func() {
|
|
func() {
|
|
if tc.t.Failed() {
|
|
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", tc.tester.adminPort))
|
|
if err != nil {
|
|
tc.t.Log("unable to read the current config")
|
|
return
|
|
}
|
|
defer res.Body.Close()
|
|
body, _ := io.ReadAll(res.Body)
|
|
|
|
var out bytes.Buffer
|
|
_ = json.Indent(&out, body, "", " ")
|
|
tc.t.Logf("----------- failed with config -----------\n%s", out.String())
|
|
}
|
|
}()
|
|
// shutdown server after extracing the config
|
|
err = tc.tester.CleanupCaddy()
|
|
if err != nil {
|
|
tc.t.Errorf("failed to clean up caddy instance: %s", err)
|
|
tc.t.FailNow()
|
|
}
|
|
})
|
|
}
|
|
|
|
// AssertRedirect makes a request and asserts the redirection happens
|
|
func (tc *TestHarness) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
|
|
redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
}
|
|
|
|
// using the existing client, we override the check redirect policy for this test
|
|
old := tc.tester.Client.CheckRedirect
|
|
tc.tester.Client.CheckRedirect = redirectPolicyFunc
|
|
defer func() { tc.tester.Client.CheckRedirect = old }()
|
|
|
|
resp, err := tc.tester.Client.Get(requestURI)
|
|
if err != nil {
|
|
tc.t.Errorf("failed to call server %s", err)
|
|
return nil
|
|
}
|
|
|
|
if 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 {
|
|
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got error: %s", requestURI, expectedToLocation, err)
|
|
}
|
|
if loc == nil && expectedToLocation != "" {
|
|
tc.t.Errorf("requesting \"%s\" expected a Location header, but didn't get one", requestURI)
|
|
}
|
|
if loc != nil {
|
|
if expectedToLocation != loc.String() {
|
|
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got \"%s\"", requestURI, expectedToLocation, loc.String())
|
|
}
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions
|
|
func (tc *TestHarness) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response {
|
|
resp, err := tc.tester.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.URL.RequestURI(), expectedStatusCode, resp.StatusCode)
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
// AssertResponse request a URI and assert the status code and the body contains a string
|
|
func (tc *TestHarness) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
|
resp := tc.AssertResponseCode(req, expectedStatusCode)
|
|
|
|
defer resp.Body.Close()
|
|
bytes, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
tc.t.Fatalf("unable to read the response body %s", err)
|
|
}
|
|
|
|
body := string(bytes)
|
|
|
|
if 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 *TestHarness) 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 *TestHarness) 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 *TestHarness) 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 *TestHarness) 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 *TestHarness) 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)
|
|
}
|