Go API Documentation

github.com/caddyserver/caddy/v2/caddytest

No package summary is available.

Package

Files: 1. Third party imports: 1. Imports from organisation: 0. Tests: 0. Benchmarks: 0.

Constants

const initConfig = `{
	admin localhost:2999
}
`

Vars

Default testing values

var Default = Defaults{
	AdminPort:		2999,	// different from what a real server also running on a developer's machine might be
	Certificates:		[]string{"/caddy.localhost.crt", "/caddy.localhost.key"},
	TestRequestTimeout:	5 * time.Second,
	LoadRequestTimeout:	5 * time.Second,
}
var (
	matchKey	= regexp.MustCompile(`(/[\w\d\.]+\.key)`)
	matchCert	= regexp.MustCompile(`(/[\w\d\.]+\.crt)`)
)

Types

Defaults

Defaults store any configuration required to make the tests run

type Defaults struct {
	// Port we expect caddy to listening on
	AdminPort	int
	// Certificates we expect to be loaded before attempting to run the tests
	Certificates	[]string
	// TestRequestTimeout is the time to wait for a http request to
	TestRequestTimeout	time.Duration
	// LoadRequestTimeout is the time to wait for the config to be loaded against the caddy server
	LoadRequestTimeout	time.Duration
}

Tester

Tester represents an instance of a test client.

type Tester struct {
	Client		*http.Client
	configLoaded	bool
	t		testing.TB
}

configLoadError

This type doesn't have documentation.

type configLoadError struct {
	Response string
}

Functions

func AssertAdapt

AssertAdapt adapts a config and then tests it against an expected result

func AssertAdapt(t testing.TB, rawConfig string, adapterName string, expectedResponse string) {
	ok := CompareAdapt(t, "Caddyfile", rawConfig, adapterName, expectedResponse)
	if !ok {
		t.Fail()
	}
}

Cognitive complexity: 2, Cyclomatic complexity: 2

func AssertLoadError

AssertLoadError will load a config and expect an error

func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
	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())
	}
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: strings.Contains.

func CompareAdapt

CompareAdapt adapts a config and then compares it against an expected result

func CompareAdapt(t testing.TB, filename, rawConfig string, adapterName string, expectedResponse string) bool {
	cfgAdapter := caddyconfig.GetAdapter(adapterName)
	if cfgAdapter == nil {
		t.Logf("unrecognized config adapter '%s'", adapterName)
		return false
	}

	options := make(map[string]any)

	result, warnings, err := cfgAdapter.Adapt([]byte(rawConfig), options)
	if err != nil {
		t.Logf("adapting config using %s adapter: %v", adapterName, err)
		return false
	}

	// prettify results to keep tests human-manageable
	var prettyBuf bytes.Buffer
	err = json.Indent(&prettyBuf, result, "", "\t")
	if err != nil {
		return false
	}
	result = prettyBuf.Bytes()

	if len(warnings) > 0 {
		for _, w := range warnings {
			t.Logf("warning: %s:%d: %s: %s", filename, w.Line, w.Directive, w.Message)
		}
	}

	diff := difflib.Diff(
		strings.Split(expectedResponse, "\n"),
		strings.Split(string(result), "\n"))

	// scan for failure
	failed := false
	for _, d := range diff {
		if d.Delta != difflib.Common {
			failed = true
			break
		}
	}

	if failed {
		for _, d := range diff {
			switch d.Delta {
			case difflib.Common:
				fmt.Printf("  %s\n", d.Payload)
			case difflib.LeftOnly:
				fmt.Printf(" - %s\n", d.Payload)
			case difflib.RightOnly:
				fmt.Printf(" + %s\n", d.Payload)
			}
		}
		return false
	}
	return true
}

Cognitive complexity: 26, Cyclomatic complexity: 14

Uses: bytes.Buffer, caddyconfig.GetAdapter, difflib.Common, difflib.Diff, difflib.LeftOnly, difflib.RightOnly, fmt.Printf, json.Indent, strings.Split.

func CreateTestingTransport

CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally

func CreateTestingTransport() *http.Transport {
	dialer := net.Dialer{
		Timeout:	5 * time.Second,
		KeepAlive:	5 * time.Second,
		DualStack:	true,
	}

	dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
		parts := strings.Split(addr, ":")
		destAddr := fmt.Sprintf("127.0.0.1:%s", parts[1])
		log.Printf("caddytest: redirecting the dialer from %s to %s", addr, destAddr)
		return dialer.DialContext(ctx, network, destAddr)
	}

	return &http.Transport{
		Proxy:			http.ProxyFromEnvironment,
		DialContext:		dialContext,
		ForceAttemptHTTP2:	true,
		MaxIdleConns:		100,
		IdleConnTimeout:	90 * time.Second,
		TLSHandshakeTimeout:	5 * time.Second,
		ExpectContinueTimeout:	1 * time.Second,
		TLSClientConfig:	&tls.Config{InsecureSkipVerify: true},	//nolint:gosec
	}
}

