This commit is contained in:
a 2024-06-18 19:44:05 -05:00
parent 93a1853022
commit 2619271a5c
No known key found for this signature in database
GPG key ID: 374BC539FE795AF0
16 changed files with 534 additions and 515 deletions

View file

@ -1,14 +1,12 @@
package caddytest package caddytest
import ( import (
"bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"log" "log"
"net" "net"
"net/http" "net/http"
@ -16,14 +14,10 @@ import (
"os" "os"
"path" "path"
"reflect" "reflect"
"regexp"
"runtime" "runtime"
"strings" "strings"
"testing"
"time" "time"
"github.com/aryann/difflib"
caddycmd "github.com/caddyserver/caddy/v2/cmd" caddycmd "github.com/caddyserver/caddy/v2/cmd"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
@ -51,23 +45,18 @@ var Default = Defaults{
LoadRequestTimeout: 5 * time.Second, LoadRequestTimeout: 5 * time.Second,
} }
var (
matchKey = regexp.MustCompile(`(/[\w\d\.]+\.key)`)
matchCert = regexp.MustCompile(`(/[\w\d\.]+\.crt)`)
)
// Tester represents an instance of a test client. // Tester represents an instance of a test client.
type Tester struct { type Tester struct {
Client *http.Client Client *http.Client
configLoaded bool configLoaded bool
t testing.TB configFileName string
} }
// NewTester will create a new testing client with an attached cookie jar // NewTester will create a new testing client with an attached cookie jar
func NewTester(t testing.TB) *Tester { func NewTester() (*Tester, error) {
jar, err := cookiejar.New(nil) jar, err := cookiejar.New(nil)
if err != nil { if err != nil {
t.Fatalf("failed to create cookiejar: %s", err) return nil, fmt.Errorf("failed to create cookiejar: %w", err)
} }
return &Tester{ return &Tester{
@ -77,12 +66,7 @@ func NewTester(t testing.TB) *Tester {
Timeout: Default.TestRequestTimeout, Timeout: Default.TestRequestTimeout,
}, },
configLoaded: false, configLoaded: false,
t: t, }, nil
}
}
func (t *Tester) T() testing.TB {
return t.t
} }
type configLoadError struct { type configLoadError struct {
@ -97,83 +81,40 @@ func timeElapsed(start time.Time, name string) {
} }
// launch caddy will start the server // launch caddy will start the server
func (tc *Tester) LaunchCaddy() { func (tc *Tester) LaunchCaddy() error {
if err := tc.startServer(); err != nil { if err := tc.startServer(); err != nil {
tc.t.Logf("failed to start server: %s", err) return fmt.Errorf("failed to start server: %w", err)
tc.t.Fail()
} }
}
// DEPRECATED: InitServer
// Initserver is shorthand for LaunchCaddy() and LoadConfig(rawConfig, configType string)
func (tc *Tester) InitServer(rawConfig string, configType string) {
if err := tc.startServer(); err != nil {
tc.t.Logf("failed to start server: %s", err)
tc.t.Fail()
}
if err := tc.LoadConfig(rawConfig, configType); err != nil {
tc.t.Logf("failed ensuring config is running: %s", err)
tc.t.Fail()
}
}
func (tc *Tester) startServer() error {
if testing.Short() {
tc.t.SkipNow()
return nil
}
err := validateAndStartServer(tc.t)
if err != nil {
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
return nil
}
tc.t.Cleanup(func() {
if tc.t.Failed() && tc.configLoaded {
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.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())
}
// now shutdown the server, since the test is done.
_, err := http.Post(fmt.Sprintf("http://localhost:%d/stop", Default.AdminPort), "", nil)
if err != nil {
tc.t.Log("couldn't stop admin server")
}
time.Sleep(1 * time.Millisecond)
// try ensure the admin api is stopped three times.
for retries := 0; retries < 3; retries++ {
if isCaddyAdminRunning() != nil {
return
}
time.Sleep(10 * time.Millisecond)
tc.t.Log("timed out waiting for admin server to stop")
}
})
return nil return nil
} }
func (tc *Tester) MustLoadConfig(rawConfig string, configType string) { func (tc *Tester) CleanupCaddy() error {
if err := tc.LoadConfig(rawConfig, configType); err != nil { // now shutdown the server, since the test is done.
tc.t.Logf("failed ensuring config is running: %s", err) defer func() {
tc.t.Fail() // try to remove the tmp config file we created
os.Remove(tc.configFileName)
}()
_, err := http.Post(fmt.Sprintf("http://localhost:%d/stop", Default.AdminPort), "", nil)
if err != nil {
return fmt.Errorf("couldn't stop caddytest server")
} }
time.Sleep(200 * time.Millisecond)
for retries := 0; retries < 10; retries++ {
if isCaddyAdminRunning() != nil {
return nil
}
time.Sleep(100 * time.Millisecond)
}
return fmt.Errorf("timed out waiting for caddytest server to stop")
} }
// LoadConfig loads the config to the tester server and also ensures that the config was loaded // LoadConfig loads the config to the tester server and also ensures that the config was loaded
func (tc *Tester) LoadConfig(rawConfig string, configType string) error { func (tc *Tester) LoadConfig(rawConfig string, configType string) error {
originalRawConfig := rawConfig originalRawConfig := rawConfig
rawConfig = prependCaddyFilePath(rawConfig)
// normalize JSON config // normalize JSON config
if configType == "json" { if configType == "json" {
tc.t.Logf("Before: %s", rawConfig)
var conf any var conf any
if err := json.Unmarshal([]byte(rawConfig), &conf); err != nil { if err := json.Unmarshal([]byte(rawConfig), &conf); err != nil {
return err return err
@ -183,7 +124,6 @@ func (tc *Tester) LoadConfig(rawConfig string, configType string) error {
return err return err
} }
rawConfig = string(c) rawConfig = string(c)
tc.t.Logf("After: %s", rawConfig)
} }
client := &http.Client{ client := &http.Client{
Timeout: Default.LoadRequestTimeout, Timeout: Default.LoadRequestTimeout,
@ -191,8 +131,7 @@ func (tc *Tester) LoadConfig(rawConfig string, configType string) error {
start := time.Now() start := time.Now()
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig)) req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig))
if err != nil { if err != nil {
tc.t.Errorf("failed to create request. %s", err) return fmt.Errorf("failed to create request. %w", err)
return err
} }
if configType == "json" { if configType == "json" {
@ -203,16 +142,14 @@ func (tc *Tester) LoadConfig(rawConfig string, configType string) error {
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
tc.t.Errorf("unable to contact caddy server. %s", err) return fmt.Errorf("unable to contact caddy server. %w", err)
return err
} }
timeElapsed(start, "caddytest: config load time") timeElapsed(start, "caddytest: config load time")
defer res.Body.Close() defer res.Body.Close()
body, err := io.ReadAll(res.Body) body, err := io.ReadAll(res.Body)
if err != nil { if err != nil {
tc.t.Errorf("unable to read response. %s", err) return fmt.Errorf("unable to read response. %w", err)
return err
} }
if res.StatusCode != 200 { if res.StatusCode != 200 {
@ -224,7 +161,7 @@ func (tc *Tester) LoadConfig(rawConfig string, configType string) error {
} }
func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error { func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error {
expectedBytes := []byte(prependCaddyFilePath(rawConfig)) expectedBytes := []byte(rawConfig)
if configType != "json" { if configType != "json" {
adapter := caddyconfig.GetAdapter(configType) adapter := caddyconfig.GetAdapter(configType)
if adapter == nil { if adapter == nil {
@ -269,7 +206,6 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
} }
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
tc.t.Errorf("POSTed configuration isn't active")
return errors.New("EnsureConfigRunning: POSTed configuration isn't active") return errors.New("EnsureConfigRunning: POSTed configuration isn't active")
} }
@ -278,38 +214,30 @@ const initConfig = `{
} }
` `
// validateAndStartServer ensures the certificates are available in the // launches caddy, and then ensures the Caddy sub-process is running.
// designated path, launches caddy, and then ensures the Caddy sub-process is running. func (tc *Tester) startServer() error {
func validateAndStartServer(t testing.TB) error { if isCaddyAdminRunning() == nil {
// check certificates are found return fmt.Errorf("caddy test admin port still in use")
for _, certName := range Default.Certificates { }
if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) { // setup the init config file, and set the cleanup afterwards
return fmt.Errorf("caddy integration test certificates (%s) not found", certName) f, err := os.CreateTemp("", "")
} if err != nil {
return err
}
tc.configFileName = f.Name()
if _, err := f.WriteString(initConfig); err != nil {
return err
} }
if isCaddyAdminRunning() != nil { // start inprocess caddy server
// setup the init config file, and set the cleanup afterwards os.Args = []string{"caddy", "run", "--config", f.Name(), "--adapter", "caddyfile"}
f, err := os.CreateTemp("", "") go func() {
if err != nil { caddycmd.Main()
return err }()
} // wait for caddy admin api to start. it should happen quickly.
t.Cleanup(func() { for retries := 3; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
os.Remove(f.Name()) time.Sleep(1 * time.Second)
})
if _, err := f.WriteString(initConfig); err != nil {
return err
}
// start inprocess caddy server
os.Args = []string{"caddy", "run", "--config", f.Name(), "--adapter", "caddyfile"}
go func() {
caddycmd.Main()
}()
// wait for caddy admin api to start. it should happen quickly.
for retries := 3; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
time.Sleep(1 * time.Second)
}
} }
// one more time to return the error // one more time to return the error
@ -339,15 +267,6 @@ func getIntegrationDir() string {
return path.Dir(filename) return path.Dir(filename)
} }
// 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
}
// CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally // CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally
func CreateTestingTransport() *http.Transport { func CreateTestingTransport() *http.Transport {
dialer := net.Dialer{ dialer := net.Dialer{
@ -374,231 +293,3 @@ func CreateTestingTransport() *http.Transport {
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec
} }
} }
// AssertLoadError will load a config and expect an error
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
tc := NewTester(t)
tc.LaunchCaddy()
err := tc.LoadConfig(rawConfig, configType)
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
}
}
// 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
}
// 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
}
// 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()
}
}
// Generic request functions
func applyHeaders(t testing.TB, 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.URL.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 := 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 *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)
}

View file

@ -0,0 +1,109 @@
package caddytest
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"github.com/aryann/difflib"
"github.com/caddyserver/caddy/v2/caddyconfig"
)
// AssertLoadError will load a config and expect an error
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
tc := StartHarness(t)
err := tc.tester.LoadConfig(rawConfig, configType)
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
}
}
// 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
}
// 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()
}
}
// Generic request functions
func applyHeaders(t testing.TB, 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")
}
}

View file

@ -12,10 +12,10 @@ func TestReplaceCertificatePaths(t *testing.T) {
} }
redir / https://b.caddy.localhost:9443/version 301 redir / https://b.caddy.localhost:9443/version 301
respond /version 200 { respond /version 200 {
body "hello from a.caddy.localhost" body "hello from a.caddy.localhost"
} }
}` }`
r := prependCaddyFilePath(rawConfig) r := prependCaddyFilePath(rawConfig)
@ -34,8 +34,8 @@ func TestReplaceCertificatePaths(t *testing.T) {
} }
func TestLoadUnorderedJSON(t *testing.T) { func TestLoadUnorderedJSON(t *testing.T) {
tester := NewTester(t) tester := StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"logging": { "logging": {
"logs": { "logs": {

View file

@ -19,27 +19,13 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func curry2[A, B any](fn func(A, B)) func(a A) func(b B) {
return func(a A) func(B) {
return func(b B) {
fn(a, b)
}
}
}
const acmeChallengePort = 9081 const acmeChallengePort = 9081
func TestAcmeServer(t *testing.T) {
tester := caddytest.NewTester(t)
tester.LaunchCaddy()
t.Run("WithDefaults", curry2(testACMEServerWithDefaults)(tester))
t.Run("WithMismatchedChallenges", curry2(testACMEServerWithDefaults)(tester))
}
// Test the basic functionality of Caddy's ACME server // Test the basic functionality of Caddy's ACME server
func testACMEServerWithDefaults(tester *caddytest.Tester, t *testing.T) { func TestACMEServerWithDefaults(t *testing.T) {
ctx := context.Background() ctx := context.Background()
tester.MustLoadConfig(` tester := caddytest.StartHarness(t)
tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
@ -56,7 +42,7 @@ func testACMEServerWithDefaults(tester *caddytest.Tester, t *testing.T) {
client := acmez.Client{ client := acmez.Client{
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client(),
Logger: logger, Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
@ -102,10 +88,20 @@ func testACMEServerWithDefaults(tester *caddytest.Tester, t *testing.T) {
} }
} }
func testACMEServerWithMismatchedChallenges(tester *caddytest.Tester, t *testing.T) { func TestACMEServerWithMismatchedChallenges(t *testing.T) {
ctx := context.Background() ctx := context.Background()
logger := caddy.Log().Named("acmez") logger := caddy.Log().Named("acmez")
tester.MustLoadConfig(`
tester := caddytest.StartHarness(t)
tester.LoadConfig(`
{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
local_certs
}
acme.localhost {
acme_server { acme_server {
challenges tls-alpn-01 challenges tls-alpn-01
} }
@ -115,7 +111,7 @@ func testACMEServerWithMismatchedChallenges(tester *caddytest.Tester, t *testing
client := acmez.Client{ client := acmez.Client{
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client(),
Logger: logger, Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{

View file

@ -15,8 +15,8 @@ import (
) )
func TestACMEServerDirectory(t *testing.T) { func TestACMEServerDirectory(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
local_certs local_certs
@ -41,8 +41,8 @@ func TestACMEServerDirectory(t *testing.T) {
} }
func TestACMEServerAllowPolicy(t *testing.T) { func TestACMEServerAllowPolicy(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
local_certs local_certs
@ -71,7 +71,7 @@ func TestACMEServerAllowPolicy(t *testing.T) {
client := acmez.Client{ client := acmez.Client{
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client(),
Logger: logger, Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
@ -127,8 +127,8 @@ func TestACMEServerAllowPolicy(t *testing.T) {
} }
func TestACMEServerDenyPolicy(t *testing.T) { func TestACMEServerDenyPolicy(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
local_certs local_certs
@ -156,7 +156,7 @@ func TestACMEServerDenyPolicy(t *testing.T) {
client := acmez.Client{ client := acmez.Client{
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client(),
Logger: logger, Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{

View file

@ -8,8 +8,8 @@ import (
) )
func TestAutoHTTPtoHTTPSRedirectsImplicitPort(t *testing.T) { func TestAutoHTTPtoHTTPSRedirectsImplicitPort(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
skip_install_trust skip_install_trust
@ -24,8 +24,8 @@ func TestAutoHTTPtoHTTPSRedirectsImplicitPort(t *testing.T) {
} }
func TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort(t *testing.T) { func TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
@ -40,8 +40,8 @@ func TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort(t *testing.T) {
} }
func TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort(t *testing.T) { func TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
@ -56,8 +56,8 @@ func TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort(t *testing.T
} }
func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) { func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"
@ -98,8 +98,8 @@ func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) {
} }
func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll(t *testing.T) { func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
@ -123,8 +123,8 @@ func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll(t *testing.T) {
} }
func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplicitHTTPSite(t *testing.T) { func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplicitHTTPSite(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999

View file

@ -10,19 +10,19 @@ import (
func TestRespond(t *testing.T) { func TestRespond(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
https_port 9443 https_port 9443
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:9080 {
respond /version 200 { respond /version 200 {
body "hello from localhost" body "hello from localhost"
} }
} }
`, "caddyfile") `, "caddyfile")
@ -32,22 +32,22 @@ func TestRespond(t *testing.T) {
func TestRedirect(t *testing.T) { func TestRedirect(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
https_port 9443 https_port 9443
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:9080 {
redir / http://localhost:9080/hello 301 redir / http://localhost:9080/hello 301
respond /hello 200 { respond /hello 200 {
body "hello from localhost" body "hello from localhost"
} }
} }
`, "caddyfile") `, "caddyfile")
@ -64,8 +64,8 @@ func TestDuplicateHosts(t *testing.T) {
` `
localhost:9080 { localhost:9080 {
} }
localhost:9080 { localhost:9080 {
} }
`, `,
"caddyfile", "caddyfile",
@ -80,9 +80,9 @@ func TestReadCookie(t *testing.T) {
} }
// arrange // arrange
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.Client.Jar.SetCookies(localhost, []*http.Cookie{&cookie}) tester.Client().Jar.SetCookies(localhost, []*http.Cookie{&cookie})
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
@ -90,7 +90,7 @@ func TestReadCookie(t *testing.T) {
https_port 9443 https_port 9443
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:9080 {
templates { templates {
root testdata root testdata
@ -106,8 +106,8 @@ func TestReadCookie(t *testing.T) {
} }
func TestReplIndex(t *testing.T) { func TestReplIndex(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
@ -481,9 +481,9 @@ func TestValidPrefix(t *testing.T) {
} }
func TestUriReplace(t *testing.T) { func TestUriReplace(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
@ -491,16 +491,16 @@ func TestUriReplace(t *testing.T) {
:9080 :9080
uri replace "\}" %7D uri replace "\}" %7D
uri replace "\{" %7B uri replace "\{" %7B
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D") tester.AssertGetResponse("http://localhost:9080/endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D")
} }
func TestUriOps(t *testing.T) { func TestUriOps(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
@ -511,7 +511,7 @@ func TestUriOps(t *testing.T) {
uri query taz test uri query taz test
uri query key=value example uri query key=value example
uri query changethis>changed uri query changethis>changed
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest&changethis=val", 200, "changed=val&foo=bar0&foo=bar&key%3Dvalue=example&taz=test") tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest&changethis=val", 200, "changed=val&foo=bar0&foo=bar&key%3Dvalue=example&taz=test")
@ -523,9 +523,9 @@ func TestUriOps(t *testing.T) {
// refer to 127.0.0.1 or ::1. // refer to 127.0.0.1 or ::1.
// TODO: Test each http version separately (especially http/3) // TODO: Test each http version separately (especially http/3)
func TestHttpRequestLocalPortPlaceholder(t *testing.T) { func TestHttpRequestLocalPortPlaceholder(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
@ -537,9 +537,9 @@ func TestHttpRequestLocalPortPlaceholder(t *testing.T) {
} }
func TestSetThenAddQueryParams(t *testing.T) { func TestSetThenAddQueryParams(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
@ -547,16 +547,16 @@ func TestSetThenAddQueryParams(t *testing.T) {
:9080 :9080
uri query foo bar uri query foo bar
uri query +foo baz uri query +foo baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint", 200, "foo=bar&foo=baz") tester.AssertGetResponse("http://localhost:9080/endpoint", 200, "foo=bar&foo=baz")
} }
func TestSetThenDeleteParams(t *testing.T) { func TestSetThenDeleteParams(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
@ -564,16 +564,16 @@ func TestSetThenDeleteParams(t *testing.T) {
:9080 :9080
uri query bar foo{query.foo} uri query bar foo{query.foo}
uri query -foo uri query -foo
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=foobar") tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=foobar")
} }
func TestRenameAndOtherOps(t *testing.T) { func TestRenameAndOtherOps(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
@ -582,36 +582,36 @@ func TestRenameAndOtherOps(t *testing.T) {
uri query foo>bar uri query foo>bar
uri query bar taz uri query bar taz
uri query +bar baz uri query +bar baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=taz&bar=baz") tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=taz&bar=baz")
} }
func TestReplaceOps(t *testing.T) { func TestReplaceOps(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
} }
:9080 :9080
uri query foo bar baz uri query foo bar baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz") tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz")
} }
func TestReplaceWithReplacementPlaceholder(t *testing.T) { func TestReplaceWithReplacementPlaceholder(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
} }
:9080 :9080
uri query foo bar {query.placeholder} uri query foo bar {query.placeholder}
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz") tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz")
@ -619,66 +619,66 @@ func TestReplaceWithReplacementPlaceholder(t *testing.T) {
} }
func TestReplaceWithKeyPlaceholder(t *testing.T) { func TestReplaceWithKeyPlaceholder(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
} }
:9080 :9080
uri query {query.placeholder} bar baz uri query {query.placeholder} bar baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=foo&foo=bar", 200, "foo=baz&placeholder=foo") tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=foo&foo=bar", 200, "foo=baz&placeholder=foo")
} }
func TestPartialReplacement(t *testing.T) { func TestPartialReplacement(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
} }
:9080 :9080
uri query foo ar az uri query foo ar az
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz") tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz")
} }
func TestNonExistingSearch(t *testing.T) { func TestNonExistingSearch(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
} }
:9080 :9080
uri query foo var baz uri query foo var baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=bar") tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=bar")
} }
func TestReplaceAllOps(t *testing.T) { func TestReplaceAllOps(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
} }
:9080 :9080
uri query * bar baz uri query * bar baz
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar&baz=bar", 200, "baz=baz&foo=baz") tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar&baz=bar", 200, "baz=baz&foo=baz")
} }
func TestUriOpsBlock(t *testing.T) { func TestUriOpsBlock(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
@ -688,15 +688,15 @@ func TestUriOpsBlock(t *testing.T) {
+foo bar +foo bar
-baz -baz
taz test taz test
} }
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest", 200, "foo=bar0&foo=bar&taz=test") tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest", 200, "foo=bar0&foo=bar&taz=test")
} }
func TestHandleErrorSimpleCodes(t *testing.T) { func TestHandleErrorSimpleCodes(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(`{ tester.LoadConfig(`{
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
} }
@ -704,7 +704,7 @@ func TestHandleErrorSimpleCodes(t *testing.T) {
root * /srv root * /srv
error /private* "Unauthorized" 410 error /private* "Unauthorized" 410
error /hidden* "Not found" 404 error /hidden* "Not found" 404
handle_errors 404 410 { handle_errors 404 410 {
respond "404 or 410 error" respond "404 or 410 error"
} }
@ -715,8 +715,8 @@ func TestHandleErrorSimpleCodes(t *testing.T) {
} }
func TestHandleErrorRange(t *testing.T) { func TestHandleErrorRange(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(`{ tester.LoadConfig(`{
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
} }
@ -735,8 +735,8 @@ func TestHandleErrorRange(t *testing.T) {
} }
func TestHandleErrorSort(t *testing.T) { func TestHandleErrorSort(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(`{ tester.LoadConfig(`{
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
} }
@ -759,8 +759,8 @@ func TestHandleErrorSort(t *testing.T) {
} }
func TestHandleErrorRangeAndCodes(t *testing.T) { func TestHandleErrorRangeAndCodes(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(`{ tester.LoadConfig(`{
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
} }

View file

@ -9,8 +9,8 @@ import (
) )
func TestBrowse(t *testing.T) { func TestBrowse(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
@ -32,8 +32,8 @@ func TestBrowse(t *testing.T) {
} }
func TestRespondWithJSON(t *testing.T) { func TestRespondWithJSON(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999

View file

@ -7,8 +7,8 @@ import (
) )
func TestIntercept(t *testing.T) { func TestIntercept(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(`{ tester.LoadConfig(`{
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080

View file

@ -7,8 +7,8 @@ import (
) )
func TestLeafCertLoaders(t *testing.T) { func TestLeafCertLoaders(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"

View file

@ -12,7 +12,7 @@ import (
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
) )
func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc) *caddytest.Tester { func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc) *caddytest.TestHarness {
l, err := net.Listen("tcp", "127.0.0.1:0") l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatalf("failed to listen: %s", err) t.Fatalf("failed to listen: %s", err)
@ -28,8 +28,8 @@ func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc) *caddy
_ = srv.Close() _ = srv.Close()
_ = l.Close() _ = l.Close()
}) })
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(fmt.Sprintf(` tester.LoadConfig(fmt.Sprintf(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
@ -69,7 +69,7 @@ func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) {
writer.WriteHeader(http.StatusNoContent) writer.WriteHeader(http.StatusNoContent)
}) })
resp, err := tester.Client.Post("https://localhost:9443", "application/octet-stream", bytes.NewReader(body)) resp, err := tester.Client().Post("https://localhost:9443", "application/octet-stream", bytes.NewReader(body))
if err != nil { if err != nil {
t.Fatalf("failed to post: %s", err) t.Fatalf("failed to post: %s", err)
} }
@ -87,7 +87,7 @@ func TestLargeHttpRequest(t *testing.T) {
// We never read the body in any way, set an extra long header instead. // We never read the body in any way, set an extra long header instead.
req, _ := http.NewRequest("POST", "http://localhost:9443", nil) req, _ := http.NewRequest("POST", "http://localhost:9443", nil)
req.Header.Set("Long-Header", strings.Repeat("X", 1024*1024)) req.Header.Set("Long-Header", strings.Repeat("X", 1024*1024))
_, err := tester.Client.Do(req) _, err := tester.Client().Do(req)
if err == nil { if err == nil {
t.Fatal("not supposed to succeed") t.Fatal("not supposed to succeed")
} }

View file

@ -9,8 +9,8 @@ import (
func TestMap(t *testing.T) { func TestMap(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(`{ tester.LoadConfig(`{
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
@ -39,8 +39,8 @@ func TestMap(t *testing.T) {
func TestMapRespondWithDefault(t *testing.T) { func TestMapRespondWithDefault(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(`{ tester.LoadConfig(`{
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
@ -67,8 +67,8 @@ func TestMapRespondWithDefault(t *testing.T) {
func TestMapAsJSON(t *testing.T) { func TestMapAsJSON(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"

View file

@ -14,8 +14,8 @@ import (
) )
func TestSRVReverseProxy(t *testing.T) { func TestSRVReverseProxy(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"
@ -87,8 +87,8 @@ func TestDialWithPlaceholderUnix(t *testing.T) {
}) })
runtime.Gosched() // Allow other goroutines to run runtime.Gosched() // Allow other goroutines to run
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"
@ -139,8 +139,8 @@ func TestDialWithPlaceholderUnix(t *testing.T) {
} }
func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) { func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"
@ -233,8 +233,8 @@ func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
} }
func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) { func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"
@ -327,8 +327,8 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
} }
func TestReverseProxyHealthCheck(t *testing.T) { func TestReverseProxyHealthCheck(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
@ -364,7 +364,7 @@ func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.SkipNow() t.SkipNow()
} }
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
f, err := os.CreateTemp("", "*.sock") f, err := os.CreateTemp("", "*.sock")
if err != nil { if err != nil {
t.Errorf("failed to create TempFile: %s", err) t.Errorf("failed to create TempFile: %s", err)
@ -395,7 +395,7 @@ func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
}) })
runtime.Gosched() // Allow other goroutines to run runtime.Gosched() // Allow other goroutines to run
tester.InitServer(fmt.Sprintf(` tester.LoadConfig(fmt.Sprintf(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999
@ -422,7 +422,7 @@ func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.SkipNow() t.SkipNow()
} }
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
f, err := os.CreateTemp("", "*.sock") f, err := os.CreateTemp("", "*.sock")
if err != nil { if err != nil {
t.Errorf("failed to create TempFile: %s", err) t.Errorf("failed to create TempFile: %s", err)
@ -453,7 +453,7 @@ func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
}) })
runtime.Gosched() // Allow other goroutines to run runtime.Gosched() // Allow other goroutines to run
tester.InitServer(fmt.Sprintf(` tester.LoadConfig(fmt.Sprintf(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin localhost:2999

View file

@ -8,8 +8,8 @@ import (
func TestDefaultSNI(t *testing.T) { func TestDefaultSNI(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(`{ tester.LoadConfig(`{
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"
}, },
@ -107,8 +107,8 @@ func TestDefaultSNI(t *testing.T) {
func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) { func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"
@ -211,8 +211,8 @@ func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
func TestDefaultSNIWithPortMappingOnly(t *testing.T) { func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"

View file

@ -20,8 +20,8 @@ import (
// (see https://github.com/caddyserver/caddy/issues/3556 for use case) // (see https://github.com/caddyserver/caddy/issues/3556 for use case)
func TestH2ToH2CStream(t *testing.T) { func TestH2ToH2CStream(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"
@ -204,8 +204,8 @@ func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
// (see https://github.com/caddyserver/caddy/issues/3606 for use case) // (see https://github.com/caddyserver/caddy/issues/3606 for use case)
func TestH2ToH1ChunkedResponse(t *testing.T) { func TestH2ToH1ChunkedResponse(t *testing.T) {
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(` tester.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "localhost:2999"

View file

@ -0,0 +1,223 @@
package caddytest
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"
"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
}
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) 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 tester: %s", err)
}
// cleanup
tc.t.Cleanup(func() {
func() {
if tc.t.Failed() {
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.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)
}
})
}
// 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)
}