| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | package caddytest | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"crypto/tls" | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2021-09-30 01:17:48 +08:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2024-01-02 08:48:55 +03:00
										 |  |  | 	"io/fs" | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	"log" | 
					
						
							|  |  |  | 	"net" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 	"net/http/cookiejar" | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 	"reflect" | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	"regexp" | 
					
						
							|  |  |  | 	"runtime" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2020-03-23 13:08:02 +13:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-04 16:02:46 +13:00
										 |  |  | 	"github.com/aryann/difflib" | 
					
						
							| 
									
										
										
										
											2023-08-14 23:41:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-23 13:08:02 +13:00
										 |  |  | 	caddycmd "github.com/caddyserver/caddy/v2/cmd" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-14 23:41:15 +08:00
										 |  |  | 	"github.com/caddyserver/caddy/v2/caddyconfig" | 
					
						
							| 
									
										
										
										
											2020-03-23 13:08:02 +13:00
										 |  |  | 	// plug in Caddy modules here | 
					
						
							|  |  |  | 	_ "github.com/caddyserver/caddy/v2/modules/standard" | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Defaults store any configuration required to make the tests run | 
					
						
							|  |  |  | type Defaults struct { | 
					
						
							|  |  |  | 	// Port we expect caddy to listening on | 
					
						
							|  |  |  | 	AdminPort int | 
					
						
							|  |  |  | 	// Certificates we expect to be loaded before attempting to run the tests | 
					
						
							|  |  |  | 	Certifcates []string | 
					
						
							| 
									
										
										
										
											2020-05-02 10:24:35 +12:00
										 |  |  | 	// TestRequestTimeout is the time to wait for a http request to | 
					
						
							|  |  |  | 	TestRequestTimeout time.Duration | 
					
						
							|  |  |  | 	// LoadRequestTimeout is the time to wait for the config to be loaded against the caddy server | 
					
						
							|  |  |  | 	LoadRequestTimeout time.Duration | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Default testing values | 
					
						
							|  |  |  | var Default = Defaults{ | 
					
						
							| 
									
										
										
										
											2022-09-19 21:54:47 -06:00
										 |  |  | 	AdminPort:          2999, // different from what a real server also running on a developer's machine might be | 
					
						
							| 
									
										
										
										
											2020-05-02 10:24:35 +12:00
										 |  |  | 	Certifcates:        []string{"/caddy.localhost.crt", "/caddy.localhost.key"}, | 
					
						
							|  |  |  | 	TestRequestTimeout: 5 * time.Second, | 
					
						
							|  |  |  | 	LoadRequestTimeout: 5 * time.Second, | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	matchKey  = regexp.MustCompile(`(/[\w\d\.]+\.key)`) | 
					
						
							|  |  |  | 	matchCert = regexp.MustCompile(`(/[\w\d\.]+\.crt)`) | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | // Tester represents an instance of a test client. | 
					
						
							|  |  |  | type Tester struct { | 
					
						
							| 
									
										
										
										
											2020-05-10 08:11:35 +12:00
										 |  |  | 	Client       *http.Client | 
					
						
							|  |  |  | 	configLoaded bool | 
					
						
							|  |  |  | 	t            *testing.T | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewTester will create a new testing client with an attached cookie jar | 
					
						
							|  |  |  | func NewTester(t *testing.T) *Tester { | 
					
						
							|  |  |  | 	jar, err := cookiejar.New(nil) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("failed to create cookiejar: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &Tester{ | 
					
						
							|  |  |  | 		Client: &http.Client{ | 
					
						
							|  |  |  | 			Transport: CreateTestingTransport(), | 
					
						
							|  |  |  | 			Jar:       jar, | 
					
						
							| 
									
										
										
										
											2020-05-02 10:24:35 +12:00
										 |  |  | 			Timeout:   Default.TestRequestTimeout, | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2020-05-10 08:11:35 +12:00
										 |  |  | 		configLoaded: false, | 
					
						
							|  |  |  | 		t:            t, | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | type configLoadError struct { | 
					
						
							|  |  |  | 	Response string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (e configLoadError) Error() string { return e.Response } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-18 07:39:01 +13:00
										 |  |  | func timeElapsed(start time.Time, name string) { | 
					
						
							|  |  |  | 	elapsed := time.Since(start) | 
					
						
							|  |  |  | 	log.Printf("%s took %s", name, elapsed) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | // InitServer this will configure the server with a configurion of a specific | 
					
						
							|  |  |  | // type. The configType must be either "json" or the adapter type. | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | func (tc *Tester) InitServer(rawConfig string, configType string) { | 
					
						
							|  |  |  | 	if err := tc.initServer(rawConfig, configType); err != nil { | 
					
						
							|  |  |  | 		tc.t.Logf("failed to load config: %s", err) | 
					
						
							|  |  |  | 		tc.t.Fail() | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 	if err := tc.ensureConfigRunning(rawConfig, configType); err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-13 13:43:21 -06:00
										 |  |  | 		tc.t.Logf("failed ensuring config is running: %s", err) | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 		tc.t.Fail() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // InitServer this will configure the server with a configurion of a specific | 
					
						
							|  |  |  | // type. The configType must be either "json" or the adapter type. | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | func (tc *Tester) initServer(rawConfig string, configType string) error { | 
					
						
							| 
									
										
										
										
											2020-03-26 03:55:14 +13:00
										 |  |  | 	if testing.Short() { | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 		tc.t.SkipNow() | 
					
						
							| 
									
										
										
										
											2020-03-26 03:55:14 +13:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-05 21:49:41 +03:00
										 |  |  | 	err := validateTestPrerequisites(tc.t) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 		tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 	tc.t.Cleanup(func() { | 
					
						
							| 
									
										
										
										
											2020-05-10 08:11:35 +12:00
										 |  |  | 		if tc.t.Failed() && tc.configLoaded { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 			res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort)) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 				tc.t.Log("unable to read the current config") | 
					
						
							|  |  |  | 				return | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			defer res.Body.Close() | 
					
						
							| 
									
										
										
										
											2021-09-30 01:17:48 +08:00
										 |  |  | 			body, _ := io.ReadAll(res.Body) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			var out bytes.Buffer | 
					
						
							| 
									
										
										
										
											2020-11-22 16:50:29 -05:00
										 |  |  | 			_ = json.Indent(&out, body, "", "  ") | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 			tc.t.Logf("----------- failed with config -----------\n%s", out.String()) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rawConfig = prependCaddyFilePath(rawConfig) | 
					
						
							|  |  |  | 	client := &http.Client{ | 
					
						
							| 
									
										
										
										
											2020-05-02 10:24:35 +12:00
										 |  |  | 		Timeout: Default.LoadRequestTimeout, | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-03-18 07:39:01 +13:00
										 |  |  | 	start := time.Now() | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 		tc.t.Errorf("failed to create request. %s", err) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if configType == "json" { | 
					
						
							|  |  |  | 		req.Header.Add("Content-Type", "application/json") | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		req.Header.Add("Content-Type", "text/"+configType) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res, err := client.Do(req) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 		tc.t.Errorf("unable to contact caddy server. %s", err) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-03-18 07:39:01 +13:00
										 |  |  | 	timeElapsed(start, "caddytest: config load time") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	defer res.Body.Close() | 
					
						
							| 
									
										
										
										
											2021-09-30 01:17:48 +08:00
										 |  |  | 	body, err := io.ReadAll(res.Body) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 		tc.t.Errorf("unable to read response. %s", err) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if res.StatusCode != 200 { | 
					
						
							|  |  |  | 		return configLoadError{Response: string(body)} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-10 08:11:35 +12:00
										 |  |  | 	tc.configLoaded = true | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) 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) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-02 16:39:09 -04:00
										 |  |  | 	var expected any | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 	err := json.Unmarshal(expectedBytes, &expected) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client := &http.Client{ | 
					
						
							|  |  |  | 		Timeout: Default.LoadRequestTimeout, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-02 16:39:09 -04:00
										 |  |  | 	fetchConfig := func(client *http.Client) any { | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 		resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort)) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 		defer resp.Body.Close() | 
					
						
							| 
									
										
										
										
											2021-09-30 01:17:48 +08:00
										 |  |  | 		actualBytes, err := io.ReadAll(resp.Body) | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-08-02 16:39:09 -04:00
										 |  |  | 		var actual any | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 		err = json.Unmarshal(actualBytes, &actual) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return actual | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-05 11:40:41 -06:00
										 |  |  | 	for retries := 10; retries > 0; retries-- { | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 		if reflect.DeepEqual(expected, fetchConfig(client)) { | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-10-27 23:12:30 +01:00
										 |  |  | 		time.Sleep(1 * time.Second) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 	tc.t.Errorf("POSTed configuration isn't active") | 
					
						
							|  |  |  | 	return errors.New("EnsureConfigRunning: POSTed configuration isn't active") | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-05 21:49:41 +03:00
										 |  |  | const initConfig = `{ | 
					
						
							|  |  |  | 	admin localhost:2999 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | // validateTestPrerequisites ensures the certificates are available in the | 
					
						
							|  |  |  | // designated path and Caddy sub-process is running. | 
					
						
							| 
									
										
										
										
											2022-12-05 21:49:41 +03:00
										 |  |  | func validateTestPrerequisites(t *testing.T) error { | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	// check certificates are found | 
					
						
							|  |  |  | 	for _, certName := range Default.Certifcates { | 
					
						
							| 
									
										
										
										
											2024-01-02 08:48:55 +03:00
										 |  |  | 		if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) { | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 			return fmt.Errorf("caddy integration test certificates (%s) not found", certName) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-23 13:08:02 +13:00
										 |  |  | 	if isCaddyAdminRunning() != nil { | 
					
						
							| 
									
										
										
										
											2022-12-05 21:49:41 +03:00
										 |  |  | 		// setup the init config file, and set the cleanup afterwards | 
					
						
							|  |  |  | 		f, err := os.CreateTemp("", "") | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.Cleanup(func() { | 
					
						
							|  |  |  | 			os.Remove(f.Name()) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		if _, err := f.WriteString(initConfig); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-23 13:08:02 +13:00
										 |  |  | 		// start inprocess caddy server | 
					
						
							| 
									
										
										
										
											2022-12-05 21:49:41 +03:00
										 |  |  | 		os.Args = []string{"caddy", "run", "--config", f.Name(), "--adapter", "caddyfile"} | 
					
						
							| 
									
										
										
										
											2020-03-23 13:08:02 +13:00
										 |  |  | 		go func() { | 
					
						
							|  |  |  | 			caddycmd.Main() | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 		// wait for caddy to start serving the initial config | 
					
						
							| 
									
										
										
										
											2022-10-05 11:40:41 -06:00
										 |  |  | 		for retries := 10; retries > 0 && isCaddyAdminRunning() != nil; retries-- { | 
					
						
							| 
									
										
										
										
											2022-10-27 23:12:30 +01:00
										 |  |  | 			time.Sleep(1 * time.Second) | 
					
						
							| 
									
										
										
										
											2020-03-23 13:08:02 +13:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-05 18:36:52 +03:00
										 |  |  | 	// one more time to return the error | 
					
						
							|  |  |  | 	return isCaddyAdminRunning() | 
					
						
							| 
									
										
										
										
											2020-03-23 13:08:02 +13:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func isCaddyAdminRunning() error { | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	// assert that caddy is running | 
					
						
							|  |  |  | 	client := &http.Client{ | 
					
						
							| 
									
										
										
										
											2020-05-02 10:24:35 +12:00
										 |  |  | 		Timeout: Default.LoadRequestTimeout, | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-11-22 16:50:29 -05:00
										 |  |  | 	resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort)) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-01-14 11:30:49 +10:00
										 |  |  | 		return fmt.Errorf("caddy integration test caddy server not running. Expected to be listening on localhost:%d", Default.AdminPort) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-11-22 16:50:29 -05:00
										 |  |  | 	resp.Body.Close() | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | // CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally | 
					
						
							|  |  |  | func CreateTestingTransport() *http.Transport { | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	dialer := net.Dialer{ | 
					
						
							|  |  |  | 		Timeout:   5 * time.Second, | 
					
						
							|  |  |  | 		KeepAlive: 5 * time.Second, | 
					
						
							|  |  |  | 		DualStack: true, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { | 
					
						
							|  |  |  | 		parts := strings.Split(addr, ":") | 
					
						
							|  |  |  | 		destAddr := fmt.Sprintf("127.0.0.1:%s", parts[1]) | 
					
						
							|  |  |  | 		log.Printf("caddytest: redirecting the dialer from %s to %s", addr, destAddr) | 
					
						
							|  |  |  | 		return dialer.DialContext(ctx, network, destAddr) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &http.Transport{ | 
					
						
							|  |  |  | 		Proxy:                 http.ProxyFromEnvironment, | 
					
						
							|  |  |  | 		DialContext:           dialContext, | 
					
						
							|  |  |  | 		ForceAttemptHTTP2:     true, | 
					
						
							|  |  |  | 		MaxIdleConns:          100, | 
					
						
							|  |  |  | 		IdleConnTimeout:       90 * time.Second, | 
					
						
							|  |  |  | 		TLSHandshakeTimeout:   5 * time.Second, | 
					
						
							|  |  |  | 		ExpectContinueTimeout: 1 * time.Second, | 
					
						
							| 
									
										
										
										
											2020-11-22 16:50:29 -05:00
										 |  |  | 		TLSClientConfig:       &tls.Config{InsecureSkipVerify: true}, //nolint:gosec | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // AssertLoadError will load a config and expect an error | 
					
						
							|  |  |  | func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) { | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 	tc := NewTester(t) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 	err := tc.initServer(rawConfig, configType) | 
					
						
							|  |  |  | 	if !strings.Contains(err.Error(), expectedError) { | 
					
						
							|  |  |  | 		t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error()) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // AssertRedirect makes a request and asserts the redirection happens | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response { | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	redirectPolicyFunc := func(req *http.Request, via []*http.Request) error { | 
					
						
							|  |  |  | 		return http.ErrUseLastResponse | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 	// 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 }() | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 	resp, err := tc.Client.Get(requestURI) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 		tc.t.Errorf("failed to call server %s", err) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if expectedStatusCode != resp.StatusCode { | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 		tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode) | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	loc, err := resp.Location() | 
					
						
							| 
									
										
										
										
											2020-03-24 16:18:53 +13:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 		tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got error: %s", requestURI, expectedToLocation, err) | 
					
						
							| 
									
										
										
										
											2020-03-24 16:18:53 +13:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-12-10 14:36:46 -07:00
										 |  |  | 	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()) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-03-14 06:32:53 +13:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return resp | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-04-04 16:02:46 +13:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-14 17:53:28 -04:00
										 |  |  | // CompareAdapt adapts a config and then compares it against an expected result | 
					
						
							| 
									
										
										
										
											2021-01-19 14:21:11 -07:00
										 |  |  | func CompareAdapt(t *testing.T, filename, rawConfig string, adapterName string, expectedResponse string) bool { | 
					
						
							| 
									
										
										
										
											2020-04-04 16:02:46 +13:00
										 |  |  | 	cfgAdapter := caddyconfig.GetAdapter(adapterName) | 
					
						
							|  |  |  | 	if cfgAdapter == nil { | 
					
						
							| 
									
										
										
										
											2020-05-14 17:53:28 -04:00
										 |  |  | 		t.Logf("unrecognized config adapter '%s'", adapterName) | 
					
						
							|  |  |  | 		return false | 
					
						
							| 
									
										
										
										
											2020-04-04 16:02:46 +13:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-02 16:39:09 -04:00
										 |  |  | 	options := make(map[string]any) | 
					
						
							| 
									
										
										
										
											2020-04-04 16:02:46 +13:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	result, warnings, err := cfgAdapter.Adapt([]byte(rawConfig), options) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-05-14 17:53:28 -04:00
										 |  |  | 		t.Logf("adapting config using %s adapter: %v", adapterName, err) | 
					
						
							|  |  |  | 		return false | 
					
						
							| 
									
										
										
										
											2020-04-04 16:02:46 +13:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-04 11:11:36 -07:00
										 |  |  | 	// 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() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-04 16:02:46 +13:00
										 |  |  | 	if len(warnings) > 0 { | 
					
						
							|  |  |  | 		for _, w := range warnings { | 
					
						
							| 
									
										
										
										
											2021-01-19 14:21:11 -07:00
										 |  |  | 			t.Logf("warning: %s:%d: %s: %s", filename, w.Line, w.Directive, w.Message) | 
					
						
							| 
									
										
										
										
											2020-04-04 16:02:46 +13:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-05-14 17:53:28 -04:00
										 |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // AssertAdapt adapts a config and then tests it against an expected result | 
					
						
							|  |  |  | func AssertAdapt(t *testing.T, rawConfig string, adapterName string, expectedResponse string) { | 
					
						
							| 
									
										
										
										
											2021-01-19 14:21:11 -07:00
										 |  |  | 	ok := CompareAdapt(t, "Caddyfile", rawConfig, adapterName, expectedResponse) | 
					
						
							| 
									
										
										
										
											2020-05-14 17:53:28 -04:00
										 |  |  | 	if !ok { | 
					
						
							| 
									
										
										
										
											2020-04-04 16:02:46 +13:00
										 |  |  | 		t.Fail() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Generic request functions | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func applyHeaders(t *testing.T, req *http.Request, requestHeaders []string) { | 
					
						
							|  |  |  | 	requestContentType := "" | 
					
						
							|  |  |  | 	for _, requestHeader := range requestHeaders { | 
					
						
							|  |  |  | 		arr := strings.SplitAfterN(requestHeader, ":", 2) | 
					
						
							|  |  |  | 		k := strings.TrimRight(arr[0], ":") | 
					
						
							|  |  |  | 		v := strings.TrimSpace(arr[1]) | 
					
						
							|  |  |  | 		if k == "Content-Type" { | 
					
						
							|  |  |  | 			requestContentType = v | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.Logf("Request header: %s => %s", k, v) | 
					
						
							|  |  |  | 		req.Header.Set(k, v) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if requestContentType == "" { | 
					
						
							|  |  |  | 		t.Logf("Content-Type header not provided") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions | 
					
						
							|  |  |  | func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response { | 
					
						
							|  |  |  | 	resp, err := tc.Client.Do(req) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		tc.t.Fatalf("failed to call server %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if expectedStatusCode != resp.StatusCode { | 
					
						
							| 
									
										
										
										
											2023-10-02 22:55:09 +02:00
										 |  |  | 		tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.URL.RequestURI(), expectedStatusCode, resp.StatusCode) | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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() | 
					
						
							| 
									
										
										
										
											2021-09-30 01:17:48 +08:00
										 |  |  | 	bytes, err := io.ReadAll(resp.Body) | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		tc.t.Fatalf("unable to read the response body %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	body := string(bytes) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-02 14:23:40 -06:00
										 |  |  | 	if body != expectedBody { | 
					
						
							| 
									
										
										
										
											2020-04-27 13:23:46 +12:00
										 |  |  | 		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) | 
					
						
							|  |  |  | } |