This commit is contained in:
a 2025-12-06 22:47:24 -07:00 committed by GitHub
commit 14d7b13e89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 1049 additions and 858 deletions

View file

@ -20,6 +20,7 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"flag"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@ -749,9 +750,12 @@ func Validate(cfg *Config) error {
// Errors are logged along the way, and an appropriate exit // Errors are logged along the way, and an appropriate exit
// code is emitted. // code is emitted.
func exitProcess(ctx context.Context, logger *zap.Logger) { func exitProcess(ctx context.Context, logger *zap.Logger) {
notRunningInTest := flag.Lookup("test.v") == nil && !strings.Contains(os.Args[0], ".test")
// let the rest of the program know we're quitting; only do it once // let the rest of the program know we're quitting; only do it once
if !atomic.CompareAndSwapInt32(exiting, 0, 1) { if notRunningInTest {
return if !atomic.CompareAndSwapInt32(exiting, 0, 1) {
return
}
} }
// give the OS or service/process manager our 2 weeks' notice: we quit // give the OS or service/process manager our 2 weeks' notice: we quit
@ -809,7 +813,10 @@ func exitProcess(ctx context.Context, logger *zap.Logger) {
} else { } else {
logger.Error("unclean shutdown") logger.Error("unclean shutdown")
} }
os.Exit(exitCode) // check if we are in test environment, and dont call exit if we are
if notRunningInTest {
os.Exit(exitCode)
}
}() }()
if remoteAdminServer != nil { if remoteAdminServer != nil {

View file

@ -5,28 +5,21 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"log" "log"
"net" "net"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"os" "os"
"path" "strconv"
"reflect"
"regexp"
"runtime"
"strings" "strings"
"sync/atomic"
"testing" "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"
// plug in Caddy modules here // plug in Caddy modules here
_ "github.com/caddyserver/caddy/v2/modules/standard" _ "github.com/caddyserver/caddy/v2/modules/standard"
) )
@ -45,30 +38,32 @@ type Config struct {
// Default testing values // Default testing values
var Default = Config{ var Default = Config{
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"}, Certificates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
TestRequestTimeout: 5 * time.Second, TestRequestTimeout: 5 * time.Second,
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
t testing.TB portOne int
config Config portTwo int
started atomic.Bool
configLoaded bool
configFileName string
envFileName string
t testing.TB
config Config
} }
// 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(t testing.TB) (*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{
@ -80,7 +75,7 @@ func NewTester(t testing.TB) *Tester {
configLoaded: false, configLoaded: false,
t: t, t: t,
config: Default, config: Default,
} }, nil
} }
// WithDefaultOverrides this will override the default test configuration with the provided values. // WithDefaultOverrides this will override the default test configuration with the provided values.
@ -113,34 +108,28 @@ func timeElapsed(start time.Time, name string) {
log.Printf("%s took %s", name, elapsed) log.Printf("%s took %s", name, elapsed)
} }
// InitServer this will configure the server with a configurion of a specific // launch caddy will start the server
// type. The configType must be either "json" or the adapter type. func (tc *Tester) LaunchCaddy() error {
func (tc *Tester) InitServer(rawConfig string, configType string) { if !tc.started.CompareAndSwap(false, true) {
if err := tc.initServer(rawConfig, configType); err != nil { return fmt.Errorf("already launched caddy with this tester")
tc.t.Logf("failed to load config: %s", err)
tc.t.Fail()
} }
if err := tc.ensureConfigRunning(rawConfig, configType); err != nil { if err := tc.startServer(); err != nil {
tc.t.Logf("failed ensuring config is running: %s", err) return fmt.Errorf("failed to start server: %w", err)
tc.t.Fail()
} }
return nil
} }
// InitServer this will configure the server with a configurion of a specific func (tc *Tester) CleanupCaddy() error {
// type. The configType must be either "json" or the adapter type. // now shutdown the server, since the test is done.
func (tc *Tester) initServer(rawConfig string, configType string) error { defer func() {
if testing.Short() { // try to remove pthe tmp config file we created
tc.t.SkipNow() if tc.configFileName != "" {
return nil os.Remove(tc.configFileName)
} }
if tc.envFileName != "" {
os.Remove(tc.envFileName)
}
err := validateTestPrerequisites(tc)
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 { if tc.t.Failed() && tc.configLoaded {
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort)) res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
if err != nil { if err != nil {
@ -154,9 +143,52 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
_ = json.Indent(&out, body, "", " ") _ = json.Indent(&out, body, "", " ")
tc.t.Logf("----------- failed with config -----------\n%s", out.String()) tc.t.Logf("----------- failed with config -----------\n%s", out.String())
} }
}) }()
rawConfig = prependCaddyFilePath(rawConfig) resp, err := http.Post(fmt.Sprintf("http://localhost:%d/stop", tc.config.AdminPort), "", nil)
if err != nil {
return fmt.Errorf("couldn't stop caddytest server: %w", err)
}
resp.Body.Close()
for range 10 {
if tc.isCaddyAdminRunning() != nil {
return nil
}
time.Sleep(100 * time.Millisecond)
}
return fmt.Errorf("timed out waiting for caddytest server to stop")
}
func (tc *Tester) AdminPort() int {
return tc.config.AdminPort
}
func (tc *Tester) PortOne() int {
return tc.portOne
}
func (tc *Tester) PortTwo() int {
return tc.portTwo
}
func (tc *Tester) ReplaceTestingPlaceholders(x string) string {
x = strings.ReplaceAll(x, "{$TESTING_CADDY_ADMIN_BIND}", fmt.Sprintf("localhost:%d", tc.config.AdminPort))
x = strings.ReplaceAll(x, "{$TESTING_CADDY_ADMIN_PORT}", fmt.Sprintf("%d", tc.config.AdminPort))
x = strings.ReplaceAll(x, "{$TESTING_CADDY_PORT_ONE}", fmt.Sprintf("%d", tc.portOne))
x = strings.ReplaceAll(x, "{$TESTING_CADDY_PORT_TWO}", fmt.Sprintf("%d", tc.portTwo))
return x
}
// LoadConfig loads the config to the tester server and also ensures that the config was loaded
// it should not be run
func (tc *Tester) LoadConfig(rawConfig string, configType string) error {
if tc.config.AdminPort == 0 {
return fmt.Errorf("load config called where startServer didnt succeed")
}
rawConfig = tc.ReplaceTestingPlaceholders(rawConfig)
// replace special testing placeholders so we can have our admin api be on a random port
// normalize JSON config // normalize JSON config
if configType == "json" { if configType == "json" {
tc.t.Logf("Before: %s", rawConfig) tc.t.Logf("Before: %s", rawConfig)
@ -177,8 +209,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
start := time.Now() start := time.Now()
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", tc.config.AdminPort), strings.NewReader(rawConfig)) req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", tc.config.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" {
@ -189,16 +220,14 @@ func (tc *Tester) initServer(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 {
@ -206,105 +235,107 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
} }
tc.configLoaded = true tc.configLoaded = true
// if the config is not loaded at this point, it is a bug in caddy's config.Load
// the contract for config.Load states that the config must be loaded before it returns, and that it will
// error if the config fails to apply
return nil return nil
} }
func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error { func (tc *Tester) GetCurrentConfig(receiver any) error {
expectedBytes := []byte(prependCaddyFilePath(rawConfig))
if configType != "json" {
adapter := caddyconfig.GetAdapter(configType)
if adapter == nil {
return fmt.Errorf("adapter of config type is missing: %s", configType)
}
expectedBytes, _, _ = adapter.Adapt([]byte(rawConfig), nil)
}
var expected any
err := json.Unmarshal(expectedBytes, &expected)
if err != nil {
return err
}
client := &http.Client{ client := &http.Client{
Timeout: tc.config.LoadRequestTimeout, Timeout: tc.config.LoadRequestTimeout,
} }
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
fetchConfig := func(client *http.Client) any { if err != nil {
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort)) return nil
if err != nil {
return nil
}
defer resp.Body.Close()
actualBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil
}
var actual any
err = json.Unmarshal(actualBytes, &actual)
if err != nil {
return nil
}
return actual
} }
defer resp.Body.Close()
for retries := 10; retries > 0; retries-- { actualBytes, err := io.ReadAll(resp.Body)
if reflect.DeepEqual(expected, fetchConfig(client)) { if err != nil {
return nil return nil
}
time.Sleep(1 * time.Second)
} }
tc.t.Errorf("POSTed configuration isn't active") err = json.Unmarshal(actualBytes, &receiver)
return errors.New("EnsureConfigRunning: POSTed configuration isn't active") if err != nil {
return nil
}
return nil
} }
const initConfig = `{ func getFreePort() (int, error) {
admin localhost:%d lr, err := net.Listen("tcp", "localhost:0")
} if err != nil {
` return 0, err
// validateTestPrerequisites ensures the certificates are available in the
// designated path and Caddy sub-process is running.
func validateTestPrerequisites(tc *Tester) error {
// check certificates are found
for _, certName := range tc.config.Certificates {
if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("caddy integration test certificates (%s) not found", certName)
}
} }
if isCaddyAdminRunning(tc) != nil { port := strings.Split(lr.Addr().String(), ":")
// setup the init config file, and set the cleanup afterwards if len(port) < 2 {
return 0, fmt.Errorf("no port available")
}
i, err := strconv.Atoi(port[1])
if err != nil {
return 0, err
}
err = lr.Close()
if err != nil {
return 0, fmt.Errorf("failed to close listener: %w", err)
}
return i, nil
}
// launches caddy, and then ensures the Caddy sub-process is running.
func (tc *Tester) startServer() error {
if tc.isCaddyAdminRunning() == nil {
return fmt.Errorf("caddy test admin port still in use")
}
a, err := getFreePort()
if err != nil {
return fmt.Errorf("could not find a open port to listen on: %w", err)
}
tc.config.AdminPort = a
tc.portOne, err = getFreePort()
if err != nil {
return fmt.Errorf("could not find a open portOne: %w", err)
}
tc.portTwo, err = getFreePort()
if err != nil {
return fmt.Errorf("could not find a open portOne: %w", err)
}
// setup the init config file, and set the cleanup afterwards
{
f, err := os.CreateTemp("", "") f, err := os.CreateTemp("", "")
if err != nil { if err != nil {
return err return err
} }
tc.t.Cleanup(func() { tc.configFileName = f.Name()
os.Remove(f.Name())
}) initConfig := `{
admin localhost:%d
}`
if _, err := fmt.Fprintf(f, initConfig, tc.config.AdminPort); err != nil { if _, err := fmt.Fprintf(f, initConfig, tc.config.AdminPort); err != nil {
return err return err
} }
if err := f.Close(); err != nil {
// start inprocess caddy server return err
os.Args = []string{"caddy", "run", "--config", f.Name(), "--adapter", "caddyfile"}
go func() {
caddycmd.Main()
}()
// wait for caddy to start serving the initial config
for retries := 10; retries > 0 && isCaddyAdminRunning(tc) != nil; retries-- {
time.Sleep(1 * time.Second)
} }
} }
// one more time to return the error // start inprocess caddy server
return isCaddyAdminRunning(tc) go func() {
} _ = caddycmd.MainForTesting("run", "--config", tc.configFileName, "--adapter", "caddyfile")
}()
// wait for caddy admin api to start. it should happen quickly.
for retries := 10; retries > 0 && tc.isCaddyAdminRunning() != nil; retries-- {
time.Sleep(100 * time.Millisecond)
}
func isCaddyAdminRunning(tc *Tester) error { // one more time to return the error
return tc.isCaddyAdminRunning()
}
func (tc *Tester) isCaddyAdminRunning() error {
// assert that caddy is running // assert that caddy is running
client := &http.Client{ client := &http.Client{
Timeout: tc.config.LoadRequestTimeout, Timeout: Default.LoadRequestTimeout,
} }
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort)) resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
if err != nil { if err != nil {
@ -315,24 +346,6 @@ func isCaddyAdminRunning(tc *Tester) error {
return nil return nil
} }
func getIntegrationDir() string {
_, filename, _, ok := runtime.Caller(1)
if !ok {
panic("unable to determine the current file path")
}
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{
@ -359,231 +372,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)
err := tc.initServer(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,116 @@
package caddytest
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"github.com/aryann/difflib"
"github.com/stretchr/testify/require"
"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, err := NewTester(t)
require.NoError(t, err)
err = tc.LaunchCaddy()
require.NoError(t, err)
err = tc.LoadConfig(rawConfig, configType)
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
}
_ = tc.CleanupCaddy()
}
// 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

@ -1,13 +1,14 @@
package caddytest package caddytest
import ( import (
"fmt"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
) )
func TestReplaceCertificatePaths(t *testing.T) { func TestReplaceCertificatePaths(t *testing.T) {
rawConfig := `a.caddy.localhost:9443 { rawConfig := `a.caddy.localhost:9443{
tls /caddy.localhost.crt /caddy.localhost.key { tls /caddy.localhost.crt /caddy.localhost.key {
} }
@ -34,8 +35,8 @@ func TestReplaceCertificatePaths(t *testing.T) {
} }
func TestLoadUnorderedJSON(t *testing.T) { func TestLoadUnorderedJSON(t *testing.T) {
tester := NewTester(t) harness := StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"logging": { "logging": {
"logs": { "logs": {
@ -68,7 +69,7 @@ func TestLoadUnorderedJSON(t *testing.T) {
} }
}, },
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@ -79,12 +80,13 @@ func TestLoadUnorderedJSON(t *testing.T) {
} }
}, },
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"servers": { "servers": {
"s_server": { "s_server": {
"listen": [ "listen": [
":9080" ":{$TESTING_CADDY_PORT_ONE}",
":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@ -119,10 +121,10 @@ func TestLoadUnorderedJSON(t *testing.T) {
} }
} }
`, "json") `, "json")
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), nil)
if err != nil { if err != nil {
t.Fail() t.Fail()
return return
} }
tester.AssertResponseCode(req, 200) harness.AssertResponseCode(req, 200)
} }