Cognitive complexity: 4, Cyclomatic complexity: 1

Uses: context.Context, fmt.Sprintf, http.ProxyFromEnvironment, http.Transport, log.Printf, net.Conn, net.Dialer, strings.Split, time.Second, tls.Config.

func NewTester

NewTester will create a new testing client with an attached cookie jar

func NewTester(t testing.TB) *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:	Default.TestRequestTimeout,
		},
		configLoaded:	false,
		t:		t,
	}
}

Cognitive complexity: 4, Cyclomatic complexity: 2

Uses: cookiejar.New, http.Client.

func (*Tester) AssertDeleteResponse

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)
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: http.NewRequest.

func (*Tester) AssertGetResponse

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)
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: http.NewRequest.

func (*Tester) AssertPatchResponseBody

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)
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: http.NewRequest.

func (*Tester) AssertPostResponseBody

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)
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: http.NewRequest.

func (*Tester) AssertPutResponseBody

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)
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: http.NewRequest.

func (*Tester) AssertRedirect

AssertRedirect makes a request and asserts the redirection happens

func (tc *Tester) 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.Client.CheckRedirect
	tc.Client.CheckRedirect = redirectPolicyFunc
	defer func() { tc.Client.CheckRedirect = old }()

	resp, err := tc.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
}

Cognitive complexity: 14, Cyclomatic complexity: 8

Uses: http.ErrUseLastResponse, http.Request.

func (*Tester) AssertResponse

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 := 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
}

Cognitive complexity: 4, Cyclomatic complexity: 3

Uses: io.ReadAll.

func (*Tester) AssertResponseCode

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.URL.RequestURI(), expectedStatusCode, resp.StatusCode)
	}

	return resp
}

Cognitive complexity: 4, Cyclomatic complexity: 3

func (*Tester) InitServer

InitServer this will configure the server with a configurion of a specific type. The configType must be either "json" or the adapter type.

func (tc *Tester) InitServer(rawConfig string, configType string) {
	if err := tc.initServer(rawConfig, configType); err != nil {
		tc.t.Logf("failed to load config: %s", err)
		tc.t.Fail()
	}
	if err := tc.ensureConfigRunning(rawConfig, configType); err != nil {
		tc.t.Logf("failed ensuring config is running: %s", err)
		tc.t.Fail()
	}
}

Cognitive complexity: 4, Cyclomatic complexity: 3

func (configLoadError) Error

func (e configLoadError) Error() string	{ return e.Response }

Cognitive complexity: 0, Cyclomatic complexity: 1

Private functions

func applyHeaders

applyHeaders (t testing.TB, req *http.Request, requestHeaders []string)
References: strings.SplitAfterN, strings.TrimRight, strings.TrimSpace.

func getIntegrationDir

getIntegrationDir () string
References: path.Dir, runtime.Caller.

func isCaddyAdminRunning

isCaddyAdminRunning () error
References: fmt.Errorf, fmt.Sprintf, http.Client.

func prependCaddyFilePath

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

prependCaddyFilePath (rawConfig string) string

func timeElapsed

timeElapsed (start time.Time, name string)
References: log.Printf, time.Since.

func validateTestPrerequisites

validateTestPrerequisites ensures the certificates are available in the designated path and Caddy sub-process is running.

validateTestPrerequisites (t testing.TB) error
References: caddycmd.Main, errors.Is, fmt.Errorf, fs.ErrNotExist, os.Args, os.CreateTemp, os.Remove, os.Stat, time.Second, time.Sleep.

func ensureConfigRunning

ensureConfigRunning (rawConfig string, configType string) error
References: caddyconfig.GetAdapter, errors.New, fmt.Errorf, fmt.Sprintf, http.Client, io.ReadAll, json.Unmarshal, reflect.DeepEqual, time.Second, time.Sleep.

func initServer

InitServer this will configure the server with a configurion of a specific type. The configType must be either "json" or the adapter type.

initServer (rawConfig string, configType string) error
References: bytes.Buffer, fmt.Sprintf, http.Client, http.Get, http.NewRequest, io.ReadAll, json.Indent, json.Marshal, json.Unmarshal, strings.NewReader, testing.Short, time.Now.


Tests

Files: 1. Third party imports: 0. Imports from organisation: 0. Tests: 2. Benchmarks: 0.

Test functions

TestLoadUnorderedJSON

References: http.MethodGet, http.NewRequest.

TestReplaceCertificatePaths

References: strings.Contains.