diff --git a/cmd/run.go b/cmd/run.go index d879595..f294ee1 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -26,6 +26,8 @@ var runCmd = &cobra.Command{ dir, _ := cmd.Flags().GetString("dir") showTime, _ := cmd.Flags().GetBool("time") quiet, _ := cmd.Flags().GetBool("quiet") + connectTimeout, _ := cmd.Flags().GetDuration("connect-timeout") + readTimeout, _ := cmd.Flags().GetDuration("read-timeout") if !quiet { log.Info().Msgf(emoji.Sprintf(":hammer_and_wrench: Starting tests!\n")) } else { @@ -44,7 +46,7 @@ var runCmd = &cobra.Command{ log.Fatal().Err(err) } - currentRun := runner.Run(include, exclude, showTime, quiet, tests) + currentRun := runner.Run(include, exclude, showTime, quiet, connectTimeout, readTimeout, tests) os.Exit(currentRun.Stats.TotalFailed()) }, } @@ -57,5 +59,6 @@ func init() { runCmd.Flags().StringP("dir", "d", ".", "recursively find yaml tests in this directory") runCmd.Flags().BoolP("quiet", "q", false, "do not show test by test, only results") runCmd.Flags().BoolP("time", "t", false, "show time spent per test") - runCmd.Flags().Duration("request-timeout", 1*time.Second, "timeout for individual requests during test execution") + runCmd.Flags().Duration("connect-timeout", 3*time.Second, "timeout for connecting to endpoints during test execution") + runCmd.Flags().Duration("read-timeout", 1*time.Second, "timeout for receiving responses during test execution") } diff --git a/ftwhttp/client.go b/ftwhttp/client.go index 1db65c1..e02332b 100644 --- a/ftwhttp/client.go +++ b/ftwhttp/client.go @@ -3,26 +3,32 @@ package ftwhttp import ( "crypto/tls" "fmt" + "github.com/rs/zerolog/log" + "golang.org/x/net/publicsuffix" "net" "net/http/cookiejar" "strings" "time" - - "github.com/rs/zerolog/log" - "golang.org/x/net/publicsuffix" ) +// NewClientConfig returns a new ClientConfig with reasonable defaults. +func NewClientConfig() ClientConfig { + return ClientConfig{ + ConnectTimeout: 3 * time.Second, + ReadTimeout: 1 * time.Second, + } +} + // NewClient initializes the http client, creating the cookiejar -func NewClient() *Client { +func NewClient(config ClientConfig) *Client { // All users of cookiejar should import "golang.org/x/net/publicsuffix" jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) if err != nil { log.Fatal().Err(err) } c := &Client{ - Jar: jar, - // default Timeout - Timeout: 3 * time.Second, + Jar: jar, + config: config, } return c } @@ -38,16 +44,17 @@ func (c *Client) NewConnection(d Destination) error { // strings.HasSuffix(err.String(), "connection refused") { if strings.ToLower(d.Protocol) == "https" { // Commenting InsecureSkipVerify: true. - netConn, err = tls.DialWithDialer(&net.Dialer{Timeout: c.Timeout}, "tcp", hostPort, &tls.Config{MinVersion: tls.VersionTLS12}) + netConn, err = tls.DialWithDialer(&net.Dialer{Timeout: c.config.ConnectTimeout}, "tcp", hostPort, &tls.Config{MinVersion: tls.VersionTLS12}) } else { - netConn, err = net.DialTimeout("tcp", hostPort, c.Timeout) + netConn, err = net.DialTimeout("tcp", hostPort, c.config.ConnectTimeout) } if err == nil { c.Transport = &Connection{ - connection: netConn, - protocol: d.Protocol, - duration: NewRoundTripTime(), + connection: netConn, + protocol: d.Protocol, + readTimeout: c.config.ReadTimeout, + duration: NewRoundTripTime(), } } diff --git a/ftwhttp/client_test.go b/ftwhttp/client_test.go index b5c4624..d447b2c 100644 --- a/ftwhttp/client_test.go +++ b/ftwhttp/client_test.go @@ -1,152 +1,152 @@ package ftwhttp import ( - "testing" + "testing" ) func TestNewClient(t *testing.T) { - c := NewClient() + c := NewClient(NewClientConfig()) - if c.Jar == nil { - t.Logf("Error creating Client") - } + if c.Jar == nil { + t.Logf("Error creating Client") + } } func TestConnectDestinationHTTPS(t *testing.T) { - d := &Destination{ - DestAddr: "example.com", - Port: 443, - Protocol: "https", - } - - c := NewClient() - - err := c.NewConnection(*d) - if err != nil { - t.Logf("This should not error") - } - - if c.Transport.protocol != "https" { - t.Logf("Error connecting to example.com using https") - } + d := &Destination{ + DestAddr: "example.com", + Port: 443, + Protocol: "https", + } + + c := NewClient(NewClientConfig()) + + err := c.NewConnection(*d) + if err != nil { + t.Logf("This should not error") + } + + if c.Transport.protocol != "https" { + t.Logf("Error connecting to example.com using https") + } } func TestDoRequest(t *testing.T) { - d := &Destination{ - DestAddr: "httpbin.org", - Port: 443, - Protocol: "https", - } + d := &Destination{ + DestAddr: "httpbin.org", + Port: 443, + Protocol: "https", + } - c := NewClient() + c := NewClient(NewClientConfig()) - req := generateBaseRequestForTesting() + req := generateBaseRequestForTesting() - err := c.NewConnection(*d) - if err != nil { - t.Logf("This should not error") - } + err := c.NewConnection(*d) + if err != nil { + t.Logf("This should not error") + } - _, err = c.Do(*req) + _, err = c.Do(*req) - if err == nil { - t.Logf("This should return error") - } + if err == nil { + t.Logf("This should return error") + } } func TestGetTrackedTime(t *testing.T) { - d := &Destination{ - DestAddr: "httpbin.org", - Port: 443, - Protocol: "https", - } + d := &Destination{ + DestAddr: "httpbin.org", + Port: 443, + Protocol: "https", + } - c := NewClient() + c := NewClient(NewClientConfig()) - rl := &RequestLine{ - Method: "POST", - URI: "/post", - Version: "HTTP/1.1", - } + rl := &RequestLine{ + Method: "POST", + URI: "/post", + Version: "HTTP/1.1", + } - h := Header{"Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost"} + h := Header{"Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost"} - data := []byte(`test=me&one=two&one=twice`) - req := NewRequest(rl, h, data, true) + data := []byte(`test=me&one=two&one=twice`) + req := NewRequest(rl, h, data, true) - err := c.NewConnection(*d) - if err != nil { - t.Logf("This should not error") - } + err := c.NewConnection(*d) + if err != nil { + t.Logf("This should not error") + } - c.StartTrackingTime() + c.StartTrackingTime() - resp, err := c.Do(*req) + resp, err := c.Do(*req) - c.StopTrackingTime() + c.StopTrackingTime() - if err != nil { - t.Logf("This should not error") - } + if err != nil { + t.Logf("This should not error") + } - if resp.Parsed.StatusCode != 200 { - t.Logf("Error in calling website") - } + if resp.Parsed.StatusCode != 200 { + t.Logf("Error in calling website") + } - rtt := c.GetRoundTripTime() + rtt := c.GetRoundTripTime() - if rtt.RoundTripDuration() < 0 { - t.Logf("Error getting RTT") - } + if rtt.RoundTripDuration() < 0 { + t.Logf("Error getting RTT") + } } func TestClientMultipartFormDataRequest(t *testing.T) { - d := &Destination{ - DestAddr: "httpbin.org", - Port: 443, - Protocol: "https", - } - - c := NewClient() - - rl := &RequestLine{ - Method: "POST", - URI: "/post", - Version: "HTTP/1.1", - } - - h := Header{ - "Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost", - "Content-Type": "multipart/form-data; boundary=--------397236876", - } - - data := []byte(`----------397236876 + d := &Destination{ + DestAddr: "httpbin.org", + Port: 443, + Protocol: "https", + } + + c := NewClient(NewClientConfig()) + + rl := &RequestLine{ + Method: "POST", + URI: "/post", + Version: "HTTP/1.1", + } + + h := Header{ + "Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost", + "Content-Type": "multipart/form-data; boundary=--------397236876", + } + + data := []byte(`----------397236876 Content-Disposition: form-data; name="fileRap"; filename="test.txt" Content-Type: text/plain Some-file-test-here ----------397236876--`) - req := NewRequest(rl, h, data, true) + req := NewRequest(rl, h, data, true) - err := c.NewConnection(*d) + err := c.NewConnection(*d) - if err != nil { - t.Logf("This should not error") - } + if err != nil { + t.Logf("This should not error") + } - c.StartTrackingTime() + c.StartTrackingTime() - resp, err := c.Do(*req) + resp, err := c.Do(*req) - c.StopTrackingTime() + c.StopTrackingTime() - if err != nil { - t.Logf("This should not error") - } + if err != nil { + t.Logf("This should not error") + } - if resp.Parsed.StatusCode != 200 { - t.Logf("Error in calling website") + if resp.Parsed.StatusCode != 200 { + t.Logf("Error in calling website") } } diff --git a/ftwhttp/connection.go b/ftwhttp/connection.go index c78d0ae..a29f0a8 100644 --- a/ftwhttp/connection.go +++ b/ftwhttp/connection.go @@ -70,13 +70,9 @@ func (c *Connection) receive() ([]byte, error) { var err error var buf []byte - // Set a deadline for reading. Read operation will fail if no data - // is received after deadline. - timeoutDuration := 1000 * time.Millisecond - // We assume the response body can be handled in memory without problems // That's why we use io.ReadAll - if err = c.connection.SetReadDeadline(time.Now().Add(timeoutDuration)); err == nil { + if err = c.connection.SetReadDeadline(time.Now().Add(c.readTimeout)); err == nil { buf, err = io.ReadAll(c.connection) } diff --git a/ftwhttp/response_test.go b/ftwhttp/response_test.go index 3ca8bfb..56b8ce2 100644 --- a/ftwhttp/response_test.go +++ b/ftwhttp/response_test.go @@ -88,7 +88,7 @@ func TestResponse(t *testing.T) { } req := generateRequestForTesting(true) - client := NewClient() + client := NewClient(NewClientConfig()) err = client.NewConnection(*d) if err != nil { @@ -98,7 +98,7 @@ func TestResponse(t *testing.T) { response, err := client.Do(*req) if err != nil { - t.Logf("Failed !") + t.Fatal(err) } if response.GetBodyAsString() != "Hello, client\n" { @@ -118,7 +118,7 @@ func TestResponseWithCookies(t *testing.T) { } req := generateRequestForTesting(true) - client := NewClient() + client := NewClient(NewClientConfig()) err = client.NewConnection(*d) if err != nil { diff --git a/ftwhttp/types.go b/ftwhttp/types.go index a679191..de29c47 100644 --- a/ftwhttp/types.go +++ b/ftwhttp/types.go @@ -6,18 +6,27 @@ import ( "time" ) +// ClientConfig provides configuration options for the HTTP client. +type ClientConfig struct { + // ConnectTimeout is the timeout for connecting to a server. + ConnectTimeout time.Duration + // ReadTimeout is the timeout for reading a response. + ReadTimeout time.Duration +} + // Client is the top level abstraction in http type Client struct { Transport *Connection Jar http.CookieJar - Timeout time.Duration + config ClientConfig } // Connection is the type used for sending/receiving data type Connection struct { - connection net.Conn - protocol string - duration *RoundTripTime + connection net.Conn + protocol string + readTimeout time.Duration + duration *RoundTripTime } // RoundTripTime abstracts the time a transaction takes diff --git a/runner/run.go b/runner/run.go index 8e58f7d..5f73a14 100644 --- a/runner/run.go +++ b/runner/run.go @@ -21,12 +21,16 @@ import ( // testid is the name of the unique test you want to run // exclude is a regexp that matches the test name: e.g. "920*", excludes all tests starting with "920" // Returns error if some test failed -func Run(include string, exclude string, showTime bool, output bool, ftwtests []test.FTWTest) TestRunContext { +func Run(include string, exclude string, showTime bool, output bool, + connectTimeout time.Duration, readTimeout time.Duration, ftwtests []test.FTWTest) TestRunContext { printUnlessQuietMode(output, ":rocket:Running go-ftw!\n") logLines := waflog.NewFTWLogLines(waflog.WithLogFile(config.FTWConfig.LogFile)) - client := ftwhttp.NewClient() + conf := ftwhttp.NewClientConfig() + conf.ConnectTimeout = connectTimeout + conf.ReadTimeout = readTimeout + client := ftwhttp.NewClient(conf) runContext := TestRunContext{ Include: include, Exclude: exclude, diff --git a/runner/run_test.go b/runner/run_test.go index 43eeb12..3075aef 100644 --- a/runner/run_test.go +++ b/runner/run_test.go @@ -1,18 +1,19 @@ package runner import ( - "fmt" - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/fzipi/go-ftw/check" - "github.com/fzipi/go-ftw/config" - "github.com/fzipi/go-ftw/ftwhttp" - "github.com/fzipi/go-ftw/test" - - "github.com/rs/zerolog/log" + "fmt" + "net/http" + "net/http/httptest" + "os" + "testing" + "time" + + "github.com/fzipi/go-ftw/check" + "github.com/fzipi/go-ftw/config" + "github.com/fzipi/go-ftw/ftwhttp" + "github.com/fzipi/go-ftw/test" + + "github.com/rs/zerolog/log" ) var yamlConfig = ` @@ -277,448 +278,448 @@ tests: // Error checking omitted for brevity func newTestServer(t *testing.T, logLines string) (destination *ftwhttp.Destination, logFilePath string) { - logFilePath = setUpLogFileForTestServer(t) + logFilePath = setUpLogFileForTestServer(t) - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("Hello, client")) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello, client")) - writeTestServerLog(t, logLines, logFilePath, r) - })) + writeTestServerLog(t, logLines, logFilePath, r) + })) - // close server after test - t.Cleanup(ts.Close) + // close server after test + t.Cleanup(ts.Close) - dest, err := ftwhttp.DestinationFromString(ts.URL) - if err != nil { - t.Error(err) - t.FailNow() - } - return dest, logFilePath + dest, err := ftwhttp.DestinationFromString(ts.URL) + if err != nil { + t.Error(err) + t.FailNow() + } + return dest, logFilePath } // Error checking omitted for brevity func newTestServerForCloudTest(t *testing.T, responseStatus int, logLines string) (server *httptest.Server, destination *ftwhttp.Destination) { - server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(responseStatus) - _, _ = w.Write([]byte("Hello, client")) - })) - - // close server after test - t.Cleanup(server.Close) - - dest, err := ftwhttp.DestinationFromString(server.URL) - if err != nil { - t.Error(err) - t.FailNow() - } - return server, dest + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(responseStatus) + _, _ = w.Write([]byte("Hello, client")) + })) + + // close server after test + t.Cleanup(server.Close) + + dest, err := ftwhttp.DestinationFromString(server.URL) + if err != nil { + t.Error(err) + t.FailNow() + } + return server, dest } func setUpLogFileForTestServer(t *testing.T) (logFilePath string) { - // log to the configured file - if config.FTWConfig != nil && config.FTWConfig.RunMode == config.DefaultRunMode { - logFilePath = config.FTWConfig.LogFile - } - // if no file has been configured, create one and handle cleanup - if logFilePath == "" { - file, err := os.CreateTemp("", "go-ftw-test-*.log") - if err != nil { - t.Error(err) - } - logFilePath = file.Name() - t.Cleanup(func() { - os.Remove(logFilePath) - log.Info().Msgf("Deleting temporary file '%s'", logFilePath) - }) - } - return logFilePath + // log to the configured file + if config.FTWConfig != nil && config.FTWConfig.RunMode == config.DefaultRunMode { + logFilePath = config.FTWConfig.LogFile + } + // if no file has been configured, create one and handle cleanup + if logFilePath == "" { + file, err := os.CreateTemp("", "go-ftw-test-*.log") + if err != nil { + t.Error(err) + } + logFilePath = file.Name() + t.Cleanup(func() { + os.Remove(logFilePath) + log.Info().Msgf("Deleting temporary file '%s'", logFilePath) + }) + } + return logFilePath } func writeTestServerLog(t *testing.T, logLines string, logFilePath string, r *http.Request) { - // write supplied log lines, emulating the output of the rule engine - logMessage := logLines - // if the request has the special test header, log the request instead - // this emulates the log marker rule - if r.Header.Get(config.FTWConfig.LogMarkerHeaderName) != "" { - logMessage = fmt.Sprintf("request line: %s %s %s, headers: %s\n", r.Method, r.RequestURI, r.Proto, r.Header) - } - file, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) - if err != nil { - t.Error(err) - t.FailNow() - } - defer file.Close() - - _, err = file.WriteString(logMessage) - if err != nil { - t.Error(err) - t.FailNow() - } + // write supplied log lines, emulating the output of the rule engine + logMessage := logLines + // if the request has the special test header, log the request instead + // this emulates the log marker rule + if r.Header.Get(config.FTWConfig.LogMarkerHeaderName) != "" { + logMessage = fmt.Sprintf("request line: %s %s %s, headers: %s\n", r.Method, r.RequestURI, r.Proto, r.Header) + } + file, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) + if err != nil { + t.Error(err) + t.FailNow() + } + defer file.Close() + + _, err = file.WriteString(logMessage) + if err != nil { + t.Error(err) + t.FailNow() + } } func replaceDestinationInTest(ftwTest *test.FTWTest, d ftwhttp.Destination) { - // This function doesn't use `range` because we want to modify the struct in place. - // Range (and assignments in general) create copies of structs, not references. - // Maps, slices, etc. on the other hand, are assigned as references. - for testIndex := 0; testIndex < len(ftwTest.Tests); testIndex++ { - testCase := &ftwTest.Tests[testIndex] - for stageIndex := 0; stageIndex < len(testCase.Stages); stageIndex++ { - input := &testCase.Stages[stageIndex].Stage.Input - - if *input.DestAddr == "TEST_ADDR" { - input.DestAddr = &d.DestAddr - } - if input.Headers.Get("Host") == "TEST_ADDR" { - input.Headers.Set("Host", d.DestAddr) - } - if input.Port != nil && *input.Port == -1 { - input.Port = &d.Port - } - } - } + // This function doesn't use `range` because we want to modify the struct in place. + // Range (and assignments in general) create copies of structs, not references. + // Maps, slices, etc. on the other hand, are assigned as references. + for testIndex := 0; testIndex < len(ftwTest.Tests); testIndex++ { + testCase := &ftwTest.Tests[testIndex] + for stageIndex := 0; stageIndex < len(testCase.Stages); stageIndex++ { + input := &testCase.Stages[stageIndex].Stage.Input + + if *input.DestAddr == "TEST_ADDR" { + input.DestAddr = &d.DestAddr + } + if input.Headers.Get("Host") == "TEST_ADDR" { + input.Headers.Set("Host", d.DestAddr) + } + if input.Port != nil && *input.Port == -1 { + input.Port = &d.Port + } + } + } } func replaceDestinationInConfiguration(dest ftwhttp.Destination) { - replaceableAddress := "TEST_ADDR" - replaceablePort := -1 - - input := &config.FTWConfig.TestOverride.Input - if input.DestAddr != nil && *input.DestAddr == replaceableAddress { - input.DestAddr = &dest.DestAddr - } - if input.Port != nil && *input.Port == replaceablePort { - input.Port = &dest.Port - } + replaceableAddress := "TEST_ADDR" + replaceablePort := -1 + + input := &config.FTWConfig.TestOverride.Input + if input.DestAddr != nil && *input.DestAddr == replaceableAddress { + input.DestAddr = &dest.DestAddr + } + if input.Port != nil && *input.Port == replaceablePort { + input.Port = &dest.Port + } } func TestRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlConfig) - if err != nil { - t.Errorf("Failed!") - } - - // setup test webserver (not a waf) - dest, logFilePath := newTestServer(t, logText) - config.FTWConfig.LogFile = logFilePath - ftwTest, err := test.GetTestFromYaml([]byte(yamlTest)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *dest) - - t.Run("show time and execute all", func(t *testing.T) { - if res := Run("", "", true, false, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Errorf("Oops, %d tests failed to run!", res.Stats.TotalFailed()) - } - }) - - t.Run("be verbose and execute all", func(t *testing.T) { - if res := Run("0*", "", true, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("don't show time and execute all", func(t *testing.T) { - if res := Run("0*", "", false, false, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("execute only test 008 but exclude all", func(t *testing.T) { - if res := Run("008", "0*", false, false, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("exclude test 010", func(t *testing.T) { - if res := Run("*", "010", false, false, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("test exceptions 1", func(t *testing.T) { - if res := Run("1*", "0*", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlConfig) + if err != nil { + t.Errorf("Failed!") + } + + // setup test webserver (not a waf) + dest, logFilePath := newTestServer(t, logText) + config.FTWConfig.LogFile = logFilePath + ftwTest, err := test.GetTestFromYaml([]byte(yamlTest)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *dest) + + t.Run("show time and execute all", func(t *testing.T) { + if res := Run("", "", true, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Errorf("Oops, %d tests failed to run!", res.Stats.TotalFailed()) + } + }) + + t.Run("be verbose and execute all", func(t *testing.T) { + if res := Run("0*", "", true, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("don't show time and execute all", func(t *testing.T) { + if res := Run("0*", "", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("execute only test 008 but exclude all", func(t *testing.T) { + if res := Run("008", "0*", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("exclude test 010", func(t *testing.T) { + if res := Run("*", "010", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("test exceptions 1", func(t *testing.T) { + if res := Run("1*", "0*", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestOverrideRun(t *testing.T) { - t.Cleanup(config.Reset) - - // setup test webserver (not a waf) - err := config.NewConfigFromString(yamlConfigOverride) - if err != nil { - t.Error(err) - } - - dest, logFilePath := newTestServer(t, logText) - - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - // replace host and port with values that can be overridden by config - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - t.Run("override and execute all", func(t *testing.T) { - if res := Run("", "", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + // setup test webserver (not a waf) + err := config.NewConfigFromString(yamlConfigOverride) + if err != nil { + t.Error(err) + } + + dest, logFilePath := newTestServer(t, logText) + + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + // replace host and port with values that can be overridden by config + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + t.Run("override and execute all", func(t *testing.T) { + if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestBrokenOverrideRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlBrokenConfigOverride) - if err != nil { - t.Errorf("Failed!") - } - - dest, logFilePath := newTestServer(t, logText) - - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - // replace host and port with values that can be overridden by config - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - // the test should succeed, despite the unknown override property - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("", "", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlBrokenConfigOverride) + if err != nil { + t.Errorf("Failed!") + } + + dest, logFilePath := newTestServer(t, logText) + + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + // replace host and port with values that can be overridden by config + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + // the test should succeed, despite the unknown override property + t.Run("showtime and execute all", func(t *testing.T) { + if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestBrokenPortOverrideRun(t *testing.T) { - t.Cleanup(config.Reset) - - // TestServer initialized first to retrieve the correct port number - dest, logFilePath := newTestServer(t, logText) - - // replace destination port inside the yaml with the retrieved one - err := config.NewConfigFromString(fmt.Sprintf(yamlConfigPortOverride, dest.Port)) - if err != nil { - t.Errorf("Failed!") - } - - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - // replace host and port with values that can be overridden by config - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverrideWithNoPort)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - // the test should succeed, despite the unknown override property - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("", "", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + // TestServer initialized first to retrieve the correct port number + dest, logFilePath := newTestServer(t, logText) + + // replace destination port inside the yaml with the retrieved one + err := config.NewConfigFromString(fmt.Sprintf(yamlConfigPortOverride, dest.Port)) + if err != nil { + t.Errorf("Failed!") + } + + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + // replace host and port with values that can be overridden by config + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverrideWithNoPort)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + // the test should succeed, despite the unknown override property + t.Run("showtime and execute all", func(t *testing.T) { + if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestDisabledRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlConfig) - if err != nil { - t.Errorf("Failed!") - } - - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlDisabledTest)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("*", "", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlConfig) + if err != nil { + t.Errorf("Failed!") + } + + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlDisabledTest)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + t.Run("showtime and execute all", func(t *testing.T) { + if res := Run("*", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestLogsRun(t *testing.T) { - t.Cleanup(config.Reset) - - // setup test webserver (not a waf) - dest, logFilePath := newTestServer(t, logText) - - err := config.NewConfigFromString(yamlConfig) - if err != nil { - t.Errorf("Failed!") - } - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *dest) - - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("", "", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + // setup test webserver (not a waf) + dest, logFilePath := newTestServer(t, logText) + + err := config.NewConfigFromString(yamlConfig) + if err != nil { + t.Errorf("Failed!") + } + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *dest) + + t.Run("showtime and execute all", func(t *testing.T) { + if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestCloudRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlCloudConfig) - if err != nil { - t.Errorf("Failed!") - } - - ftwTestDummy, err := test.GetTestFromYaml([]byte(yamlTestLogs)) - if err != nil { - t.Error(err) - } - - t.Run("don't show time and execute all", func(t *testing.T) { - for testCaseIndex, testCaseDummy := range ftwTestDummy.Tests { - for stageIndex := range testCaseDummy.Stages { - // Read the tests for every stage so we can replace the destination - // in each run. The server needs to be configured for each stage - // individually. - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) - if err != nil { - t.Error(err) - } - testCase := &ftwTest.Tests[testCaseIndex] - stage := &testCase.Stages[stageIndex].Stage - - ftwCheck := check.NewCheck(config.FTWConfig) - - // this mirrors check.SetCloudMode() - responseStatus := 200 - if stage.Output.LogContains != "" { - responseStatus = 403 - } else if stage.Output.NoLogContains != "" { - responseStatus = 405 - } - server, dest := newTestServerForCloudTest(t, responseStatus, logText) - - replaceDestinationInConfiguration(*dest) - - replaceDestinationInTest(&ftwTest, *dest) - if err != nil { - t.Error(err) - } - runContext := TestRunContext{ - Include: "", - Exclude: "", - ShowTime: false, - Output: true, - Client: ftwhttp.NewClient(), - LogLines: nil, - } - - RunStage(&runContext, ftwCheck, *testCase, *stage) - if runContext.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - - server.Close() - } - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlCloudConfig) + if err != nil { + t.Errorf("Failed!") + } + + ftwTestDummy, err := test.GetTestFromYaml([]byte(yamlTestLogs)) + if err != nil { + t.Error(err) + } + + t.Run("don't show time and execute all", func(t *testing.T) { + for testCaseIndex, testCaseDummy := range ftwTestDummy.Tests { + for stageIndex := range testCaseDummy.Stages { + // Read the tests for every stage so we can replace the destination + // in each run. The server needs to be configured for each stage + // individually. + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) + if err != nil { + t.Error(err) + } + testCase := &ftwTest.Tests[testCaseIndex] + stage := &testCase.Stages[stageIndex].Stage + + ftwCheck := check.NewCheck(config.FTWConfig) + + // this mirrors check.SetCloudMode() + responseStatus := 200 + if stage.Output.LogContains != "" { + responseStatus = 403 + } else if stage.Output.NoLogContains != "" { + responseStatus = 405 + } + server, dest := newTestServerForCloudTest(t, responseStatus, logText) + + replaceDestinationInConfiguration(*dest) + + replaceDestinationInTest(&ftwTest, *dest) + if err != nil { + t.Error(err) + } + runContext := TestRunContext{ + Include: "", + Exclude: "", + ShowTime: false, + Output: true, + Client: ftwhttp.NewClient(ftwhttp.NewClientConfig()), + LogLines: nil, + } + + RunStage(&runContext, ftwCheck, *testCase, *stage) + if runContext.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + + server.Close() + } + } + }) } func TestFailedTestsRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlConfig) - dest, logFilePath := newTestServer(t, logText) - if err != nil { - t.Errorf("Failed!") - } - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - ftwTest, err := test.GetTestFromYaml([]byte(yamlFailedTest)) - if err != nil { - t.Error(err.Error()) - } - replaceDestinationInTest(&ftwTest, *dest) - - t.Run("run test that fails", func(t *testing.T) { - if res := Run("*", "", false, false, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() != 1 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlConfig) + dest, logFilePath := newTestServer(t, logText) + if err != nil { + t.Errorf("Failed!") + } + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + ftwTest, err := test.GetTestFromYaml([]byte(yamlFailedTest)) + if err != nil { + t.Error(err.Error()) + } + replaceDestinationInTest(&ftwTest, *dest) + + t.Run("run test that fails", func(t *testing.T) { + if res := Run("*", "", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() != 1 { + t.Error("Oops, test run failed!") + } + }) } func TestApplyInputOverrideSetHostFromDestAddr(t *testing.T) { - t.Cleanup(config.Reset) - - originalHost := "original.com" - overrideHost := "override.com" - testInput := test.Input{ - DestAddr: &originalHost, - } - config.FTWConfig = &config.FTWConfiguration{ - TestOverride: config.FTWTestOverride{ - Input: test.Input{ - DestAddr: &overrideHost, - }, - }, - } - - err := applyInputOverride(&testInput) - if err != nil { - t.Error("Failed to apply input overrides", err) - } - - if *testInput.DestAddr != overrideHost { - t.Error("`dest_addr` should have been overridden") - } - if testInput.Headers == nil { - t.Error("Header map must exist after overriding `dest_addr`") - } - - hostHeader := testInput.Headers.Get("Host") - if hostHeader == "" { - t.Error("Host header must be set after overriding `dest_addr`") - } - if hostHeader != overrideHost { - t.Error("Host header must be identical to `dest_addr` after overrding `dest_addr`") - } + t.Cleanup(config.Reset) + + originalHost := "original.com" + overrideHost := "override.com" + testInput := test.Input{ + DestAddr: &originalHost, + } + config.FTWConfig = &config.FTWConfiguration{ + TestOverride: config.FTWTestOverride{ + Input: test.Input{ + DestAddr: &overrideHost, + }, + }, + } + + err := applyInputOverride(&testInput) + if err != nil { + t.Error("Failed to apply input overrides", err) + } + + if *testInput.DestAddr != overrideHost { + t.Error("`dest_addr` should have been overridden") + } + if testInput.Headers == nil { + t.Error("Header map must exist after overriding `dest_addr`") + } + + hostHeader := testInput.Headers.Get("Host") + if hostHeader == "" { + t.Error("Host header must be set after overriding `dest_addr`") + } + if hostHeader != overrideHost { + t.Error("Host header must be identical to `dest_addr` after overrding `dest_addr`") + } }