View file

@ -27,19 +27,13 @@ const acmeChallengePort = 9081
// Test the basic functionality of Caddy's ACME server // Test the basic functionality of Caddy's ACME server
func TestACMEServerWithDefaults(t *testing.T) { func TestACMEServerWithDefaults(t *testing.T) {
ctx := context.Background() ctx := context.Background()
logger, err := zap.NewDevelopment() harness := caddytest.StartHarness(t)
if err != nil { harness.LoadConfig(`
t.Error(err)
return
}
tester := caddytest.NewTester(t)
tester.InitServer(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
local_certs local_certs
} }
acme.localhost { acme.localhost {
@ -47,10 +41,11 @@ func TestACMEServerWithDefaults(t *testing.T) {
} }
`, "caddyfile") `, "caddyfile")
logger := caddy.Log().Named("acmeserver")
client := acmez.Client{ client := acmez.Client{
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: fmt.Sprintf("https://acme.localhost:%d/acme/local/directory", harness.Tester().PortTwo()),
HTTPClient: tester.Client, HTTPClient: harness.Client(),
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: slog.New(zapslog.NewHandler(logger.Core())),
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
@ -100,13 +95,13 @@ func TestACMEServerWithMismatchedChallenges(t *testing.T) {
ctx := context.Background() ctx := context.Background()
logger := caddy.Log().Named("acmez") logger := caddy.Log().Named("acmez")
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
local_certs local_certs
} }
acme.localhost { acme.localhost {
@ -118,8 +113,8 @@ func TestACMEServerWithMismatchedChallenges(t *testing.T) {
client := acmez.Client{ client := acmez.Client{
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: fmt.Sprintf("https://acme.localhost:%d/acme/local/directory", harness.Tester().PortTwo()),
HTTPClient: tester.Client, HTTPClient: harness.Client(),
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: slog.New(zapslog.NewHandler(logger.Core())),
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{

View file

@ -5,6 +5,7 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"fmt"
"log/slog" "log/slog"
"strings" "strings"
"testing" "testing"
@ -18,40 +19,40 @@ import (
) )
func TestACMEServerDirectory(t *testing.T) { func TestACMEServerDirectory(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
local_certs local_certs
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
pki { pki {
ca local { ca local {
name "Caddy Local Authority" name "Caddy Local Authority"
} }
} }
} }
acme.localhost:9443 { https://acme.localhost:{$TESTING_CADDY_PORT_TWO} {
acme_server acme_server
} }
`, "caddyfile") `, "caddyfile")
tester.AssertGetResponse( harness.AssertGetResponse(
"https://acme.localhost:9443/acme/local/directory", fmt.Sprintf("https://acme.localhost:%d/acme/local/directory", harness.Tester().PortTwo()),
200, 200,
`{"newNonce":"https://acme.localhost:9443/acme/local/new-nonce","newAccount":"https://acme.localhost:9443/acme/local/new-account","newOrder":"https://acme.localhost:9443/acme/local/new-order","revokeCert":"https://acme.localhost:9443/acme/local/revoke-cert","keyChange":"https://acme.localhost:9443/acme/local/key-change"} fmt.Sprintf(`{"newNonce":"https://acme.localhost:%[1]d/acme/local/new-nonce","newAccount":"https://acme.localhost:%[1]d/acme/local/new-account","newOrder":"https://acme.localhost:%[1]d/acme/local/new-order","revokeCert":"https://acme.localhost:%[1]d/acme/local/revoke-cert","keyChange":"https://acme.localhost:%[1]d/acme/local/key-change"}
`) `, harness.Tester().PortTwo()))
} }
func TestACMEServerAllowPolicy(t *testing.T) { func TestACMEServerAllowPolicy(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
local_certs local_certs
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
pki { pki {
ca local { ca local {
name "Caddy Local Authority" name "Caddy Local Authority"
@ -77,8 +78,8 @@ 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: fmt.Sprintf("https://acme.localhost:%d/acme/local/directory", harness.Tester().PortTwo()),
HTTPClient: tester.Client, HTTPClient: harness.Client(),
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: slog.New(zapslog.NewHandler(logger.Core())),
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
@ -134,14 +135,14 @@ func TestACMEServerAllowPolicy(t *testing.T) {
} }
func TestACMEServerDenyPolicy(t *testing.T) { func TestACMEServerDenyPolicy(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
local_certs local_certs
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
pki { pki {
ca local { ca local {
name "Caddy Local Authority" name "Caddy Local Authority"
@ -166,8 +167,8 @@ 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: fmt.Sprintf("https://acme.localhost:%d/acme/local/directory", harness.Tester().PortTwo()),
HTTPClient: tester.Client, HTTPClient: harness.Client(),
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: slog.New(zapslog.NewHandler(logger.Core())),
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
@ -200,7 +201,7 @@ func TestACMEServerDenyPolicy(t *testing.T) {
_, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"deny.localhost"}) _, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"deny.localhost"})
if err == nil { if err == nil {
t.Errorf("obtaining certificate for 'deny.localhost' domain") t.Errorf("obtaining certificate for 'deny.localhost' domain")
} else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") { } else if !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
t.Logf("unexpected error: %v", err) t.Logf("unexpected error: %v", err)
} }
} }

View file

@ -1,6 +1,7 @@
package integration package integration
import ( import (
"fmt"
"net/http" "net/http"
"testing" "testing"
@ -8,69 +9,69 @@ import (
) )
func TestAutoHTTPtoHTTPSRedirectsImplicitPort(t *testing.T) { func TestAutoHTTPtoHTTPSRedirectsImplicitPort(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
skip_install_trust skip_install_trust
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
} }
localhost localhost
respond "Yahaha! You found me!" respond "Yahaha! You found me!"
`, "caddyfile") `, "caddyfile")
tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), "https://localhost/", http.StatusPermanentRedirect)
} }
func TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort(t *testing.T) { func TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
} }
localhost:9443 localhost:{$TESTING_CADDY_PORT_TWO}
respond "Yahaha! You found me!" respond "Yahaha! You found me!"
`, "caddyfile") `, "caddyfile")
tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), "https://localhost/", http.StatusPermanentRedirect)
} }
func TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort(t *testing.T) { func TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
} }
localhost:1234 localhost:1234
respond "Yahaha! You found me!" respond "Yahaha! You found me!"
`, "caddyfile") `, "caddyfile")
tester.AssertRedirect("http://localhost:9080/", "https://localhost:1234/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), "https://localhost:1234/", http.StatusPermanentRedirect)
} }
func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) { func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"servers": { "servers": {
"ingress_server": { "ingress_server": {
"listen": [ "listen": [
":9080", ":{$TESTING_CADDY_PORT_ONE}",
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@ -94,52 +95,52 @@ func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) {
} }
} }
`, "json") `, "json")
tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), "https://localhost/", http.StatusPermanentRedirect)
} }
func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll(t *testing.T) { func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
local_certs local_certs
} }
http://:9080 { http://:{$TESTING_CADDY_PORT_ONE} {
respond "Foo" respond "Foo"
} }
http://baz.localhost:9080 { http://baz.localhost:{$TESTING_CADDY_PORT_ONE} {
respond "Baz" respond "Baz"
} }
bar.localhost { bar.localhost {
respond "Bar" respond "Bar"
} }
`, "caddyfile") `, "caddyfile")
tester.AssertRedirect("http://bar.localhost:9080/", "https://bar.localhost/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://bar.localhost:%d/", harness.Tester().PortOne()), "https://bar.localhost/", http.StatusPermanentRedirect)
tester.AssertGetResponse("http://foo.localhost:9080/", 200, "Foo") harness.AssertGetResponse(fmt.Sprintf("http://foo.localhost:%d/", harness.Tester().PortOne()), 200, "Foo")
tester.AssertGetResponse("http://baz.localhost:9080/", 200, "Baz") harness.AssertGetResponse(fmt.Sprintf("http://baz.localhost:%d/", harness.Tester().PortOne()), 200, "Baz")
} }
func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplicitHTTPSite(t *testing.T) { func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplicitHTTPSite(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
local_certs local_certs
} }
http://:9080 { http://:{$TESTING_CADDY_PORT_ONE} {
respond "Foo" respond "Foo"
} }
bar.localhost { bar.localhost {
respond "Bar" respond "Bar"
} }
`, "caddyfile") `, "caddyfile")
tester.AssertRedirect("http://bar.localhost:9080/", "https://bar.localhost/", http.StatusPermanentRedirect) harness.AssertRedirect(fmt.Sprintf("http://bar.localhost:%d/", harness.Tester().PortOne()), "https://bar.localhost/", http.StatusPermanentRedirect)
tester.AssertGetResponse("http://foo.localhost:9080/", 200, "Foo") harness.AssertGetResponse(fmt.Sprintf("http://foo.localhost:%d/", harness.Tester().PortOne()), 200, "Foo")
tester.AssertGetResponse("http://baz.localhost:9080/", 200, "Foo") harness.AssertGetResponse(fmt.Sprintf("http://baz.localhost:%d/", harness.Tester().PortOne()), 200, "Foo")
} }

View file

@ -1,6 +1,7 @@
package integration package integration
import ( import (
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"testing" "testing"
@ -10,16 +11,16 @@ import (
func TestRespond(t *testing.T) { func TestRespond(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
respond /version 200 { respond /version 200 {
body "hello from localhost" body "hello from localhost"
} }
@ -27,23 +28,23 @@ func TestRespond(t *testing.T) {
`, "caddyfile") `, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost") harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne()), 200, "hello from localhost")
} }
func TestRedirect(t *testing.T) { func TestRedirect(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
redir / http://localhost:9080/hello 301 redir / http://localhost:{$TESTING_CADDY_PORT_ONE}/hello 301
respond /hello 200 { respond /hello 200 {
body "hello from localhost" body "hello from localhost"
@ -51,21 +52,22 @@ func TestRedirect(t *testing.T) {
} }
`, "caddyfile") `, "caddyfile")
target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
// act and assert // act and assert
tester.AssertRedirect("http://localhost:9080/", "http://localhost:9080/hello", 301) harness.AssertRedirect(target, target+"hello", 301)
// follow redirect // follow redirect
tester.AssertGetResponse("http://localhost:9080/", 200, "hello from localhost") harness.AssertGetResponse(target, 200, "hello from localhost")
} }
func TestDuplicateHosts(t *testing.T) { func TestDuplicateHosts(t *testing.T) {
// act and assert // act and assert
caddytest.AssertLoadError(t, caddytest.AssertLoadError(t,
` `
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
} }
`, `,
"caddyfile", "caddyfile",
@ -80,18 +82,18 @@ func TestReadCookie(t *testing.T) {
} }
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.Client.Jar.SetCookies(localhost, []*http.Cookie{&cookie}) harness.Client().Jar.SetCookies(localhost, []*http.Cookie{&cookie})
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
templates { templates {
root testdata root testdata
} }
@ -102,21 +104,22 @@ func TestReadCookie(t *testing.T) {
`, "caddyfile") `, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/cookie.html", 200, "<h2>Cookie.ClientName caddytest</h2>") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"cookie.html", 200, "<h2>Cookie.ClientName caddytest</h2>")
} }
func TestReplIndex(t *testing.T) { func TestReplIndex(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
templates { templates {
root testdata root testdata
} }
@ -128,7 +131,8 @@ func TestReplIndex(t *testing.T) {
`, "caddyfile") `, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/", 200, "") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target, 200, "")
} }
func TestInvalidPrefix(t *testing.T) { func TestInvalidPrefix(t *testing.T) {
@ -481,31 +485,32 @@ func TestValidPrefix(t *testing.T) {
} }
func TestUriReplace(t *testing.T) { func TestUriReplace(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D")
} }
func TestUriOps(t *testing.T) { func TestUriOps(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query +foo bar uri query +foo bar
uri query -baz uri query -baz
uri query taz test uri query taz test
@ -514,7 +519,8 @@ func TestUriOps(t *testing.T) {
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar0&baz=buz&taz=nottest&changethis=val", 200, "changed=val&foo=bar0&foo=bar&key%3Dvalue=example&taz=test")
} }
// Tests the `http.request.local.port` placeholder. // Tests the `http.request.local.port` placeholder.
@ -523,166 +529,176 @@ 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) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
respond "{http.request.local.port}"`, "caddyfile") respond "{http.request.local.port}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/", 200, "9080") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target, 200, fmt.Sprintf("%d", harness.Tester().PortOne()))
} }
func TestSetThenAddQueryParams(t *testing.T) { func TestSetThenAddQueryParams(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint", 200, "foo=bar&foo=baz")
} }
func TestSetThenDeleteParams(t *testing.T) { func TestSetThenDeleteParams(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar", 200, "bar=foobar")
} }
func TestRenameAndOtherOps(t *testing.T) { func TestRenameAndOtherOps(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar", 200, "bar=taz&bar=baz")
} }
func TestReplaceOps(t *testing.T) { func TestReplaceOps(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar", 200, "foo=baz")
} }
func TestReplaceWithReplacementPlaceholder(t *testing.T) { func TestReplaceWithReplacementPlaceholder(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz")
} }
func TestReplaceWithKeyPlaceholder(t *testing.T) { func TestReplaceWithKeyPlaceholder(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?placeholder=foo&foo=bar", 200, "foo=baz&placeholder=foo")
} }
func TestPartialReplacement(t *testing.T) { func TestPartialReplacement(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar", 200, "foo=baz")
} }
func TestNonExistingSearch(t *testing.T) { func TestNonExistingSearch(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar", 200, "foo=bar")
} }
func TestReplaceAllOps(t *testing.T) { func TestReplaceAllOps(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"endpoint?foo=bar&baz=bar", 200, "baz=baz&foo=baz")
} }
func TestUriOpsBlock(t *testing.T) { func TestUriOpsBlock(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
:9080 :{$TESTING_CADDY_PORT_ONE}
uri query { uri query {
+foo bar +foo bar
-baz -baz
@ -690,16 +706,17 @@ func TestUriOpsBlock(t *testing.T) {
} }
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") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target+"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) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
root * /srv root * /srv
error /private* "Unauthorized" 410 error /private* "Unauthorized" 410
error /hidden* "Not found" 404 error /hidden* "Not found" 404
@ -709,17 +726,18 @@ func TestHandleErrorSimpleCodes(t *testing.T) {
} }
}`, "caddyfile") }`, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/private", 410, "404 or 410 error") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
tester.AssertGetResponse("http://localhost:9080/hidden", 404, "404 or 410 error") harness.AssertGetResponse(target+"private", 410, "404 or 410 error")
harness.AssertGetResponse(target+"hidden", 404, "404 or 410 error")
} }
func TestHandleErrorRange(t *testing.T) { func TestHandleErrorRange(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
root * /srv root * /srv
error /private* "Unauthorized" 410 error /private* "Unauthorized" 410
error /hidden* "Not found" 404 error /hidden* "Not found" 404
@ -729,17 +747,18 @@ func TestHandleErrorRange(t *testing.T) {
} }
}`, "caddyfile") }`, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
tester.AssertGetResponse("http://localhost:9080/hidden", 404, "Error in the [400 .. 499] range") harness.AssertGetResponse(target+"private", 410, "Error in the [400 .. 499] range")
harness.AssertGetResponse(target+"hidden", 404, "Error in the [400 .. 499] range")
} }
func TestHandleErrorSort(t *testing.T) { func TestHandleErrorSort(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
root * /srv root * /srv
error /private* "Unauthorized" 410 error /private* "Unauthorized" 410
error /hidden* "Not found" 404 error /hidden* "Not found" 404
@ -753,17 +772,18 @@ func TestHandleErrorSort(t *testing.T) {
} }
}`, "caddyfile") }`, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/internalerr", 500, "Fallback route: code outside the [400..499] range") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
tester.AssertGetResponse("http://localhost:9080/hidden", 404, "Error in the [400 .. 499] range") harness.AssertGetResponse(target+"internalerr", 500, "Fallback route: code outside the [400..499] range")
harness.AssertGetResponse(target+"hidden", 404, "Error in the [400 .. 499] range")
} }
func TestHandleErrorRangeAndCodes(t *testing.T) { func TestHandleErrorRangeAndCodes(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
root * /srv root * /srv
error /private* "Unauthorized" 410 error /private* "Unauthorized" 410
error /threehundred* "Moved Permanently" 301 error /threehundred* "Moved Permanently" 301
@ -777,14 +797,15 @@ func TestHandleErrorRangeAndCodes(t *testing.T) {
} }
}`, "caddyfile") }`, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/internalerr", 500, "Error code is equal to 500 or in the [300..399] range") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
tester.AssertGetResponse("http://localhost:9080/threehundred", 301, "Error code is equal to 500 or in the [300..399] range") harness.AssertGetResponse(target+"internalerr", 500, "Error code is equal to 500 or in the [300..399] range")
tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range") harness.AssertGetResponse(target+"threehundred", 301, "Error code is equal to 500 or in the [300..399] range")
harness.AssertGetResponse(target+"private", 410, "Error in the [400 .. 499] range")
} }
func TestHandleErrorSubHandlers(t *testing.T) { func TestHandleErrorSubHandlers(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
admin localhost:2999 admin localhost:2999
http_port 9080 http_port 9080
} }
@ -815,11 +836,11 @@ func TestHandleErrorSubHandlers(t *testing.T) {
} }
`, "caddyfile") `, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/en/notfound", 404, "not found") harness.AssertGetResponse("http://localhost:9080/en/notfound", 404, "not found")
tester.AssertGetResponse("http://localhost:9080/es/notfound", 404, "no encontrado") harness.AssertGetResponse("http://localhost:9080/es/notfound", 404, "no encontrado")
tester.AssertGetResponse("http://localhost:9080/notfound", 404, "default not found") harness.AssertGetResponse("http://localhost:9080/notfound", 404, "default not found")
tester.AssertGetResponse("http://localhost:9080/es/internalerr", 500, "Default error") harness.AssertGetResponse("http://localhost:9080/es/internalerr", 500, "Default error")
tester.AssertGetResponse("http://localhost:9080/en/internalerr", 500, "English error") harness.AssertGetResponse("http://localhost:9080/en/internalerr", 500, "English error")
} }
func TestInvalidSiteAddressesAsDirectives(t *testing.T) { func TestInvalidSiteAddressesAsDirectives(t *testing.T) {

View file

@ -10,14 +10,15 @@ import (
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
) )
func newH2ListenerWithVersionsWithTLSTester(t *testing.T, serverVersions []string, clientVersions []string) *caddytest.Tester { func newH2ListenerWithVersionsWithTLSTester(t *testing.T, serverVersions []string, clientVersions []string) *caddytest.TestHarness {
const baseConfig = ` const baseConfig = `
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
servers :9443 {
servers :{$TESTING_CADDY_PORT_TWO} {
protocols %s protocols %s
} }
} }
@ -25,10 +26,10 @@ func newH2ListenerWithVersionsWithTLSTester(t *testing.T, serverVersions []strin
respond "{http.request.tls.proto} {http.request.proto}" respond "{http.request.tls.proto} {http.request.proto}"
} }
` `
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(fmt.Sprintf(baseConfig, strings.Join(serverVersions, " ")), "caddyfile") harness.LoadConfig(fmt.Sprintf(baseConfig, strings.Join(serverVersions, " ")), "caddyfile")
tr := tester.Client.Transport.(*http.Transport) tr := harness.Client().Transport.(*http.Transport)
tr.TLSClientConfig.NextProtos = clientVersions tr.TLSClientConfig.NextProtos = clientVersions
tr.Protocols = new(http.Protocols) tr.Protocols = new(http.Protocols)
if slices.Contains(clientVersions, "h2") { if slices.Contains(clientVersions, "h2") {
@ -39,10 +40,10 @@ func newH2ListenerWithVersionsWithTLSTester(t *testing.T, serverVersions []strin
tr.Protocols.SetHTTP1(false) tr.Protocols.SetHTTP1(false)
} }
return tester return harness
} }
func TestH2ListenerWithTLS(t *testing.T) { func TestH2ListenerWithTLS(pt *testing.T) {
tests := []struct { tests := []struct {
serverVersions []string serverVersions []string
clientVersions []string clientVersions []string
@ -57,26 +58,28 @@ func TestH2ListenerWithTLS(t *testing.T) {
{[]string{"h2", "h1"}, []string{"http/1.1"}, "http/1.1 HTTP/1.1", false}, {[]string{"h2", "h1"}, []string{"http/1.1"}, "http/1.1 HTTP/1.1", false},
} }
for _, tc := range tests { for _, tc := range tests {
tester := newH2ListenerWithVersionsWithTLSTester(t, tc.serverVersions, tc.clientVersions) pt.Run(fmt.Sprintf("serverVersions=%v,clientVersions=%v", tc.serverVersions, tc.clientVersions), func(t *testing.T) {
t.Logf("running with server versions %v and client versions %v:", tc.serverVersions, tc.clientVersions) harness := newH2ListenerWithVersionsWithTLSTester(t, tc.serverVersions, tc.clientVersions)
if tc.failed { t.Logf("running with server versions %v and client versions %v:", tc.serverVersions, tc.clientVersions)
resp, err := tester.Client.Get("https://localhost:9443") if tc.failed {
if err == nil { resp, err := harness.Client().Get(fmt.Sprintf("http://localhost:%d", harness.Tester().PortTwo()))
t.Errorf("unexpected response: %d", resp.StatusCode) if err == nil {
t.Errorf("unexpected response: %d", resp.StatusCode)
}
} else {
harness.AssertGetResponse(fmt.Sprintf("https://localhost:%d", harness.Tester().PortTwo()), 200, tc.expectedBody)
} }
} else { })
tester.AssertGetResponse("https://localhost:9443", 200, tc.expectedBody)
}
} }
} }
func newH2ListenerWithVersionsWithoutTLSTester(t *testing.T, serverVersions []string, clientVersions []string) *caddytest.Tester { func newH2ListenerWithVersionsWithoutTLSTester(t *testing.T, serverVersions []string, clientVersions []string) *caddytest.TestHarness {
const baseConfig = ` const baseConfig = `
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
servers :9080 { servers :{$TESTING_CADDY_PORT_ONE} {
protocols %s protocols %s
} }
} }
@ -84,10 +87,10 @@ func newH2ListenerWithVersionsWithoutTLSTester(t *testing.T, serverVersions []st
respond "{http.request.proto}" respond "{http.request.proto}"
} }
` `
tester := caddytest.NewTester(t) tester := caddytest.StartHarness(t)
tester.InitServer(fmt.Sprintf(baseConfig, strings.Join(serverVersions, " ")), "caddyfile") tester.LoadConfig(fmt.Sprintf(baseConfig, strings.Join(serverVersions, " ")), "caddyfile")
tr := tester.Client.Transport.(*http.Transport) tr := tester.Client().Transport.(*http.Transport)
tr.Protocols = new(http.Protocols) tr.Protocols = new(http.Protocols)
if slices.Contains(clientVersions, "h2c") { if slices.Contains(clientVersions, "h2c") {
tr.Protocols.SetHTTP1(false) tr.Protocols.SetHTTP1(false)
@ -100,7 +103,7 @@ func newH2ListenerWithVersionsWithoutTLSTester(t *testing.T, serverVersions []st
return tester return tester
} }
func TestH2ListenerWithoutTLS(t *testing.T) { func TestH2ListenerWithoutTLS(pt *testing.T) {
tests := []struct { tests := []struct {
serverVersions []string serverVersions []string
clientVersions []string clientVersions []string
@ -115,15 +118,17 @@ func TestH2ListenerWithoutTLS(t *testing.T) {
{[]string{"h2c", "h1"}, []string{"http/1.1"}, "HTTP/1.1", false}, {[]string{"h2c", "h1"}, []string{"http/1.1"}, "HTTP/1.1", false},
} }
for _, tc := range tests { for _, tc := range tests {
tester := newH2ListenerWithVersionsWithoutTLSTester(t, tc.serverVersions, tc.clientVersions) pt.Run(fmt.Sprintf("serverVersions=%v,clientVersions=%v", tc.serverVersions, tc.clientVersions), func(t *testing.T) {
t.Logf("running with server versions %v and client versions %v:", tc.serverVersions, tc.clientVersions) harness := newH2ListenerWithVersionsWithoutTLSTester(t, tc.serverVersions, tc.clientVersions)
if tc.failed { t.Logf("running with server versions %v and client versions %v:", tc.serverVersions, tc.clientVersions)
resp, err := tester.Client.Get("http://localhost:9080") if tc.failed {
if err == nil { resp, err := harness.Client().Get(fmt.Sprintf("http://localhost:%d", harness.Tester().PortOne()))
t.Errorf("unexpected response: %d", resp.StatusCode) if err == nil {
t.Errorf("unexpected response: %d", resp.StatusCode)
}
} else {
harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d", harness.Tester().PortOne()), 200, tc.expectedBody)
} }
} else { })
tester.AssertGetResponse("http://localhost:9080", 200, tc.expectedBody)
}
} }
} }

View file

@ -2,6 +2,7 @@ package integration
import ( import (
"bytes" "bytes"
"fmt"
"net/http" "net/http"
"testing" "testing"
@ -9,36 +10,36 @@ import (
) )
func TestBrowse(t *testing.T) { func TestBrowse(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
http://localhost:9080 { http://localhost:{$TESTING_CADDY_PORT_ONE} {
file_server browse file_server browse
} }
`, "caddyfile") `, "caddyfile")
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), nil)
if err != nil { if err != nil {
t.Fail() t.Fail()
return return
} }
tester.AssertResponseCode(req, 200) harness.AssertResponseCode(req, 200)
} }
func TestRespondWithJSON(t *testing.T) { func TestRespondWithJSON(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost { localhost {
@ -46,7 +47,7 @@ func TestRespondWithJSON(t *testing.T) {
} }
`, "caddyfile") `, "caddyfile")
res, _ := tester.AssertPostResponseBody("https://localhost:9443/", res, _ := harness.AssertPostResponseBody(fmt.Sprintf("https://localhost:%d/", harness.Tester().PortTwo()),
nil, nil,
bytes.NewBufferString(`{ bytes.NewBufferString(`{
"greeting": "Hello, world!" "greeting": "Hello, world!"

View file

@ -1,22 +1,23 @@
package integration package integration
import ( import (
"fmt"
"testing" "testing"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
) )
func TestIntercept(t *testing.T) { func TestIntercept(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
respond /intercept "I'm a teapot" 408 respond /intercept "I'm a teapot" 408
header /intercept To-Intercept ok header /intercept To-Intercept ok
respond /no-intercept "I'm not a teapot" respond /no-intercept "I'm not a teapot"
@ -31,10 +32,9 @@ func TestIntercept(t *testing.T) {
} }
`, "caddyfile") `, "caddyfile")
r, _ := tester.AssertGetResponse("http://localhost:9080/intercept", 503, "I'm a combined coffee/tea pot that is temporarily out of coffee") r, _ := harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/intercept", harness.Tester().PortOne()), 503, "I'm a combined coffee/tea pot that is temporarily out of coffee")
if r.Header.Get("intercepted") != "ok" { if r.Header.Get("intercepted") != "ok" {
t.Fatalf(`header "intercepted" value is not "ok": %s`, r.Header.Get("intercepted")) t.Fatalf(`header "intercepted" value is not "ok": %s`, r.Header.Get("intercepted"))
} }
harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/no-intercept", harness.Tester().PortOne()), 200, "I'm not a teapot")
tester.AssertGetResponse("http://localhost:9080/no-intercept", 200, "I'm not a teapot")
} }

View file

@ -7,21 +7,21 @@ import (
) )
func TestLeafCertLoaders(t *testing.T) { func TestLeafCertLoaders(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {

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,15 +28,15 @@ func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc) *caddy
_ = srv.Close() _ = srv.Close()
_ = l.Close() _ = l.Close()
}) })
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(fmt.Sprintf(` harness.LoadConfig(fmt.Sprintf(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
local_certs local_certs
servers :9443 { servers :{$TESTING_CADDY_PORT_TWO} {
listener_wrappers { listener_wrappers {
http_redirect http_redirect
tls tls
@ -47,7 +47,7 @@ func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc) *caddy
reverse_proxy %s reverse_proxy %s
} }
`, l.Addr().String()), "caddyfile") `, l.Addr().String()), "caddyfile")
return tester return harness
} }
func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) { func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) {
@ -56,7 +56,7 @@ func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) {
body := make([]byte, uploadSize) body := make([]byte, uploadSize)
rand.New(rand.NewSource(0)).Read(body) rand.New(rand.NewSource(0)).Read(body)
tester := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) { harness := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
_, err := buf.ReadFrom(request.Body) _, err := buf.ReadFrom(request.Body)
if err != nil { if err != nil {
@ -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 := harness.Client().Post(fmt.Sprintf("https://localhost:%d", harness.Tester().PortTwo()), "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)
} }
@ -80,14 +80,14 @@ func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) {
} }
func TestLargeHttpRequest(t *testing.T) { func TestLargeHttpRequest(t *testing.T) {
tester := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) { harness := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) {
t.Fatal("not supposed to handle a request") t.Fatal("not supposed to handle a request")
}) })
// 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", fmt.Sprintf("http://localhost:%d", harness.Tester().PortTwo()), 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 := harness.Client().Do(req)
if err == nil { if err == nil {
t.Fatal("not supposed to succeed") t.Fatal("not supposed to succeed")
} }

View file

@ -2,6 +2,7 @@ package integration
import ( import (
"bytes" "bytes"
"fmt"
"testing" "testing"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
@ -9,16 +10,16 @@ import (
func TestMap(t *testing.T) { func TestMap(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
map {http.request.method} {dest-1} {dest-2} { map {http.request.method} {dest-1} {dest-2} {
default unknown1 unknown2 default unknown1 unknown2
@ -33,21 +34,21 @@ func TestMap(t *testing.T) {
`, "caddyfile") `, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost GET-called unknown2") harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne()), 200, "hello from localhost GET-called unknown2")
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called foobar") harness.AssertPostResponseBody(fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne()), []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called foobar")
} }
func TestMapRespondWithDefault(t *testing.T) { func TestMapRespondWithDefault(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
} }
localhost:9080 { localhost:{$TESTING_CADDY_PORT_ONE} {
map {http.request.method} {dest-name} { map {http.request.method} {dest-name} {
default unknown default unknown
@ -61,17 +62,17 @@ func TestMapRespondWithDefault(t *testing.T) {
`, "caddyfile") `, "caddyfile")
// act and assert // act and assert
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne()), 200, "hello from localhost get-called")
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost unknown") harness.AssertPostResponseBody(fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne()), []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost unknown")
} }
func TestMapAsJSON(t *testing.T) { func TestMapAsJSON(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@ -82,12 +83,12 @@ func TestMapAsJSON(t *testing.T) {
} }
}, },
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9080" ":{$TESTING_CADDY_PORT_ONE}"
], ],
"routes": [ "routes": [
{ {
@ -145,7 +146,7 @@ func TestMapAsJSON(t *testing.T) {
} }
} }
}`, "json") }`, "json")
target := fmt.Sprintf("http://localhost:%d/version", harness.Tester().PortOne())
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") harness.AssertGetResponse(target, 200, "hello from localhost get-called")
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called") harness.AssertPostResponseBody(target, []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called")
} }

View file

@ -14,11 +14,11 @@ import (
) )
func TestSRVReverseProxy(t *testing.T) { func TestSRVReverseProxy(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@ -87,11 +87,11 @@ func TestDialWithPlaceholderUnix(t *testing.T) {
}) })
runtime.Gosched() // Allow other goroutines to run runtime.Gosched() // Allow other goroutines to run
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@ -135,15 +135,15 @@ func TestDialWithPlaceholderUnix(t *testing.T) {
return return
} }
req.Header.Set("X-Caddy-Upstream-Dial", socketName) req.Header.Set("X-Caddy-Upstream-Dial", socketName)
tester.AssertResponse(req, 200, "Hello, World!") harness.AssertResponse(req, 200, "Hello, World!")
} }
func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) { func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@ -186,7 +186,7 @@ func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
}, },
"srv1": { "srv1": {
"listen": [ "listen": [
":9080" ":{$TESTING_CADDY_PORT_ONE}"
], ],
"routes": [ "routes": [
{ {
@ -223,21 +223,21 @@ func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
} }
`, "json") `, "json")
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d", harness.Tester().PortOne()), nil)
if err != nil { if err != nil {
t.Fail() t.Fail()
return return
} }
req.Header.Set("X-Caddy-Upstream-Dial", "localhost:18080") req.Header.Set("X-Caddy-Upstream-Dial", "localhost:18080")
tester.AssertResponse(req, 200, "Hello, World!") harness.AssertResponse(req, 200, "Hello, World!")
} }
func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) { func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"pki": { "pki": {
@ -280,7 +280,7 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
}, },
"srv1": { "srv1": {
"listen": [ "listen": [
":9080" ":{$TESTING_CADDY_PORT_ONE}"
], ],
"routes": [ "routes": [
{ {
@ -317,23 +317,23 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
} }
`, "json") `, "json")
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d", harness.Tester().PortOne()), nil)
if err != nil { if err != nil {
t.Fail() t.Fail()
return return
} }
req.Header.Set("X-Caddy-Upstream-Dial", "localhost") req.Header.Set("X-Caddy-Upstream-Dial", "localhost")
tester.AssertResponse(req, 200, "Hello, World!") harness.AssertResponse(req, 200, "Hello, World!")
} }
func TestReverseProxyHealthCheck(t *testing.T) { func TestReverseProxyHealthCheck(t *testing.T) {
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
http://localhost:2020 { http://localhost:2020 {
@ -342,7 +342,7 @@ func TestReverseProxyHealthCheck(t *testing.T) {
http://localhost:2021 { http://localhost:2021 {
respond "ok" respond "ok"
} }
http://localhost:9080 { http://localhost:{$TESTING_CADDY_PORT_ONE} {
reverse_proxy { reverse_proxy {
to localhost:2020 to localhost:2020
@ -357,14 +357,15 @@ func TestReverseProxyHealthCheck(t *testing.T) {
`, "caddyfile") `, "caddyfile")
time.Sleep(100 * time.Millisecond) // TODO: for some reason this test seems particularly flaky, getting 503 when it should be 200, unless we wait time.Sleep(100 * time.Millisecond) // TODO: for some reason this test seems particularly flaky, getting 503 when it should be 200, unless we wait
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target, 200, "Hello, World!")
} }
func TestReverseProxyHealthCheckUnixSocket(t *testing.T) { func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.SkipNow() t.SkipNow()
} }
tester := caddytest.NewTester(t) harness := 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,15 +396,15 @@ func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
}) })
runtime.Gosched() // Allow other goroutines to run runtime.Gosched() // Allow other goroutines to run
tester.InitServer(fmt.Sprintf(` harness.LoadConfig(fmt.Sprintf(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
http://localhost:9080 { http://localhost:{$TESTING_CADDY_PORT_ONE} {
reverse_proxy { reverse_proxy {
to unix/%s to unix/%s
@ -415,14 +416,15 @@ func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
} }
`, socketName), "caddyfile") `, socketName), "caddyfile")
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!") target := fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne())
harness.AssertGetResponse(target, 200, "Hello, World!")
} }
func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) { func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.SkipNow() t.SkipNow()
} }
tester := caddytest.NewTester(t) harness := 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,15 +455,15 @@ func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
}) })
runtime.Gosched() // Allow other goroutines to run runtime.Gosched() // Allow other goroutines to run
tester.InitServer(fmt.Sprintf(` harness.LoadConfig(fmt.Sprintf(`
{ {
skip_install_trust skip_install_trust
admin localhost:2999 admin {$TESTING_CADDY_ADMIN_BIND}
http_port 9080 http_port {$TESTING_CADDY_PORT_ONE}
https_port 9443 https_port {$TESTING_CADDY_PORT_TWO}
grace_period 1ns grace_period 1ns
} }
http://localhost:9080 { http://localhost:{$TESTING_CADDY_PORT_ONE} {
reverse_proxy { reverse_proxy {
to unix/%s to unix/%s
@ -472,5 +474,5 @@ func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
} }
`, socketName), "caddyfile") `, socketName), "caddyfile")
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!") harness.AssertGetResponse(fmt.Sprintf("http://localhost:%d/", harness.Tester().PortOne()), 200, "Hello, World!")
} }

View file

@ -1,6 +1,7 @@
package integration package integration
import ( import (
"fmt"
"testing" "testing"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
@ -8,20 +9,20 @@ import (
func TestDefaultSNI(t *testing.T) { func TestDefaultSNI(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(`{ harness.LoadConfig(`{
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@ -102,26 +103,27 @@ func TestDefaultSNI(t *testing.T) {
// act and assert // act and assert
// makes a request with no sni // makes a request with no sni
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost") target := fmt.Sprintf("https://127.0.0.1:%d/", harness.Tester().PortTwo())
harness.AssertGetResponse(target+"version", 200, "hello from a.caddy.localhost")
} }
func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) { func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@ -206,26 +208,27 @@ func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
// act and assert // act and assert
// makes a request with no sni // makes a request with no sni
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a") target := fmt.Sprintf("https://127.0.0.1:%d/", harness.Tester().PortTwo())
harness.AssertGetResponse(target+"version", 200, "hello from a")
} }
func TestDefaultSNIWithPortMappingOnly(t *testing.T) { func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
// arrange // arrange
tester := caddytest.NewTester(t) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@ -282,7 +285,8 @@ func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
// act and assert // act and assert
// makes a request with no sni // makes a request with no sni
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost") target := fmt.Sprintf("https://127.0.0.1:%d/", harness.Tester().PortTwo())
harness.AssertGetResponse(target+"version", 200, "hello from a.caddy.localhost")
} }
func TestHttpOnlyOnDomainWithSNI(t *testing.T) { func TestHttpOnlyOnDomainWithSNI(t *testing.T) {

View file

@ -21,21 +21,21 @@ 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) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@ -103,7 +103,7 @@ func TestH2ToH2CStream(t *testing.T) {
expectedBody := "some data to be echoed" expectedBody := "some data to be echoed"
// start the server // start the server
server := testH2ToH2CStreamServeH2C(t) server := testH2ToH2CStreamServeH2C(harness, t)
go server.ListenAndServe() go server.ListenAndServe()
defer func() { defer func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
@ -117,7 +117,7 @@ func TestH2ToH2CStream(t *testing.T) {
Body: io.NopCloser(r), Body: io.NopCloser(r),
URL: &url.URL{ URL: &url.URL{
Scheme: "https", Scheme: "https",
Host: "127.0.0.1:9443", Host: fmt.Sprintf("127.0.0.1:%d", harness.Tester().PortTwo()),
Path: "/tov2ray", Path: "/tov2ray",
}, },
Proto: "HTTP/2", Proto: "HTTP/2",
@ -128,7 +128,7 @@ func TestH2ToH2CStream(t *testing.T) {
// Disable any compression method from server. // Disable any compression method from server.
req.Header.Set("Accept-Encoding", "identity") req.Header.Set("Accept-Encoding", "identity")
resp := tester.AssertResponseCode(req, http.StatusOK) resp := harness.AssertResponseCode(req, http.StatusOK)
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return return
} }
@ -150,7 +150,7 @@ func TestH2ToH2CStream(t *testing.T) {
} }
} }
func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server { func testH2ToH2CStreamServeH2C(harness *caddytest.TestHarness, t *testing.T) *http.Server {
h2s := &http2.Server{} h2s := &http2.Server{}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rstring, err := httputil.DumpRequest(r, false) rstring, err := httputil.DumpRequest(r, false)
@ -164,7 +164,7 @@ func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
return return
} }
if r.Host != "127.0.0.1:9443" { if r.Host != fmt.Sprintf("127.0.0.1:%d", harness.Tester().PortTwo()) {
t.Errorf("r.Host doesn't match, %v!", r.Host) t.Errorf("r.Host doesn't match, %v!", r.Host)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
@ -205,28 +205,21 @@ 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) harness := caddytest.StartHarness(t)
tester.InitServer(` harness.LoadConfig(`
{ {
"admin": { "admin": {
"listen": "localhost:2999" "listen": "{$TESTING_CADDY_ADMIN_BIND}"
}, },
"logging": {
"logs": {
"default": {
"level": "DEBUG"
}
}
},
"apps": { "apps": {
"http": { "http": {
"http_port": 9080, "http_port": {$TESTING_CADDY_PORT_ONE},
"https_port": 9443, "https_port": {$TESTING_CADDY_PORT_TWO},
"grace_period": 1, "grace_period": 1,
"servers": { "servers": {
"srv0": { "srv0": {
"listen": [ "listen": [
":9443" ":{$TESTING_CADDY_PORT_TWO}"
], ],
"routes": [ "routes": [
{ {
@ -313,7 +306,7 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
} }
// start the server // start the server
server := testH2ToH1ChunkedResponseServeH1(t) server := testH2ToH1ChunkedResponseServeH1(harness, t)
go server.ListenAndServe() go server.ListenAndServe()
defer func() { defer func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
@ -327,7 +320,7 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
Body: io.NopCloser(r), Body: io.NopCloser(r),
URL: &url.URL{ URL: &url.URL{
Scheme: "https", Scheme: "https",
Host: "127.0.0.1:9443", Host: fmt.Sprintf("127.0.0.1:%d", harness.Tester().PortTwo()),
Path: "/tov2ray", Path: "/tov2ray",
}, },
Proto: "HTTP/2", Proto: "HTTP/2",
@ -341,7 +334,7 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
fmt.Fprint(w, expectedBody) fmt.Fprint(w, expectedBody)
w.Close() w.Close()
}() }()
resp := tester.AssertResponseCode(req, http.StatusOK) resp := harness.AssertResponseCode(req, http.StatusOK)
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return return
} }
@ -359,9 +352,9 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
} }
} }
func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server { func testH2ToH1ChunkedResponseServeH1(harness *caddytest.TestHarness, t *testing.T) *http.Server {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Host != "127.0.0.1:9443" { if r.Host != fmt.Sprintf("127.0.0.1:%d", harness.Tester().PortTwo()) {
t.Errorf("r.Host doesn't match, %v!", r.Host) t.Errorf("r.Host doesn't match, %v!", r.Host)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return

View file

@ -0,0 +1,241 @@
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(tc.t)
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 extracting 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)
}

View file

@ -20,6 +20,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/json" "encoding/json"
"errors" "errors"
"flag"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@ -308,6 +309,7 @@ func cmdRun(fl Flags) (int, error) {
// if enabled, reload config file automatically on changes // if enabled, reload config file automatically on changes
// (this better only be used in dev!) // (this better only be used in dev!)
// do not enable this during tests, it will cause leaks
if watchFlag { if watchFlag {
go watchConfigFile(configFile, adapterUsed) go watchConfigFile(configFile, adapterUsed)
} }
@ -331,10 +333,11 @@ func cmdRun(fl Flags) (int, error) {
} }
} }
// release the last local logger reference if flag.Lookup("test.v") == nil || !strings.Contains(os.Args[0], ".test") {
logger = nil //nolint:wastedassign,ineffassign select {}
} else {
select {} return caddy.ExitCodeSuccess, nil
}
} }
func cmdStop(fl Flags) (int, error) { func cmdStop(fl Flags) (int, error) {

View file

@ -525,6 +525,7 @@ argument of --directory. If the directory does not exist, it will be created.
# To load completions for every new session, run: # To load completions for every new session, run:
PS> %[1]s completion powershell > %[1]s.ps1 PS> %[1]s completion powershell > %[1]s.ps1
# and source this file from your PowerShell profile. # and source this file from your PowerShell profile.
`, rootCmd.Root().Name()), `, rootCmd.Root().Name()),
CobraFunc: func(cmd *cobra.Command) { CobraFunc: func(cmd *cobra.Command) {
cmd.DisableFlagsInUseLine = true cmd.DisableFlagsInUseLine = true

View file

@ -78,6 +78,18 @@ func Main() {
} }
} }
// MainForTesting implements the main function of the caddy command, used internally for testing
func MainForTesting(args ...string) error {
// create a root command for testing which will not pollute the global namespace, and does not
// call os.Exit().
rootCmd := defaultFactory.Build()
rootCmd.SetArgs(args)
if err := rootCmd.Execute(); err != nil {
return err
}
return nil
}
// handlePingbackConn reads from conn and ensures it matches // handlePingbackConn reads from conn and ensures it matches
// the bytes in expect, or returns an error if it doesn't. // the bytes in expect, or returns an error if it doesn't.
func handlePingbackConn(conn net.Conn, expect []byte) error { func handlePingbackConn(conn net.Conn, expect []byte) error {

View file

@ -710,6 +710,7 @@ type defaultCustomLog struct {
func newDefaultProductionLog() (*defaultCustomLog, error) { func newDefaultProductionLog() (*defaultCustomLog, error) {
cl := new(CustomLog) cl := new(CustomLog)
cl.writerOpener = StderrWriter{} cl.writerOpener = StderrWriter{}
var err error var err error
cl.writer, err = cl.writerOpener.OpenWriter() cl.writer, err = cl.writerOpener.OpenWriter()
if err != nil { if err != nil {

View file

@ -763,7 +763,6 @@ func (fsrv *FileServer) getEtagFromFile(fileSystem fs.FS, filename string) (stri
// Etags should not contain newline characters // Etags should not contain newline characters
etag = bytes.ReplaceAll(etag, []byte("\n"), []byte{}) etag = bytes.ReplaceAll(etag, []byte("\n"), []byte{})
return string(etag), nil return string(etag), nil
} }
return "", nil return "", nil