diff --git a/.editorconfig b/.editorconfig
index f45b19b..2c0ed4d 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -5,4 +5,7 @@ indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
-insert_final_newline = false
\ No newline at end of file
+insert_final_newline = true
+
+[*.yaml]
+indent_size = 2
diff --git a/check/base.go b/check/base.go
index 32c9836..548dacd 100644
--- a/check/base.go
+++ b/check/base.go
@@ -16,16 +16,18 @@ type FTWCheck struct {
}
// NewCheck creates a new FTWCheck, allowing to inject the configuration
-func NewCheck(c *config.FTWConfiguration) *FTWCheck {
- //TODO: check error
- ll, _ := waflog.NewFTWLogLines(c)
+func NewCheck(c *config.FTWConfiguration) (*FTWCheck, error) {
+ ll, err := waflog.NewFTWLogLines(c)
+ if err != nil {
+ return nil, err
+ }
check := &FTWCheck{
log: ll,
cfg: c,
expected: &test.Output{},
}
- return check
+ return check, nil
}
// SetExpectTestOutput sets the combined expected output from this test
diff --git a/check/base_test.go b/check/base_test.go
index 9ee2017..f29d28f 100644
--- a/check/base_test.go
+++ b/check/base_test.go
@@ -4,35 +4,57 @@ import (
"sort"
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/coreruleset/go-ftw/utils"
+
+ "github.com/stretchr/testify/suite"
"github.com/coreruleset/go-ftw/config"
"github.com/coreruleset/go-ftw/test"
)
-var yamlApacheConfig = `---
-logfile: 'tests/logs/modsec2-apache/apache2/error.log'
-`
-
-var yamlNginxConfig = `---
+var configMap = map[string]string{
+ "TestNewCheck": `---
logfile: 'tests/logs/modsec3-nginx/nginx/error.log'
testoverride:
ignore:
'942200-1': 'Ignore Me'
-`
+`, "TestForced": `---
+testoverride:
+ ignore:
+ '942200-1': 'Ignore Me'
+ forcepass:
+ '1245': 'Forced Pass'
+ forcefail:
+ '6789': 'Forced Fail'
+`, "TestCloudMode": `---
+mode: "cloud"`,
+}
-var yamlCloudConfig = `---
-mode: "cloud"
-`
+type checkBaseTestSuite struct {
+ suite.Suite
+ cfg *config.FTWConfiguration
+}
-func TestNewCheck(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlNginxConfig)
- assert.NoError(t, err)
+func (s *checkBaseTestSuite) BeforeTest(_, name string) {
+ var err error
+ var logName string
+ s.cfg, err = config.NewConfigFromString(configMap[name])
+ s.NoError(err)
+ logName, err = utils.CreateTempFileWithContent(logText, "test-*.log")
+ s.NoError(err)
+ s.cfg.WithLogfile(logName)
+}
- c := NewCheck(cfg)
+func TestCheckBaseTestSuite(t *testing.T) {
+ suite.Run(t, new(checkBaseTestSuite))
+}
+
+func (s *checkBaseTestSuite) TestNewCheck() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
for _, text := range c.cfg.TestOverride.Ignore {
- assert.Equal(t, text, "Ignore Me", "Well, didn't match Ignore Me")
+ s.Equal(text, "Ignore Me", "Well, didn't match Ignore Me")
}
to := test.Output{
@@ -44,33 +66,37 @@ func TestNewCheck(t *testing.T) {
}
c.SetExpectTestOutput(&to)
- assert.True(t, c.expected.ExpectError, "Problem setting expected output")
+ s.True(c.expected.ExpectError, "Problem setting expected output")
c.SetNoLogContains("nologcontains")
- assert.Equal(t, c.expected.NoLogContains, "nologcontains", "Problem setting nologcontains")
+ s.Equal(c.expected.NoLogContains, "nologcontains", "Problem setting nologcontains")
}
-func TestForced(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlNginxConfig)
- assert.NoError(t, err)
+func (s *checkBaseTestSuite) TestForced() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
- c := NewCheck(cfg)
+ s.True(c.ForcedIgnore("942200-1"), "Can't find ignored value")
- assert.True(t, c.ForcedIgnore("942200-1"), "Can't find ignored value")
+ s.False(c.ForcedFail("1245"), "Value should not be found")
- assert.False(t, c.ForcedFail("1245"), "Value should not be found")
+ s.False(c.ForcedPass("1234"), "Value should not be found")
- assert.False(t, c.ForcedPass("1245"), "Value should not be found")
-}
+ s.True(c.ForcedPass("1245"), "Value should be found")
+
+ s.True(c.ForcedFail("6789"), "Value should be found")
+
+ s.cfg.TestOverride.Ignore = make(map[*config.FTWRegexp]string)
+ s.Falsef(c.ForcedIgnore("anything"), "Should not find ignored value in empty map")
-func TestCloudMode(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlCloudConfig)
- assert.NoError(t, err)
+}
- c := NewCheck(cfg)
+func (s *checkBaseTestSuite) TestCloudMode() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
- assert.True(t, c.CloudMode(), "couldn't detect cloud mode")
+ s.True(c.CloudMode(), "couldn't detect cloud mode")
status := []int{200, 301}
c.SetExpectStatus(status)
@@ -81,7 +107,7 @@ func TestCloudMode(t *testing.T) {
cloudStatus := c.expected.Status
sort.Ints(cloudStatus)
res := sort.SearchInts(cloudStatus, 403)
- assert.Equalf(t, 2, res, "couldn't find expected 403 status in %#v -> %d", cloudStatus, res)
+ s.Equalf(2, res, "couldn't find expected 403 status in %#v -> %d", cloudStatus, res)
c.SetLogContains("")
c.SetNoLogContains("no log contains")
@@ -96,6 +122,16 @@ func TestCloudMode(t *testing.T) {
found = true
}
}
- assert.True(t, found, "couldn't find expected 200 status")
+ s.True(found, "couldn't find expected 200 status")
+
+}
+
+func (s *checkBaseTestSuite) TestSetMarkers() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
+ c.SetStartMarker([]byte("TesTingStArtMarKer"))
+ c.SetEndMarker([]byte("TestIngEnDMarkeR"))
+ s.Equal([]byte("testingstartmarker"), c.log.StartMarker, "Couldn't set start marker")
+ s.Equal([]byte("testingendmarker"), c.log.EndMarker, "Couldn't set end marker")
}
diff --git a/check/error_test.go b/check/error_test.go
index 15c7406..b49df57 100644
--- a/check/error_test.go
+++ b/check/error_test.go
@@ -4,7 +4,9 @@ import (
"errors"
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/coreruleset/go-ftw/utils"
+
+ "github.com/stretchr/testify/suite"
"github.com/coreruleset/go-ftw/config"
)
@@ -25,25 +27,38 @@ var expectedFailTests = []struct {
{errors.New("a"), false},
}
-func TestAssertResponseErrorOK(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlApacheConfig)
- assert.NoError(t, err)
+type checkErrorTestSuite struct {
+ suite.Suite
+ cfg *config.FTWConfiguration
+}
+
+func TestCheckErrorTestSuite(t *testing.T) {
+ suite.Run(t, new(checkErrorTestSuite))
+}
- c := NewCheck(cfg)
+func (s *checkErrorTestSuite) SetupTest() {
+ var err error
+ s.cfg = config.NewDefaultConfig()
+
+ logName, err := utils.CreateTempFileWithContent(logText, "test-*.log")
+ s.NoError(err)
+ s.cfg.WithLogfile(logName)
+}
+func (s *checkErrorTestSuite) TestAssertResponseErrorOK() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
for _, e := range expectedOKTests {
c.SetExpectError(e.expected)
- assert.Equal(t, e.expected, c.AssertExpectError(e.err))
+ s.Equal(e.expected, c.AssertExpectError(e.err))
}
}
-func TestAssertResponseFail(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlApacheConfig)
- assert.NoError(t, err)
-
- c := NewCheck(cfg)
+func (s *checkErrorTestSuite) TestAssertResponseFail() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
for _, e := range expectedFailTests {
c.SetExpectError(e.expected)
- assert.False(t, c.AssertExpectError(e.err))
+ s.False(c.AssertExpectError(e.err))
}
}
diff --git a/check/logs_test.go b/check/logs_test.go
index 746d660..b7a7b83 100644
--- a/check/logs_test.go
+++ b/check/logs_test.go
@@ -4,7 +4,7 @@ import (
"os"
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
"github.com/coreruleset/go-ftw/config"
"github.com/coreruleset/go-ftw/utils"
@@ -16,24 +16,50 @@ var logText = `[Tue Jan 05 02:21:09.637165 2021] [:error] [pid 76:tid 1396834345
[Tue Jan 05 02:21:09.647668 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:inbound_anomaly_score. [file "/etc/modsecurity.d/owasp-crs/rules/RESPONSE-980-CORRELATION.conf"] [line "87"] [id "980130"] [msg "Inbound Anomaly Score Exceeded (Total Inbound Score: 5 - SQLI=0,XSS=0,RFI=0,LFI=0,RCE=0,PHPI=0,HTTP=0,SESS=0): individual paranoia level scores: 3, 2, 0, 0"] [ver "OWASP_CRS/3.3.0"] [tag "event-correlation"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
`
-func TestAssertLogContainsOK(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlApacheConfig)
- assert.NoError(t, err)
+type checkLogsTestSuite struct {
+ suite.Suite
+ cfg *config.FTWConfiguration
+ logName string
+}
- logName, _ := utils.CreateTempFileWithContent(logText, "test-*.log")
- defer os.Remove(logName)
- cfg.WithLogfile(logName)
+func TestCheckLogsTestSuite(t *testing.T) {
+ suite.Run(t, new(checkLogsTestSuite))
+}
- c := NewCheck(cfg)
+func (s *checkLogsTestSuite) SetupTest() {
+ var err error
+ s.cfg = config.NewDefaultConfig()
+
+ s.logName, err = utils.CreateTempFileWithContent(logText, "test-*.log")
+ s.NoError(err)
+ s.cfg.WithLogfile(s.logName)
+}
+
+func (s *checkLogsTestSuite) TearDownTest() {
+ err := os.Remove(s.logName)
+ s.NoError(err)
+}
+func (s *checkLogsTestSuite) TestAssertLogContainsOK() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
c.SetLogContains(`id "920300"`)
- assert.True(t, c.AssertLogContains(), "did not find expected content 'id \"920300\"'")
+ s.True(c.AssertLogContains(), "did not find expected content 'id \"920300\"'")
+
c.SetLogContains(`SOMETHING`)
- assert.False(t, c.AssertLogContains(), "found something that is not there")
+ s.False(c.AssertLogContains(), "found something that is not there")
+ s.True(c.LogContainsRequired(), "if LogContains is not empty it should return true")
+
+ c.SetLogContains("")
+ s.False(c.AssertLogContains(), "empty LogContains should return false")
c.SetNoLogContains("SOMETHING")
- assert.True(t, c.AssertNoLogContains(), "found something that is not there")
+ s.True(c.AssertNoLogContains(), "found something that is not there")
+
c.SetNoLogContains(`id "920300"`)
- assert.False(t, c.AssertNoLogContains(), "did not find expected content")
+ s.False(c.AssertNoLogContains(), "did not find expected content")
+ c.SetNoLogContains("")
+ s.False(c.AssertNoLogContains(), "should return false when empty string is passed")
+ s.False(c.NoLogContainsRequired(), "if NoLogContains is an empty string is passed should return false")
}
diff --git a/check/response_test.go b/check/response_test.go
index a723cda..c03a384 100644
--- a/check/response_test.go
+++ b/check/response_test.go
@@ -3,7 +3,9 @@ package check
import (
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/coreruleset/go-ftw/utils"
+
+ "github.com/stretchr/testify/suite"
"github.com/coreruleset/go-ftw/config"
)
@@ -20,37 +22,57 @@ var expectedResponseFailTests = []struct {
expected string
}{
{`
`, "not found"},
+ {``, `empty should return false`},
+}
+
+type checkResponseTestSuite struct {
+ suite.Suite
+ cfg *config.FTWConfiguration
+}
+
+func TestCheckResponseTestSuite(t *testing.T) {
+ suite.Run(t, new(checkResponseTestSuite))
}
-func TestAssertResponseTextErrorOK(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlApacheConfig)
- assert.NoError(t, err)
+func (s *checkResponseTestSuite) SetupTest() {
+ var err error
+ s.cfg = config.NewDefaultConfig()
+ logName, err := utils.CreateTempFileWithContent(logText, "test-*.log")
+ s.NoError(err)
+ s.cfg.WithLogfile(logName)
+}
- c := NewCheck(cfg)
+func (s *checkResponseTestSuite) TestAssertResponseTextErrorOK() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
for _, e := range expectedResponseOKTests {
c.SetExpectResponse(e.expected)
- assert.Truef(t, c.AssertResponseContains(e.response), "unexpected response: %v", e.response)
+ s.Truef(c.AssertResponseContains(e.response), "unexpected response: %v", e.response)
}
}
-func TestAssertResponseTextFailOK(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlApacheConfig)
- assert.NoError(t, err)
-
- c := NewCheck(cfg)
+func (s *checkResponseTestSuite) TestAssertResponseTextFailOK() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
for _, e := range expectedResponseFailTests {
c.SetExpectResponse(e.expected)
- assert.Falsef(t, c.AssertResponseContains(e.response), "response shouldn't contain text %v", e.response)
+ s.Falsef(c.AssertResponseContains(e.response), "response shouldn't contain text %v", e.response)
}
}
-func TestAssertResponseTextChecksFullResponseOK(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlApacheConfig)
- assert.NoError(t, err)
-
- c := NewCheck(cfg)
+func (s *checkResponseTestSuite) TestAssertResponseTextChecksFullResponseOK() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
for _, e := range expectedResponseOKTests {
c.SetExpectResponse(e.expected)
- assert.Truef(t, c.AssertResponseContains(e.response), "unexpected response: %v", e.response)
+ s.Truef(c.AssertResponseContains(e.response), "unexpected response: %v", e.response)
}
}
+
+func (s *checkResponseTestSuite) TestAssertResponseContainsRequired() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
+ c.SetExpectResponse("")
+ s.False(c.AssertResponseContains(""), "response shouldn't contain text")
+ s.False(c.ResponseContainsRequired(), "response shouldn't contain text")
+}
diff --git a/check/status_test.go b/check/status_test.go
index 95fbb06..f8861d8 100644
--- a/check/status_test.go
+++ b/check/status_test.go
@@ -3,7 +3,9 @@ package check
import (
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/coreruleset/go-ftw/utils"
+
+ "github.com/stretchr/testify/suite"
"github.com/coreruleset/go-ftw/config"
)
@@ -25,26 +27,47 @@ var statusFailTests = []struct {
{200, []int{0}},
}
-func TestStatusOK(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlApacheConfig)
- assert.NoError(t, err)
+type checkStatusTestSuite struct {
+ suite.Suite
+ cfg *config.FTWConfiguration
+}
+
+func (s *checkStatusTestSuite) SetupTest() {
+ var err error
+ s.cfg = config.NewDefaultConfig()
+ logName, err := utils.CreateTempFileWithContent(logText, "test-*.log")
+ s.NoError(err)
+ s.cfg.WithLogfile(logName)
+}
+
+func TestCheckStatusTestSuite(t *testing.T) {
+ suite.Run(t, new(checkStatusTestSuite))
+}
- c := NewCheck(cfg)
+func (s *checkStatusTestSuite) TestStatusOK() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
for _, expected := range statusOKTests {
c.SetExpectStatus(expected.expectedStatus)
- assert.True(t, c.AssertStatus(expected.status))
+ s.True(c.AssertStatus(expected.status))
}
}
-func TestStatusFail(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlApacheConfig)
- assert.NoError(t, err)
-
- c := NewCheck(cfg)
+func (s *checkStatusTestSuite) TestStatusFail() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
for _, expected := range statusFailTests {
c.SetExpectStatus(expected.expectedStatus)
- assert.False(t, c.AssertStatus(expected.status))
+ s.False(c.AssertStatus(expected.status))
}
}
+
+func (s *checkStatusTestSuite) TestStatusCodeRequired() {
+ c, err := NewCheck(s.cfg)
+ s.NoError(err)
+
+ c.SetExpectStatus([]int{200})
+ s.True(c.StatusCodeRequired(), "status code should be required")
+}
diff --git a/cmd/check.go b/cmd/check.go
index 2152453..a9a848d 100644
--- a/cmd/check.go
+++ b/cmd/check.go
@@ -2,12 +2,10 @@ package cmd
import (
"fmt"
- "os"
+ "github.com/coreruleset/go-ftw/test"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
-
- "github.com/coreruleset/go-ftw/test"
)
// NewCheckCmd represents the check command
@@ -16,25 +14,22 @@ func NewCheckCommand() *cobra.Command {
Use: "check",
Short: "Checks ftw test files for syntax errors.",
Long: ``,
- Run: func(cmd *cobra.Command, args []string) {
+ RunE: func(cmd *cobra.Command, args []string) error {
dir, _ := cmd.Flags().GetString("dir")
- checkFiles(dir)
+ return checkFiles(dir)
+
},
}
checkCmd.Flags().StringP("dir", "d", ".", "recursively find yaml tests in this directory")
return checkCmd
}
-func checkFiles(dir string) {
- var exit int
+func checkFiles(dir string) error {
files := fmt.Sprintf("%s/**/*.yaml", dir)
log.Trace().Msgf("ftw/check: checking files using glob pattern: %s", files)
tests, err := test.GetTestsFromFiles(files)
- if err != nil {
- exit = 1
- } else {
+ if err == nil {
fmt.Printf("ftw/check: checked %d files, everything looks good!\n", len(tests))
- exit = 0
}
- os.Exit(exit)
+ return err
}
diff --git a/cmd/check_test.go b/cmd/check_test.go
new file mode 100644
index 0000000..9b8abe4
--- /dev/null
+++ b/cmd/check_test.go
@@ -0,0 +1,76 @@
+package cmd
+
+import (
+ "context"
+ "io/fs"
+ "os"
+ "testing"
+
+ "github.com/spf13/cobra"
+ "github.com/stretchr/testify/suite"
+)
+
+var checkFileContents = `---
+meta:
+ author: "go-ftw"
+ enabled: true
+ name: "mock-TestRunTests_Run.yaml"
+ description: "Test file for go-ftw"
+tests:
+ - # Standard GET request
+ test_title: 1234
+ stages:
+ - stage:
+ input:
+ dest_addr: "127.0.0.1"
+ method: "GET"
+ port: 1234
+ headers:
+ User-Agent: "OWASP CRS test agent"
+ Host: "localhost"
+ Accept: "*/*"
+ protocol: "http"
+ uri: "/"
+ version: "HTTP/1.1"
+ output:
+ status: [200]
+`
+
+type checkCmdTestSuite struct {
+ suite.Suite
+ tempDir string
+ rootCmd *cobra.Command
+}
+
+func (s *checkCmdTestSuite) SetupTest() {
+ tempDir, err := os.MkdirTemp("", "go-ftw-tests")
+ s.NoError(err)
+ s.tempDir = tempDir
+
+ err = os.MkdirAll(s.tempDir, fs.ModePerm)
+ s.NoError(err)
+ testFileContents, err := os.CreateTemp(s.tempDir, "mock-test-*.yaml")
+ s.NoError(err)
+ n, err := testFileContents.WriteString(checkFileContents)
+ s.NoError(err)
+ s.Equal(len(checkFileContents), n)
+
+ s.rootCmd = NewRootCommand()
+ s.rootCmd.AddCommand(NewCheckCommand())
+}
+
+func (s *checkCmdTestSuite) TearDownTest() {
+ err := os.RemoveAll(s.tempDir)
+ s.NoError(err)
+}
+
+func TestCheckChoreTestSuite(t *testing.T) {
+ suite.Run(t, new(checkCmdTestSuite))
+}
+
+func (s *checkCmdTestSuite) TestCheckCommand() {
+ s.rootCmd.SetArgs([]string{"check", "-d", s.tempDir})
+ cmd, err := s.rootCmd.ExecuteContextC(context.Background())
+ s.NoError(err, "check command should not return an error")
+ s.Equal("check", cmd.Name(), "check command should have the name 'check'")
+}
diff --git a/cmd/root.go b/cmd/root.go
index 6c7f5f9..4f83204 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -2,9 +2,7 @@ package cmd
import (
"context"
- "errors"
"log"
- "os"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
@@ -37,19 +35,13 @@ func NewRootCommand() *cobra.Command {
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
-func Execute(version string) {
+func Execute(version string) error {
rootCmd := NewRootCommand()
rootCmd.AddCommand(NewCheckCommand())
rootCmd.AddCommand(NewRunCommand())
rootCmd.Version = version
- if err := rootCmd.ExecuteContext(context.Background()); err != nil {
- if errors.Is(err, context.DeadlineExceeded) {
- os.Exit(2)
- }
-
- os.Exit(1)
- }
+ return rootCmd.ExecuteContext(context.Background())
}
func init() {
diff --git a/cmd/root_test.go b/cmd/root_test.go
new file mode 100644
index 0000000..58ee436
--- /dev/null
+++ b/cmd/root_test.go
@@ -0,0 +1,22 @@
+package cmd
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+)
+
+type rootCmdTestSuite struct {
+ suite.Suite
+}
+
+func TestRootChoreTestSuite(t *testing.T) {
+ suite.Run(t, new(rootCmdTestSuite))
+}
+
+func (s *rootCmdTestSuite) TestRootCommand() {
+ rootCmd := NewRootCommand()
+ rootCmd.SetArgs([]string{"help"})
+ err := Execute("v1.0.0")
+ s.NoError(err)
+}
diff --git a/cmd/run_test.go b/cmd/run_test.go
index 38af34d..4a796cf 100644
--- a/cmd/run_test.go
+++ b/cmd/run_test.go
@@ -21,7 +21,7 @@ var testFileContentsTemplate = `---
meta:
author: "go-ftw"
enabled: true
- name: "mock-test.yaml"
+ name: "mock-TestRunTests_Run.yaml"
description: "Test file for go-ftw"
tests:
- # Standard GET request
diff --git a/config/config.go b/config/config.go
index e0f0c45..10f9e81 100644
--- a/config/config.go
+++ b/config/config.go
@@ -24,6 +24,14 @@ func NewDefaultConfig() *FTWConfiguration {
return cfg
}
+// NewCloudConfig initializes the configuration with cloud values
+func NewCloudConfig() *FTWConfiguration {
+ cfg := NewDefaultConfig()
+ cfg.RunMode = CloudRunMode
+
+ return cfg
+}
+
// NewConfigFromFile reads configuration information from the config file if it exists,
// or uses `.ftw.yaml` as default file
func NewConfigFromFile(cfgFile string) (*FTWConfiguration, error) {
@@ -134,6 +142,6 @@ func (c *FTWConfiguration) WithMaxMarkerRetries(retries int) {
}
// WithMaxMarkerLogLines sets the new amount of lines we go back in the logfile attempting to find markers.
-func (c *FTWConfiguration) WithMaxMarkerLogLines(retries int) {
- c.MaxMarkerLogLines = retries
+func (c *FTWConfiguration) WithMaxMarkerLogLines(amount int) {
+ c.MaxMarkerLogLines = amount
}
diff --git a/config/config_test.go b/config/config_test.go
index e33c9cd..89deb52 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -5,12 +5,14 @@ import (
"regexp"
"testing"
+ "github.com/stretchr/testify/suite"
+
"github.com/coreruleset/go-ftw/test"
"github.com/coreruleset/go-ftw/utils"
- "github.com/stretchr/testify/assert"
)
-var yamlConfig = `---
+var testData = map[string]string{
+ "TestNewConfigFromFile": `---
logfile: 'tests/logs/modsec2-apache/apache2/error.log'
testoverride:
input:
@@ -18,148 +20,186 @@ testoverride:
port: '1234'
ignore:
'920400-1$': 'This test must be ignored'
-`
-
-var yamlCloudConfig = `---
+`,
+ "TestNewConfigFromFileRunMode": `---
mode: 'cloud'
-`
-
-var yamlBadConfig = `
+`,
+ "bad": `
---
logfile: 'tests/logs/modsec2-apache/apache2/error.log'
doesNotExist: ""
-`
+`,
+ "jsonConfig": `
+{"test": "type"}
+`,
+}
-var jsonConfig = `{"test": "type"}`
+type fileTestSuite struct {
+ suite.Suite
+ filename string
+ cfg *FTWConfiguration
+}
-func TestNewDefaultConfig(t *testing.T) {
- cfg := NewDefaultConfig()
- assert.Equal(t, DefaultLogMarkerHeaderName, cfg.LogMarkerHeaderName)
- assert.Equal(t, DefaultRunMode, cfg.RunMode)
- assert.Equal(t, "", cfg.LogFile)
+type envTestSuite struct {
+ suite.Suite
}
-func TestNewConfigBadFileConfig(t *testing.T) {
- filename, _ := utils.CreateTempFileWithContent(jsonConfig, "test-*.yaml")
- defer os.Remove(filename)
- cfg, err := NewConfigFromFile(filename)
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+type baseTestSuite struct {
+ suite.Suite
}
-func TestNewConfigConfig(t *testing.T) {
- filename, _ := utils.CreateTempFileWithContent(yamlConfig, "test-*.yaml")
+func TestConfigTestSuite(t *testing.T) {
+ suite.Run(t, new(baseTestSuite))
+ suite.Run(t, new(fileTestSuite))
+ suite.Run(t, new(envTestSuite))
+}
- cfg, err := NewConfigFromFile(filename)
+func (s *fileTestSuite) SetupTest() {
+}
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
- assert.NotEmpty(t, cfg.TestOverride.Overrides, "Ignore list must not be empty")
+func (s *envTestSuite) SetupTest() {
+}
+
+func (s *fileTestSuite) BeforeTest(_, name string) {
+ var err error
+ s.filename, _ = utils.CreateTempFileWithContent(testData[name], "test-*.yaml")
+ s.cfg, err = NewConfigFromFile(s.filename)
+ s.NoError(err)
+ s.NotNil(s.cfg)
+}
- for id, text := range cfg.TestOverride.Ignore {
- assert.Contains(t, (*regexp.Regexp)(id).String(), "920400-1$", "Looks like we could not find item to ignore")
- assert.Equal(t, "This test must be ignored", text, "Text doesn't match")
+func (s *fileTestSuite) TearDownTest() {
+ if s.filename != "" {
+ err := os.Remove(s.filename)
+ s.NoError(err)
+ s.filename = ""
}
+}
+
+func (s *baseTestSuite) TestBaseUnmarshalText() {
+ var ftwRegexp FTWRegexp
+ err := ftwRegexp.UnmarshalText([]byte("test"))
+ s.NoError(err)
+ s.NotNil(ftwRegexp)
+ s.True(ftwRegexp.MatchString("This is a test for unmarshalling"), "looks like we could not match string")
+}
+
+func (s *baseTestSuite) TestBaseNewFTWRegexpText() {
+ ftwRegexp, err := NewFTWRegexp("test")
+ s.NoError(err)
+ s.NotNil(ftwRegexp)
+ s.True(ftwRegexp.MatchString("This is a test"), "looks like we could not match string")
+}
- overrides := cfg.TestOverride.Overrides
- assert.NotNil(t, overrides.DestAddr, "Looks like we are not overriding destination address")
- assert.Equal(t, "httpbingo.org", *overrides.DestAddr, "Looks like we are not overriding destination address")
+func (s *baseTestSuite) TestNewCloudConfig() {
+ cfg := NewCloudConfig()
+ s.Equal(CloudRunMode, cfg.RunMode)
+ s.Equal("", cfg.LogFile)
}
-func TestNewConfigBadConfig(t *testing.T) {
- filename, _ := utils.CreateTempFileWithContent(yamlBadConfig, "test-*.yaml")
+func (s *baseTestSuite) TestNewDefaultConfig() {
+ cfg := NewDefaultConfig()
+ s.Equal(DefaultLogMarkerHeaderName, cfg.LogMarkerHeaderName)
+ s.Equal(DefaultRunMode, cfg.RunMode)
+ s.Equal("", cfg.LogFile)
+}
+
+func (s *fileTestSuite) TestNewConfigBadFileConfig() {
+ filename, _ := utils.CreateTempFileWithContent(testData["jsonConfig"], "test-*.yaml")
defer os.Remove(filename)
cfg, err := NewConfigFromFile(filename)
+ s.NoError(err)
+ s.NotNil(cfg)
+}
+
+func (s *fileTestSuite) TestNewConfigFromFile() {
+ s.NotEmpty(s.cfg.TestOverride.Overrides, "Ignore list must not be empty")
+
+ for id, text := range s.cfg.TestOverride.Ignore {
+ s.Contains((*regexp.Regexp)(id).String(), "920400-1$", "Looks like we could not find item to ignore")
+ s.Equal("This test must be ignored", text, "Text doesn't match")
+ }
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+ overrides := s.cfg.TestOverride.Overrides
+ s.NotNil(overrides.DestAddr, "Looks like we are not overriding destination address")
+ s.Equal("httpbingo.org", *overrides.DestAddr, "Looks like we are not overriding destination address")
}
-func TestNewConfigDefaultConfig(t *testing.T) {
+func (s *fileTestSuite) TestNewConfigBadConfig() {
+ // contents come from `bad` YAML config
+ s.NotNil(s.cfg)
+}
+
+func (s *fileTestSuite) TestNewConfigDefaultConfig() {
// For this test we need a local .ftw.yaml file
- fileName := ".ftw.yaml"
- _ = os.WriteFile(fileName, []byte(yamlConfig), 0644)
- t.Cleanup(func() {
- os.Remove(fileName)
- })
+ s.filename = ".ftw.yaml"
+ _ = os.WriteFile(s.filename, []byte(testData["ok"]), 0644)
cfg, err := NewConfigFromFile("")
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+ s.NoError(err)
+ s.NotNil(cfg)
}
-func TestNewConfigFromString(t *testing.T) {
- cfg, err := NewConfigFromString(yamlConfig)
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+func (s *fileTestSuite) TestNewConfigFromString() {
+ cfg, err := NewConfigFromString(testData["ok"])
+ s.NoError(err)
+ s.NotNil(cfg)
}
-func TestNewEnvConfigFromString(t *testing.T) {
- cfg, err := NewConfigFromString(yamlConfig)
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+func (s *fileTestSuite) TestNewConfigFromNoneExistingFile() {
+ cfg, err := NewConfigFromFile("nonsense")
+ s.Error(err)
+ s.Nil(cfg)
}
-func TestNewConfigFromEnv(t *testing.T) {
+func (s *fileTestSuite) TestNewConfigFromEnv() {
// Set some environment so it gets merged with conf
os.Setenv("FTW_LOGFILE", "koanf")
cfg, err := NewConfigFromEnv()
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
- assert.Equal(t, "koanf", cfg.LogFile)
+ s.NoError(err)
+ s.NotNil(cfg)
+ s.Equal("koanf", cfg.LogFile)
}
-func TestNewConfigFromEnvHasDefaults(t *testing.T) {
+func (s *fileTestSuite) TestNewConfigFromEnvHasDefaults() {
cfg, err := NewConfigFromEnv()
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+ s.NoError(err)
+ s.NotNil(cfg)
- assert.Equalf(t, DefaultRunMode, cfg.RunMode,
+ s.Equalf(DefaultRunMode, cfg.RunMode,
"unexpected default value '%s' for run mode", cfg.RunMode)
- assert.Equalf(t, DefaultLogMarkerHeaderName, cfg.LogMarkerHeaderName,
+ s.Equalf(DefaultLogMarkerHeaderName, cfg.LogMarkerHeaderName,
"unexpected default value '%s' for logmarkerheadername", cfg.LogMarkerHeaderName)
}
-func TestNewConfigFromFileHasDefaults(t *testing.T) {
- filename, _ := utils.CreateTempFileWithContent(yamlConfig, "test-*.yaml")
- defer os.Remove(filename)
-
- cfg, err := NewConfigFromFile(filename)
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
- assert.Equalf(t, DefaultRunMode, cfg.RunMode,
- "unexpected default value '%s' for run mode", cfg.RunMode)
- assert.Equalf(t, DefaultLogMarkerHeaderName, cfg.LogMarkerHeaderName,
- "unexpected default value '%s' for logmarkerheadername", cfg.LogMarkerHeaderName)
+func (s *fileTestSuite) TestNewConfigFromFileHasDefaults() {
+ s.Equalf(DefaultRunMode, s.cfg.RunMode,
+ "unexpected default value '%s' for run mode", s.cfg.RunMode)
+ s.Equalf(DefaultLogMarkerHeaderName, s.cfg.LogMarkerHeaderName,
+ "unexpected default value '%s' for logmarkerheadername", s.cfg.LogMarkerHeaderName)
}
-func TestNewConfigFromStringHasDefaults(t *testing.T) {
+func (s *fileTestSuite) TestNewConfigFromStringHasDefaults() {
cfg, err := NewConfigFromString("")
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
- assert.Equalf(t, DefaultRunMode, cfg.RunMode,
+ s.NoError(err)
+ s.NotNil(cfg)
+ s.Equalf(DefaultRunMode, cfg.RunMode,
"unexpected default value '%s' for run mode", cfg.RunMode)
- assert.Equalf(t, DefaultLogMarkerHeaderName, cfg.LogMarkerHeaderName,
- "unexpected default value '%s' for logmarkerheadername", cfg.LogMarkerHeaderName)
+ s.Equalf(DefaultLogMarkerHeaderName, cfg.LogMarkerHeaderName,
+ "unexpected default value '%s' for logmarkerheadername", s.cfg.LogMarkerHeaderName)
}
-func TestNewConfigFromFileRunMode(t *testing.T) {
- filename, _ := utils.CreateTempFileWithContent(yamlCloudConfig, "test-*.yaml")
- defer os.Remove(filename)
-
- cfg, err := NewConfigFromFile(filename)
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
- assert.Equalf(t, CloudRunMode, cfg.RunMode,
- "unexpected value '%s' for run mode, expected '%s;", cfg.RunMode, CloudRunMode)
+func (s *fileTestSuite) TestNewConfigFromFileRunMode() {
+ s.Equalf(CloudRunMode, s.cfg.RunMode,
+ "unexpected value '%s' for run mode, expected '%s;", s.cfg.RunMode, CloudRunMode)
}
-func TestNewDefaultConfigWithParams(t *testing.T) {
+func (s *fileTestSuite) TestNewDefaultConfigWithParams() {
cfg := NewDefaultConfig()
cfg.WithLogfile("mylogfile.log")
- assert.Equal(t, "mylogfile.log", cfg.LogFile)
+ s.Equal("mylogfile.log", cfg.LogFile)
overrides := FTWTestOverride{
Overrides: test.Overrides{},
Ignore: nil,
@@ -167,9 +207,18 @@ func TestNewDefaultConfigWithParams(t *testing.T) {
ForceFail: nil,
}
cfg.WithOverrides(overrides)
- assert.Equal(t, overrides, cfg.TestOverride)
+ s.Equal(overrides, cfg.TestOverride)
cfg.WithLogMarkerHeaderName("NEW-MARKER-TEST")
- assert.Equal(t, "NEW-MARKER-TEST", cfg.LogMarkerHeaderName)
+ s.Equal("NEW-MARKER-TEST", cfg.LogMarkerHeaderName)
cfg.WithRunMode(CloudRunMode)
- assert.Equal(t, CloudRunMode, cfg.RunMode)
+ s.Equal(CloudRunMode, cfg.RunMode)
+}
+
+func (s *baseTestSuite) TestWithMaxMarker() {
+ cfg := NewDefaultConfig()
+ cfg.WithMaxMarkerRetries(19)
+ s.Equal(19, cfg.MaxMarkerRetries)
+ cfg.WithMaxMarkerLogLines(111)
+ s.Equal(111, cfg.MaxMarkerLogLines)
+
}
diff --git a/config/types.go b/config/types.go
index f8d1dff..435fcee 100644
--- a/config/types.go
+++ b/config/types.go
@@ -52,8 +52,10 @@ type FTWTestOverride struct {
ForceFail map[*FTWRegexp]string `koanf:"forcefail"`
}
+// FTWRegexp is a wrapper around regexp.Regexp that implements the Unmarshaler interface
type FTWRegexp regexp.Regexp
+// UnmarshalText implements the Unmarshaler interface
func (r *FTWRegexp) UnmarshalText(b []byte) error {
re, err := regexp.Compile(string(b))
if err != nil {
@@ -63,6 +65,16 @@ func (r *FTWRegexp) UnmarshalText(b []byte) error {
return nil
}
+// MatchString implements the MatchString method of the regexp.Regexp struct
func (r *FTWRegexp) MatchString(s string) bool {
return (*regexp.Regexp)(r).MatchString(s)
}
+
+// NewFTWRegexp creates a new FTWRegexp from a string
+func NewFTWRegexp(s string) (*FTWRegexp, error) {
+ re, err := regexp.Compile(s)
+ if err != nil {
+ return nil, fmt.Errorf("invalid regexp: %w", err)
+ }
+ return (*FTWRegexp)(re), nil
+}
diff --git a/ftwhttp/client.go b/ftwhttp/client.go
index 413d7ce..9025c54 100644
--- a/ftwhttp/client.go
+++ b/ftwhttp/client.go
@@ -2,6 +2,7 @@ package ftwhttp
import (
"crypto/tls"
+ "crypto/x509"
"fmt"
"net"
"net/http/cookiejar"
@@ -34,6 +35,12 @@ func NewClient(config ClientConfig) (*Client, error) {
return c, nil
}
+// SetRootCAs sets the root CAs for the client.
+// This can be used if you are using internal certificates and for testing purposes.
+func (c *Client) SetRootCAs(cas *x509.CertPool) {
+ c.config.RootCAs = cas
+}
+
// NewConnection creates a new Connection based on a Destination
func (c *Client) NewConnection(d Destination) error {
if c.Transport != nil && c.Transport.connection != nil {
@@ -82,7 +89,15 @@ func (c *Client) dial(d Destination) (net.Conn, error) {
// strings.HasSuffix(err.String(), "connection refused") {
if strings.ToLower(d.Protocol) == "https" {
// Commenting InsecureSkipVerify: true.
- return tls.DialWithDialer(&net.Dialer{Timeout: c.config.ConnectTimeout}, "tcp", hostPort, &tls.Config{MinVersion: tls.VersionTLS12})
+ return tls.DialWithDialer(
+ &net.Dialer{
+ Timeout: c.config.ConnectTimeout,
+ },
+ "tcp", hostPort,
+ &tls.Config{
+ MinVersion: tls.VersionTLS12,
+ RootCAs: c.config.RootCAs,
+ })
}
return net.DialTimeout("tcp", hostPort, c.config.ConnectTimeout)
diff --git a/ftwhttp/client_test.go b/ftwhttp/client_test.go
index 223e7ed..dbce638 100644
--- a/ftwhttp/client_test.go
+++ b/ftwhttp/client_test.go
@@ -1,63 +1,107 @@
package ftwhttp
import (
+ "bytes"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
)
-func TestNewClient(t *testing.T) {
- c, err := NewClient(NewClientConfig())
- assert.NoError(t, err)
+const (
+ secureServer = true
+ insecureServer = false
+)
- assert.NotNil(t, c.Jar, "Error creating Client")
+type clientTestSuite struct {
+ suite.Suite
+ client *Client
+ ts *httptest.Server
}
-func TestConnectDestinationHTTPS(t *testing.T) {
- d := &Destination{
- DestAddr: "example.com",
- Port: 443,
- Protocol: "https",
- }
+func TestClientTestSuite(t *testing.T) {
+ suite.Run(t, new(clientTestSuite))
+}
- c, err := NewClient(NewClientConfig())
- assert.NoError(t, err)
+func (s *clientTestSuite) SetupTest() {
+ var err error
+ s.client, err = NewClient(NewClientConfig())
+ s.NoError(err)
+ s.Nil(s.client.Transport, "Transport not expected to be initialized yet")
+}
- err = c.NewConnection(*d)
- assert.NoError(t, err, "This should not error")
- assert.Equal(t, "https", c.Transport.protocol, "Error connecting to example.com using https")
+func (s *clientTestSuite) TearDownTest() {
+ if s.ts != nil {
+ s.ts.Close()
+ }
}
-func TestDoRequest(t *testing.T) {
- d := &Destination{
- DestAddr: "httpbin.org",
- Port: 443,
- Protocol: "https",
+func (s *clientTestSuite) httpHandler() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/not-found" {
+ w.WriteHeader(http.StatusNotFound)
+ } else {
+ w.WriteHeader(http.StatusOK)
+ }
+ resp := new(bytes.Buffer)
+ for key, value := range r.Header {
+ _, err := fmt.Fprintf(resp, "%s=%s,", key, value)
+ s.NoError(err)
+ }
+
+ _, err := w.Write(resp.Bytes())
+ s.NoError(err)
}
+}
- c, err := NewClient(NewClientConfig())
- assert.NoError(t, err)
+func (s *clientTestSuite) httpTestServer(secure bool) {
+ s.HTTPStatusCode(s.httpHandler(), http.MethodGet, "/", nil, http.StatusOK)
+ s.HTTPStatusCode(s.httpHandler(), http.MethodGet, "/not-found", nil, http.StatusNotFound)
- req := generateBaseRequestForTesting()
+ if secure {
+ s.ts = httptest.NewTLSServer(s.httpHandler())
+ } else {
+ s.ts = httptest.NewServer(s.httpHandler())
+ }
+}
- err = c.NewConnection(*d)
- assert.NoError(t, err, "This should not error")
+func (s *clientTestSuite) TestNewClient() {
+ s.NotNil(s.client.Jar, "Error creating Client")
+}
- _, err = c.Do(*req)
+func (s *clientTestSuite) TestConnectDestinationHTTPS() {
+ s.httpTestServer(secureServer)
+ d, err := DestinationFromString(s.ts.URL)
+ s.NoError(err, "This should not error")
+ s.client.SetRootCAs(s.ts.Client().Transport.(*http.Transport).TLSClientConfig.RootCAs)
+ err = s.client.NewConnection(*d)
+ s.NoError(err, "This should not error")
+ s.Equal("https", s.client.Transport.protocol, "Error connecting to example.com using https")
+}
- assert.Error(t, err, "This should return error")
+func (s *clientTestSuite) TestDoRequest() {
+ s.httpTestServer(secureServer)
+ d, err := DestinationFromString(s.ts.URL)
+ s.NoError(err, "This should not error")
+ s.client.SetRootCAs(s.ts.Client().Transport.(*http.Transport).TLSClientConfig.RootCAs)
+ req := generateBaseRequestForTesting()
+ req.requestLine.URI = "/not-found"
+ err = s.client.NewConnection(*d)
+ s.NoError(err, "This should not error")
+ response, err := s.client.Do(*req)
+ s.NoError(err, "This should error")
+ s.Equal(http.StatusNotFound, response.Parsed.StatusCode, "Error in calling website")
}
-func TestGetTrackedTime(t *testing.T) {
+func (s *clientTestSuite) TestGetTrackedTime() {
d := &Destination{
DestAddr: "httpbingo.org",
Port: 443,
Protocol: "https",
}
- c, err := NewClient(NewClientConfig())
- assert.NoError(t, err)
-
rl := &RequestLine{
Method: "POST",
URI: "/post",
@@ -69,33 +113,28 @@ func TestGetTrackedTime(t *testing.T) {
data := []byte(`test=me&one=two&one=twice`)
req := NewRequest(rl, h, data, true)
- err = c.NewConnection(*d)
- assert.NoError(t, err, "This should not error")
-
- c.StartTrackingTime()
+ err := s.client.NewConnection(*d)
+ s.NoError(err, "This should not error")
- resp, err := c.Do(*req)
+ s.client.StartTrackingTime()
- c.StopTrackingTime()
+ resp, err := s.client.Do(*req)
- assert.NoError(t, err, "This should not error")
+ s.client.StopTrackingTime()
- assert.Equal(t, 200, resp.Parsed.StatusCode, "Error in calling website")
+ s.NoError(err, "This should not error")
+ s.Equal(http.StatusOK, resp.Parsed.StatusCode, "Error in calling website")
- rtt := c.GetRoundTripTime()
-
- assert.GreaterOrEqual(t, int(rtt.RoundTripDuration()), 0, "Error getting RTT")
+ rtt := s.client.GetRoundTripTime()
+ s.GreaterOrEqual(int(rtt.RoundTripDuration()), 0, "Error getting RTT")
}
-func TestClientMultipartFormDataRequest(t *testing.T) {
- d := &Destination{
- DestAddr: "httpbingo.org",
- Port: 443,
- Protocol: "https",
- }
+func (s *clientTestSuite) TestClientMultipartFormDataRequest() {
+ s.httpTestServer(secureServer)
+ d, err := DestinationFromString(s.ts.URL)
+ s.NoError(err, "This should not error")
- c, err := NewClient(NewClientConfig())
- assert.NoError(t, err)
+ s.client.SetRootCAs(s.ts.Client().Transport.(*http.Transport).TLSClientConfig.RootCAs)
rl := &RequestLine{
Method: "POST",
@@ -117,67 +156,55 @@ Some-file-test-here
req := NewRequest(rl, h, data, true)
- err = c.NewConnection(*d)
- assert.NoError(t, err, "This should not error")
+ err = s.client.NewConnection(*d)
+ s.NoError(err, "This should not error")
- c.StartTrackingTime()
+ s.client.StartTrackingTime()
- resp, err := c.Do(*req)
+ resp, err := s.client.Do(*req)
- c.StopTrackingTime()
+ s.client.StopTrackingTime()
- assert.NoError(t, err, "This should not error")
- assert.Equal(t, 200, resp.Parsed.StatusCode, "Error in calling website")
+ s.NoError(err, "This should not error")
+ s.Equal(http.StatusOK, resp.Parsed.StatusCode, "Error in calling website")
}
-func TestNewConnectionCreatesTransport(t *testing.T) {
- c, err := NewClient(NewClientConfig())
- assert.NoError(t, err)
- assert.Nil(t, c.Transport, "Transport not expected to initialized yet")
-
- server := testServer()
- d, err := DestinationFromString(server.URL)
- assert.NoError(t, err, "Failed to construct destination from test server")
-
- err = c.NewConnection(*d)
- assert.NoError(t, err, "Failed to create new connection")
- assert.NotNil(t, c.Transport, "Transport expected to be initialized")
- assert.NotNil(t, c.Transport.connection, "Connection expected to be initialized")
+func (s *clientTestSuite) TestNewConnectionCreatesTransport() {
+ s.httpTestServer(secureServer)
+ d, err := DestinationFromString(s.ts.URL)
+ s.NoError(err, "Failed to construct destination from test server")
+ s.client.SetRootCAs(s.ts.Client().Transport.(*http.Transport).TLSClientConfig.RootCAs)
+ err = s.client.NewConnection(*d)
+ s.NoError(err, "Failed to create new connection")
+ s.NotNil(s.client.Transport, "Transport expected to be initialized")
+ s.NotNil(s.client.Transport.connection, "Connection expected to be initialized")
}
-func TestNewOrReusedConnectionCreatesTransport(t *testing.T) {
- c, err := NewClient(NewClientConfig())
- assert.NoError(t, err)
- assert.Nil(t, c.Transport, "Transport not expected to initialized yet")
-
- server := testServer()
- d, err := DestinationFromString(server.URL)
- assert.NoError(t, err, "Failed to construct destination from test server")
-
- err = c.NewOrReusedConnection(*d)
- assert.NoError(t, err, "Failed to create new or to reuse connection")
- assert.NotNil(t, c.Transport, "Transport expected to be initialized")
- assert.NotNil(t, c.Transport.connection, "Connection expected to be initialized")
+func (s *clientTestSuite) TestNewOrReusedConnectionCreatesTransport() {
+ s.httpTestServer(secureServer)
+ d, err := DestinationFromString(s.ts.URL)
+ s.NoError(err, "Failed to construct destination from test server")
+ s.client.SetRootCAs(s.ts.Client().Transport.(*http.Transport).TLSClientConfig.RootCAs)
+ err = s.client.NewOrReusedConnection(*d)
+ s.NoError(err, "Failed to create new or to reuse connection")
+ s.NotNil(s.client.Transport, "Transport expected to be initialized")
+ s.NotNil(s.client.Transport.connection, "Connection expected to be initialized")
}
-func TestNewOrReusedConnectionReusesTransport(t *testing.T) {
- c, err := NewClient(NewClientConfig())
- assert.NoError(t, err)
- assert.Nil(t, c.Transport, "Transport not expected to initialized yet")
-
- server := testServer()
- d, err := DestinationFromString(server.URL)
- assert.NoError(t, err, "Failed to construct destination from test server")
+func (s *clientTestSuite) TestNewOrReusedConnectionReusesTransport() {
+ s.httpTestServer(insecureServer)
+ d, err := DestinationFromString(s.ts.URL)
+ s.NoError(err, "Failed to construct destination from test server")
- err = c.NewOrReusedConnection(*d)
- assert.NoError(t, err, "Failed to create new or to reuse connection")
- assert.NotNil(t, c.Transport, "Transport expected to be initialized")
- assert.NotNil(t, c.Transport.connection, "Connection expected to be initialized")
+ err = s.client.NewOrReusedConnection(*d)
+ s.NoError(err, "Failed to create new or to reuse connection")
+ s.NotNil(s.client.Transport, "Transport expected to be initialized")
+ s.NotNil(s.client.Transport.connection, "Connection expected to be initialized")
- begin := c.Transport.duration.begin
- err = c.NewOrReusedConnection(*d)
- assert.NoError(t, err, "Failed to reuse connection")
+ begin := s.client.Transport.duration.begin
+ err = s.client.NewOrReusedConnection(*d)
+ s.NoError(err, "Failed to reuse connection")
- assert.Equal(t, begin, c.Transport.duration.begin, "Transport must not be reinitialized when reusing connection")
+ s.Equal(begin, s.client.Transport.duration.begin, "Transport must not be reinitialized when reusing connection")
}
diff --git a/ftwhttp/connection_test.go b/ftwhttp/connection_test.go
index b5d25ce..6334ecb 100644
--- a/ftwhttp/connection_test.go
+++ b/ftwhttp/connection_test.go
@@ -3,13 +3,26 @@ package ftwhttp
import (
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
)
-func TestDestinationFromString(t *testing.T) {
+type connectionTestSuite struct {
+ suite.Suite
+}
+
+func TestConnectionTestSuite(t *testing.T) {
+ suite.Run(t, new(connectionTestSuite))
+}
+func (s *connectionTestSuite) TestDestinationFromString() {
+ d, err := DestinationFromString("http://example.com:80")
+ s.NoError(err, "This should not error")
+ s.Equal("example.com", d.DestAddr, "Error parsing destination")
+ s.Equal(80, d.Port, "Error parsing destination")
+ s.Equal("http", d.Protocol, "Error parsing destination")
}
-func TestMultipleRequestTypes(t *testing.T) {
+
+func (s *connectionTestSuite) TestMultipleRequestTypes() {
var req *Request
rl := &RequestLine{
@@ -23,5 +36,5 @@ func TestMultipleRequestTypes(t *testing.T) {
data := []byte(`test=me&one=two`)
req = NewRequest(rl, h, data, true)
- assert.True(t, req.WithAutoCompleteHeaders(), "Set Autocomplete headers error ")
+ s.True(req.WithAutoCompleteHeaders(), "Set Autocomplete headers error ")
}
diff --git a/ftwhttp/header.go b/ftwhttp/header.go
index ccd7ea3..2f19e46 100644
--- a/ftwhttp/header.go
+++ b/ftwhttp/header.go
@@ -4,6 +4,8 @@ import (
"bytes"
"io"
"sort"
+
+ "github.com/rs/zerolog/log"
)
const (
@@ -91,18 +93,21 @@ func (h Header) Write(w io.Writer) error {
}
// WriteBytes writes a header in a ByteWriter.
-func (h Header) WriteBytes(b *bytes.Buffer) error {
+func (h Header) WriteBytes(b *bytes.Buffer) (int, error) {
sorted := h.getSortedHeadersByName()
-
+ count := 0
for _, key := range sorted {
// we want all headers "as-is"
s := key + ": " + h[key] + "\r\n"
- if _, err := b.Write([]byte(s)); err != nil {
- return err
+ log.Info().Msgf("Writing header: %s", s)
+ n, err := b.Write([]byte(s))
+ count += n
+ if err != nil {
+ return count, err
}
}
- return nil
+ return count, nil
}
diff --git a/ftwhttp/header_test.go b/ftwhttp/header_test.go
index 5869e5b..291c56b 100644
--- a/ftwhttp/header_test.go
+++ b/ftwhttp/header_test.go
@@ -10,10 +10,11 @@ package ftwhttp
import (
"bytes"
+ "errors"
"io"
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
)
var headerWriteTests = []struct {
@@ -50,31 +51,82 @@ var headerWriteTests = []struct {
},
}
-func TestHeaderWriteBytes(t *testing.T) {
- var buf bytes.Buffer
+type BadWriter struct {
+ err error
+}
+
+func (bw BadWriter) Write(_ []byte) (n int, err error) {
+ return 0, bw.err
+}
+
+type headerTestSuite struct {
+ suite.Suite
+}
+
+func TestHeaderTestSuite(t *testing.T) {
+ suite.Run(t, new(headerTestSuite))
+}
+
+func (s *headerTestSuite) TestHeaderWrite() {
+ for _, test := range headerWriteTests {
+ err := test.h.Write(io.Discard)
+ s.NoError(err)
+ err = test.h.Write(BadWriter{err: errors.New("fake error")})
+ if len(test.h) > 0 {
+ s.EqualErrorf(err, "fake error", "Write: got %v, want %v", err, "fake error")
+ } else {
+ s.NoErrorf(err, "Write: got %v", err)
+ }
+ }
+}
+
+func (s *headerTestSuite) TestHeaderWriteBytes() {
for i, test := range headerWriteTests {
- _ = test.h.WriteBytes(&buf)
- assert.Equalf(t, test.expected, buf.String(), "#%d:\n got: %q\nwant: %q", i, buf.String(), test.expected)
+ var buf bytes.Buffer
+
+ n, err := test.h.WriteBytes(&buf)
+ w := buf.String()
+ s.Lenf(w, n, "#%d: WriteBytes: got %d, want %d", i, n, len(w))
+ s.NoErrorf(err, "#%d: WriteBytes: got %v", i, err)
+ s.Equalf(test.expected, w, "#%d: WriteBytes: got %q, want %q", i, w, test.expected)
buf.Reset()
}
}
-func TestHeaderWrite(t *testing.T) {
- for _, test := range headerWriteTests {
- _ = test.h.Write(io.Discard)
+func (s *headerTestSuite) TestHeaderWriteString() {
+ sw := stringWriter{io.Discard}
+
+ for i, test := range headerWriteTests {
+ expected := test.h.Get("Content-Type")
+ n, err := sw.WriteString(expected)
+ s.NoErrorf(err, "#%d: WriteString: %v", i, err)
+ s.Equalf(len(expected), n, "#%d: WriteString: got %d, want %d", i, n, len(expected))
}
}
-func TestHeaderSetGet(t *testing.T) {
+func (s *headerTestSuite) TestHeaderSetGet() {
h := Header{
"Custom": "Value",
}
h.Add("Other", "Value")
value := h.Get("Other")
- assert.Equalf(t, "Value", value, "got: %s, want: %s\n", value, "Value")
+ s.Equalf("Value", value, "got: %s, want: %s\n", value, "Value")
+}
+
+func (s *headerTestSuite) TestHeaderDel() {
+ for i, test := range headerWriteTests {
+ // we clone it because we are modifying the original
+ headerCopy := test.h.Clone()
+ expected := headerCopy.Get("Content-Type")
+ if expected != "" {
+ headerCopy.Del("Content-Type")
+ value := headerCopy.Get("Content-Type")
+ s.Equalf("", value, "#%d: got: %s, want: %s\n", i, value, "")
+ }
+ }
}
-func TestHeaderClone(t *testing.T) {
+func (s *headerTestSuite) TestHeaderClone() {
h := Header{
"Custom": "Value",
}
@@ -83,7 +135,7 @@ func TestHeaderClone(t *testing.T) {
value := clone.Get("Custom")
- assert.Equalf(t, "Value", value, "got: %s, want: %s\n", value, "Value")
+ s.Equalf("Value", value, "got: %s, want: %s\n", value, "Value")
}
diff --git a/ftwhttp/request.go b/ftwhttp/request.go
index 9e3d9bf..69ab888 100644
--- a/ftwhttp/request.go
+++ b/ftwhttp/request.go
@@ -170,25 +170,32 @@ func buildRequest(r *Request) ([]byte, error) {
r.AddStandardHeaders()
}
- err = r.Headers().WriteBytes(&b)
+ _, err := r.Headers().WriteBytes(&b)
if err != nil {
log.Debug().Msgf("ftw/http: error writing to buffer: %s", err.Error())
return nil, err
}
// TODO: handle cookies
- // if c.Jar != nil {
- // for _, cookie := range c.Jar.Cookies(req.URL) {
+ // if client.Jar != nil {
+ // for _, cookie := range client.Jar.Cookies(req.URL) {
// req.AddCookie(cookie)
// }
// }
// After headers, we need one blank line
_, err = fmt.Fprintf(&b, "\r\n")
-
+ if err != nil {
+ log.Debug().Msgf("ftw/http: error writing to buffer: %s", err.Error())
+ return nil, err
+ }
// Now the body, if anything
if utils.IsNotEmpty(r.data) {
_, err = fmt.Fprintf(&b, "%s", r.data)
+ if err != nil {
+ log.Debug().Msgf("ftw/http: error writing to buffer: %s", err.Error())
+ return nil, err
+ }
}
} else {
dumpRawData(&b, r.raw)
diff --git a/ftwhttp/request_test.go b/ftwhttp/request_test.go
index 85f2245..dc70513 100644
--- a/ftwhttp/request_test.go
+++ b/ftwhttp/request_test.go
@@ -4,90 +4,98 @@ import (
"errors"
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
)
+type requestTestSuite struct {
+ suite.Suite
+}
+
+func TestRequestTestSuite(t *testing.T) {
+ suite.Run(t, new(requestTestSuite))
+}
+
func generateBaseRequestForTesting() *Request {
var req *Request
rl := &RequestLine{
Method: "UNEXISTENT",
URI: "/this/path",
- Version: "1.4",
+ Version: "HTTP/1.4",
}
- h := Header{"This": "Header", "Connection": "Not-Closed"}
+ h := Header{"Host": "localhost", "This": "Header", "Connection": "Not-Closed"}
req = NewRequest(rl, h, []byte("Data"), true)
return req
}
-func TestAddStandardHeadersWhenConnectionHeaderIsPresent(t *testing.T) {
+func (s *requestTestSuite) TestAddStandardHeadersWhenConnectionHeaderIsPresent() {
req := NewRequest(&RequestLine{}, Header{"Connection": "Not-Closed"}, []byte("Data"), true)
req.AddStandardHeaders()
- assert.Equal(t, req.headers.Get("Connection"), "Not-Closed")
+ s.Equal(req.headers.Get("Connection"), "Not-Closed")
}
-func TestAddStandardHeadersWhenConnectionHeaderIsEmpty(t *testing.T) {
+func (s *requestTestSuite) TestAddStandardHeadersWhenConnectionHeaderIsEmpty() {
req := NewRequest(&RequestLine{}, Header{}, []byte("Data"), true)
req.AddStandardHeaders()
- assert.Equal(t, req.headers.Get("Connection"), "close")
+ s.Equal(req.headers.Get("Connection"), "close")
}
-func TestAddStandardHeadersWhenNoData(t *testing.T) {
+func (s *requestTestSuite) TestAddStandardHeadersWhenNoData() {
req := NewRequest(&RequestLine{Method: "GET"}, Header{}, []byte(""), true)
req.AddStandardHeaders()
- assert.Equal(t, req.headers.Get("Content-Length"), "")
+ s.Equal(req.headers.Get("Content-Length"), "")
}
-func TestAddStandardHeadersWhenGetMethod(t *testing.T) {
+func (s *requestTestSuite) TestAddStandardHeadersWhenGetMethod() {
req := NewRequest(&RequestLine{Method: "GET"}, Header{}, []byte("Data"), true)
req.AddStandardHeaders()
- assert.Equal(t, req.headers.Get("Content-Length"), "4")
+ s.Equal(req.headers.Get("Content-Length"), "4")
}
-func TestAddStandardHeadersWhenPostMethod(t *testing.T) {
+func (s *requestTestSuite) TestAddStandardHeadersWhenPostMethod() {
req := NewRequest(&RequestLine{Method: "POST"}, Header{}, []byte("Data"), true)
req.AddStandardHeaders()
- assert.Equal(t, req.headers.Get("Content-Length"), "4")
+ s.Equal(req.headers.Get("Content-Length"), "4")
}
-func TestAddStandardHeadersWhenPutMethod(t *testing.T) {
+func (s *requestTestSuite) TestAddStandardHeadersWhenPutMethod() {
req := NewRequest(&RequestLine{Method: "PUT"}, Header{}, []byte("Data"), true)
req.AddStandardHeaders()
- assert.Equal(t, req.headers.Get("Content-Length"), "4")
+ s.Equal(req.headers.Get("Content-Length"), "4")
}
-func TestAddStandardHeadersWhenPatchMethod(t *testing.T) {
+func (s *requestTestSuite) TestAddStandardHeadersWhenPatchMethod() {
req := NewRequest(&RequestLine{Method: "PATCH"}, Header{}, []byte("Data"), true)
req.AddStandardHeaders()
- assert.Equal(t, req.headers.Get("Content-Length"), "4")
+ s.Equal(req.headers.Get("Content-Length"), "4")
}
-func TestAddStandardHeadersWhenDeleteMethod(t *testing.T) {
+func (s *requestTestSuite) TestAddStandardHeadersWhenDeleteMethod() {
req := NewRequest(&RequestLine{Method: "DELETE"}, Header{}, []byte("Data"), true)
req.AddStandardHeaders()
- assert.Equal(t, req.headers.Get("Content-Length"), "4")
+ s.Equal(req.headers.Get("Content-Length"), "4")
}
-func TestMultipartFormDataRequest(t *testing.T) {
+func (s *requestTestSuite) TestMultipartFormDataRequest() {
var req *Request
rl := &RequestLine{
@@ -109,7 +117,7 @@ Some-file-test-here
----------397236876--`)
req = NewRequest(rl, h, data, true)
- assert.False(t, req.isRaw())
+ s.False(req.isRaw())
}
func generateBaseRawRequestForTesting() *Request {
@@ -127,7 +135,7 @@ User-Agent: ModSecurity CRS 3 Tests
return req
}
-func TestGenerateBaseRawRequestForTesting(t *testing.T) {
+func (s *requestTestSuite) TestGenerateBaseRawRequestForTesting() {
var req *Request
raw := []byte(`POST / HTTP/1.1
@@ -139,41 +147,41 @@ User-Agent: ModSecurity CRS 3 Tests
`)
req = NewRawRequest(raw, false)
- assert.False(t, req.autoCompleteHeaders)
+ s.False(req.autoCompleteHeaders)
}
-func TestRequestLine(t *testing.T) {
+func (s *requestTestSuite) TestRequestLine() {
rl := &RequestLine{
Method: "UNEXISTENT",
URI: "/this/path",
Version: "1.4",
}
- s := rl.ToString()
+ str := rl.ToString()
- assert.Equal(t, "UNEXISTENT /this/path 1.4\r\n", s)
+ s.Equal("UNEXISTENT /this/path 1.4\r\n", str)
}
-func TestDestination(t *testing.T) {
+func (s *requestTestSuite) TestDestination() {
d := &Destination{
DestAddr: "192.168.1.1",
Port: 443,
Protocol: "https",
}
- assert.Equal(t, "192.168.1.1", d.DestAddr)
- assert.Equal(t, 443, d.Port)
- assert.Equal(t, "https", d.Protocol)
+ s.Equal("192.168.1.1", d.DestAddr)
+ s.Equal(443, d.Port)
+ s.Equal("https", d.Protocol)
}
-func TestRequestNew(t *testing.T) {
+func (s *requestTestSuite) TestRequestNew() {
req := generateBaseRequestForTesting()
head := req.Headers()
- assert.Equal(t, "Header", head.Get("This"))
- assert.Equal(t, []byte("Data"), req.Data(), "Failed to set data")
+ s.Equal("Header", head.Get("This"))
+ s.Equal([]byte("Data"), req.Data(), "Failed to set data")
}
-func TestWithAutocompleteRequest(t *testing.T) {
+func (s *requestTestSuite) TestWithAutocompleteRequest() {
var req *Request
rl := &RequestLine{
@@ -187,10 +195,10 @@ func TestWithAutocompleteRequest(t *testing.T) {
data := []byte(`test=me&one=two`)
req = NewRequest(rl, h, data, true)
- assert.True(t, req.WithAutoCompleteHeaders(), "Set Autocomplete headers error ")
+ s.True(req.WithAutoCompleteHeaders(), "Set Autocomplete headers error ")
}
-func TestWithoutAutocompleteRequest(t *testing.T) {
+func (s *requestTestSuite) TestWithoutAutocompleteRequest() {
var req *Request
rl := &RequestLine{
@@ -204,92 +212,87 @@ func TestWithoutAutocompleteRequest(t *testing.T) {
data := []byte(`test=me&one=two`)
req = NewRequest(rl, h, data, false)
- assert.False(t, req.WithAutoCompleteHeaders(), "Set Autocomplete headers error ")
+ s.False(req.WithAutoCompleteHeaders(), "Set Autocomplete headers error ")
}
-func TestRequestHeadersSet(t *testing.T) {
+func (s *requestTestSuite) TestRequestHeadersSet() {
req := generateBaseRequestForTesting()
newH := Header{"X-New-Header": "Value"}
req.SetHeaders(newH)
- if req.headers.Get("X-New-Header") == "Value" {
- t.Logf("Success !")
- } else {
- t.Errorf("Failed !")
- }
-
+ s.Equal("Value", req.headers.Get("X-New-Header"), "Failed to set headers")
req.AddHeader("X-New-Header2", "Value")
head := req.Headers()
- assert.Equal(t, "Value", head.Get("X-New-Header2"))
+ s.Equal("Value", head.Get("X-New-Header2"))
}
-func TestRequestAutoCompleteHeaders(t *testing.T) {
+func (s *requestTestSuite) TestRequestAutoCompleteHeaders() {
req := generateBaseRequestForTesting()
req.SetAutoCompleteHeaders(true)
- assert.True(t, req.WithAutoCompleteHeaders(), "Set Autocomplete headers error ")
+ s.True(req.WithAutoCompleteHeaders(), "Set Autocomplete headers error ")
}
-func TestRequestData(t *testing.T) {
+func (s *requestTestSuite) TestRequestData() {
req := generateBaseRequestForTesting()
err := req.SetData([]byte("This is the data now"))
- assert.NoError(t, err)
- assert.Equal(t, []byte("This is the data now"), req.Data(), "failed to set data")
+ s.NoError(err)
+ s.Equal([]byte("This is the data now"), req.Data(), "failed to set data")
}
-func TestRequestSettingRawDataWhenThereIsData(t *testing.T) {
+func (s *requestTestSuite) TestRequestSettingRawDataWhenThereIsData() {
req := generateBaseRequestForTesting()
err := req.SetRawData([]byte("This is the data now"))
expectedError := errors.New("ftw/http: data field is already present in this request")
- assert.Error(t, err)
- assert.Equal(t, expectedError, err)
+ s.Error(err)
+ s.Equal(expectedError, err)
}
-func TestRequestRawData(t *testing.T) {
+func (s *requestTestSuite) TestRequestRawData() {
req := generateBaseRawRequestForTesting()
err := req.SetRawData([]byte("This is the RAW data now"))
- assert.NoError(t, err)
+ s.NoError(err)
- assert.Equal(t, []byte("This is the RAW data now"), req.RawData())
+ s.Equal([]byte("This is the RAW data now"), req.RawData())
}
-func TestRequestSettingDataaWhenThereIsRawData(t *testing.T) {
+func (s *requestTestSuite) TestRequestSettingDataaWhenThereIsRawData() {
req := generateBaseRawRequestForTesting()
err := req.SetData([]byte("This is the data now"))
expectedError := errors.New("ftw/http: raw field is already present in this request")
- assert.Error(t, err)
- assert.Equal(t, expectedError, err)
+ s.Error(err)
+ s.Equal(expectedError, err)
}
-func TestRequestURLParse(t *testing.T) {
+func (s *requestTestSuite) TestRequestURLParse() {
req := generateBaseRequestForTesting()
h := req.Headers()
h.Add(ContentTypeHeader, "application/x-www-form-urlencoded")
// Test adding semicolons to test parse
err := req.SetData([]byte("test=This&test=nothing"))
- assert.NoError(t, err)
+ s.NoError(err)
}
-func TestRequestURLParseFail(t *testing.T) {
+func (s *requestTestSuite) TestRequestURLParseFail() {
req := generateBaseRequestForTesting()
h := req.Headers()
h.Add(ContentTypeHeader, "application/x-www-form-urlencoded")
// Test adding semicolons to test parse
err := req.SetData([]byte("test=This&that=but with;;;;;; data now"))
- assert.NoError(t, err)
+ s.NoError(err)
}
-func TestRequestEncodesPostData(t *testing.T) {
+func (s *requestTestSuite) TestRequestEncodesPostData() {
tests := []struct {
raw string
encoded string
@@ -312,8 +315,8 @@ func TestRequestEncodesPostData(t *testing.T) {
},
{
// Test adding semicolons to test parse
- raw: `c4= ;c3=t;c2=a;c1=c;a1=/;a2=e;a3=t;a4=c;a5=/;a6=p;a7=a;a8=s;a9=s;a10=w;a11=d;$c1$c2$c3$c4$a1$a2$a3$a4$a5$a6$a7$a8$a9$a10$a11`,
- encoded: "c4=+%3Bc3%3Dt%3Bc2%3Da%3Bc1%3Dc%3Ba1%3D%2F%3Ba2%3De%3Ba3%3Dt%3Ba4%3Dc%3Ba5%3D%2F%3Ba6%3Dp%3Ba7%3Da%3Ba8%3Ds%3Ba9%3Ds%3Ba10%3Dw%3Ba11%3Dd%3B%24c1%24c2%24c3%24c4%24a1%24a2%24a3%24a4%24a5%24a6%24a7%24a8%24a9%24a10%24a11",
+ raw: `c4= ;c3=t;c2=a;c1=client;a1=/;a2=e;a3=t;a4=client;a5=/;a6=p;a7=a;a8=s;a9=s;a10=w;a11=d;$c1$c2$c3$c4$a1$a2$a3$a4$a5$a6$a7$a8$a9$a10$a11`,
+ encoded: `c4=+%3Bc3%3Dt%3Bc2%3Da%3Bc1%3Dclient%3Ba1%3D%2F%3Ba2%3De%3Ba3%3Dt%3Ba4%3Dclient%3Ba5%3D%2F%3Ba6%3Dp%3Ba7%3Da%3Ba8%3Ds%3Ba9%3Ds%3Ba10%3Dw%3Ba11%3Dd%3B%24c1%24c2%24c3%24c4%24a1%24a2%24a3%24a4%24a5%24a6%24a7%24a8%24a9%24a10%24a11`,
},
{
// Already encoded
@@ -324,25 +327,19 @@ func TestRequestEncodesPostData(t *testing.T) {
for _, tc := range tests {
tt := tc
- t.Run(tt.raw, func(t *testing.T) {
+ s.Run(tt.raw, func() {
req := generateBaseRequestForTesting()
h := req.Headers()
h.Add(ContentTypeHeader, "application/x-www-form-urlencoded")
err := req.SetData([]byte(tt.raw))
- if err != nil {
- t.Errorf("Failed !")
- }
+ s.NoError(err)
result, err := encodeDataParameters(h, req.Data())
- if err != nil {
- t.Errorf("Failed to encode %s", req.Data())
- }
+ s.NoError(err, "Failed to encode %s", req.Data())
expected := tt.encoded
actual := string(result)
- if actual != expected {
- t.Errorf("Unexpected URL encoded payload, expected %s, got %s", expected, actual)
- }
+ s.Equal(expected, actual, "Unexpected URL encoded payload")
})
}
}
diff --git a/ftwhttp/response_test.go b/ftwhttp/response_test.go
index 1269ac7..81f6c50 100644
--- a/ftwhttp/response_test.go
+++ b/ftwhttp/response_test.go
@@ -8,9 +8,19 @@ import (
"testing"
"time"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
)
+type responseTestSuite struct {
+ suite.Suite
+ client *Client
+ ts *httptest.Server
+}
+
+func TestHResponseTestSuite(t *testing.T) {
+ suite.Run(t, new(responseTestSuite))
+}
+
func generateRequestForTesting(keepalive bool) *Request {
var req *Request
var connection string
@@ -58,114 +68,103 @@ func generateRequestWithCookiesForTesting() *Request {
return req
}
-// Error checking omitted for brevity
-func testServer() (server *httptest.Server) {
-
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, "Hello, client")
- }))
-
- return ts
+func (s *responseTestSuite) helloClient(w http.ResponseWriter, r *http.Request) {
+ n, err := fmt.Fprintln(w, "Hello, client")
+ s.NoError(err)
+ s.Equal(14, n)
}
-// Error checking omitted for brevity
-func testEchoServer(t *testing.T) (server *httptest.Server) {
-
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("X-Powered-By", "go-ftw")
- w.WriteHeader(http.StatusOK)
- resp := new(bytes.Buffer)
- for key, value := range r.Header {
- _, err := fmt.Fprintf(resp, "%s=%s,", key, value)
- assert.NoError(t, err)
- }
-
- _, err := w.Write(resp.Bytes())
- assert.NoError(t, err)
- }))
-
- return ts
+func (s *responseTestSuite) testEchoServer(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("X-Powered-By", "go-ftw")
+ w.WriteHeader(http.StatusOK)
+ resp := new(bytes.Buffer)
+ for key, value := range r.Header {
+ _, err := fmt.Fprintf(resp, "%s=%s,", key, value)
+ s.NoError(err)
+ }
+ _, err := w.Write(resp.Bytes())
+ s.NoError(err)
}
-func testServerWithCookies() (server *httptest.Server) {
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- expiration := time.Now().Add(365 * 24 * time.Hour)
- cookie := http.Cookie{Name: "username", Value: "go-ftw", Expires: expiration}
- http.SetCookie(w, &cookie)
- fmt.Fprintln(w, "Setting Cookies!")
- }))
+func (s *responseTestSuite) responseWithCookies(w http.ResponseWriter, r *http.Request) {
+ expiration := time.Now().Add(365 * 24 * time.Hour)
+ cookie := http.Cookie{Name: "username", Value: "go-ftw", Expires: expiration}
+ http.SetCookie(w, &cookie)
+ n, err := fmt.Fprintln(w, "Setting Cookies!")
+ s.NoError(err)
+ s.Equal(17, n)
+}
- return ts
+func (s *responseTestSuite) SetupTest() {
+ var err error
+ s.client, err = NewClient(NewClientConfig())
+ s.NoError(err)
}
-func TestResponse(t *testing.T) {
- server := testServer()
+func (s *responseTestSuite) TearDownTest() {
+ s.ts.Close()
+}
- defer server.Close()
+func (s *responseTestSuite) BeforeTest(_, testName string) {
+ var f http.HandlerFunc
+ switch testName {
+ case "TestResponse":
+ f = s.helloClient
+ case "TestResponseWithCookies":
+ f = s.responseWithCookies
+ case "TestResponseChecksFullResponse":
+ f = s.testEchoServer
+ default:
+ f = s.testEchoServer
+ }
+ s.ts = httptest.NewServer(f)
+}
- d, err := DestinationFromString(server.URL)
- assert.NoError(t, err)
+func (s *responseTestSuite) TestResponse() {
+ d, err := DestinationFromString(s.ts.URL)
+ s.NoError(err)
req := generateRequestForTesting(true)
- client, err := NewClient(NewClientConfig())
- assert.NoError(t, err)
- err = client.NewConnection(*d)
- assert.NoError(t, err)
+ err = s.client.NewConnection(*d)
+ s.NoError(err)
- response, err := client.Do(*req)
- assert.NoError(t, err)
+ response, err := s.client.Do(*req)
+ s.NoError(err)
- assert.Contains(t, response.GetFullResponse(), "Hello, client\n")
+ s.Contains(response.GetFullResponse(), "Hello, client\n")
}
-func TestResponseWithCookies(t *testing.T) {
- server := testServerWithCookies()
-
- defer server.Close()
-
- d, err := DestinationFromString(server.URL)
- assert.NoError(t, err)
+func (s *responseTestSuite) TestResponseWithCookies() {
+ d, err := DestinationFromString(s.ts.URL)
+ s.NoError(err)
req := generateRequestForTesting(true)
- client, err := NewClient(NewClientConfig())
- assert.NoError(t, err)
- err = client.NewConnection(*d)
-
- assert.NoError(t, err)
-
- response, err := client.Do(*req)
+ err = s.client.NewConnection(*d)
+ s.NoError(err)
- assert.NoError(t, err)
+ response, err := s.client.Do(*req)
+ s.NoError(err)
- assert.Contains(t, response.GetFullResponse(), "Setting Cookies!\n")
+ s.Contains(response.GetFullResponse(), "Setting Cookies!\n")
cookiereq := generateRequestWithCookiesForTesting()
- _, err = client.Do(*cookiereq)
-
- assert.NoError(t, err)
+ _, err = s.client.Do(*cookiereq)
+ s.NoError(err)
}
-func TestResponseChecksFullResponse(t *testing.T) {
- server := testEchoServer(t)
-
- defer server.Close()
-
- d, err := DestinationFromString(server.URL)
- assert.NoError(t, err)
+func (s *responseTestSuite) TestResponseChecksFullResponse() {
+ d, err := DestinationFromString(s.ts.URL)
+ s.NoError(err)
req := generateRequestForTesting(true)
- client, err := NewClient(NewClientConfig())
- assert.NoError(t, err)
- err = client.NewConnection(*d)
-
- assert.NoError(t, err)
-
- response, err := client.Do(*req)
+ err = s.client.NewConnection(*d)
+ s.NoError(err)
- assert.NoError(t, err)
+ response, err := s.client.Do(*req)
+ s.NoError(err)
- assert.Contains(t, response.GetFullResponse(), "X-Powered-By: go-ftw")
- assert.Contains(t, response.GetFullResponse(), "User-Agent=[Go Tests]")
+ s.Contains(response.GetFullResponse(), "X-Powered-By: go-ftw")
+ s.Contains(response.GetFullResponse(), "User-Agent=[Go Tests]")
}
diff --git a/ftwhttp/types.go b/ftwhttp/types.go
index de29c47..7682927 100644
--- a/ftwhttp/types.go
+++ b/ftwhttp/types.go
@@ -1,6 +1,7 @@
package ftwhttp
import (
+ "crypto/x509"
"net"
"net/http"
"time"
@@ -12,6 +13,8 @@ type ClientConfig struct {
ConnectTimeout time.Duration
// ReadTimeout is the timeout for reading a response.
ReadTimeout time.Duration
+ // RootCAs is the set of root CA certificates that is used to verify server
+ RootCAs *x509.CertPool
}
// Client is the top level abstraction in http
diff --git a/main.go b/main.go
index 9313e84..1a30281 100644
--- a/main.go
+++ b/main.go
@@ -3,6 +3,8 @@
package main
import (
+ "context"
+ "errors"
"fmt"
"os"
_ "time/tzdata"
@@ -28,10 +30,15 @@ func main() {
// Default level for this example is info, unless debug flag is present
zerolog.SetGlobalLevel(zerolog.InfoLevel)
- cmd.Execute(
+ err := cmd.Execute(
buildVersion(version, commit, date, builtBy),
)
+ if errors.Is(err, context.DeadlineExceeded) {
+ os.Exit(2)
+ } else if err != nil {
+ os.Exit(1)
+ }
}
func buildVersion(version, commit, date, builtBy string) string {
diff --git a/output/output_test.go b/output/output_test.go
index bded3c2..a3c9415 100644
--- a/output/output_test.go
+++ b/output/output_test.go
@@ -3,6 +3,8 @@ package output
import (
"bytes"
"testing"
+
+ "github.com/stretchr/testify/suite"
)
var testString = "test"
@@ -25,41 +27,44 @@ var outputTest = []struct {
{"json", `{"level":"notice","message":"This is the test"}`},
}
-func TestOutput(t *testing.T) {
+type outputTestSuite struct {
+ suite.Suite
+}
+
+func TestOutputTestSuite(t *testing.T) {
+ suite.Run(t, new(outputTestSuite))
+}
+
+func (s *outputTestSuite) TestOutput() {
var b bytes.Buffer
for i, test := range outputTest {
o := NewOutput(test.oType, &b)
- if err := o.Printf(format, testString); err != nil {
- t.Fatalf("Error! in test %d", i)
- }
+ err := o.Printf(format, testString)
+ s.NoError(err, "Error! in test %d", i)
}
}
-func TestNormalCatalogOutput(t *testing.T) {
+func (s *outputTestSuite) TestNormalCatalogOutput() {
var b bytes.Buffer
normal := NewOutput("normal", &b)
for _, v := range normalCatalog {
normal.RawPrint(v)
- if b.String() != v {
- t.Error("output is not equal")
- }
+ s.Equal(b.String(), v, "output is not equal")
// reset buffer
b.Reset()
}
}
-func TestPlainCatalogOutput(t *testing.T) {
+func (s *outputTestSuite) TestPlainCatalogOutput() {
var b bytes.Buffer
normal := NewOutput("normal", &b)
for _, v := range createPlainCatalog(normalCatalog) {
normal.RawPrint(v)
- if b.String() != v {
- t.Error("plain output is not equal")
- }
+ s.Equal(b.String(), v, "output is not equal")
// reset buffer
b.Reset()
}
diff --git a/runner/run.go b/runner/run.go
index 6895c80..27a714f 100644
--- a/runner/run.go
+++ b/runner/run.go
@@ -93,7 +93,10 @@ func RunTest(runContext *TestRunContext, ftwTest test.FTWTest) error {
}
// Iterate over stages
for _, stage := range testCase.Stages {
- ftwCheck := check.NewCheck(runContext.Config)
+ ftwCheck, err := check.NewCheck(runContext.Config)
+ if err != nil {
+ return err
+ }
if err := RunStage(runContext, ftwCheck, testCase, stage.Stage); err != nil {
return err
}
diff --git a/runner/run_cloud_test.go b/runner/run_cloud_test.go
new file mode 100644
index 0000000..1fd3d5e
--- /dev/null
+++ b/runner/run_cloud_test.go
@@ -0,0 +1,112 @@
+package runner
+
+import (
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "strconv"
+ "testing"
+ "text/template"
+
+ "github.com/coreruleset/go-ftw/config"
+ "github.com/coreruleset/go-ftw/ftwhttp"
+ "github.com/coreruleset/go-ftw/output"
+ "github.com/coreruleset/go-ftw/test"
+ "github.com/rs/zerolog/log"
+ "github.com/stretchr/testify/suite"
+)
+
+type runCloudTestSuite struct {
+ suite.Suite
+ cfg *config.FTWConfiguration
+ ftwTests []test.FTWTest
+ out *output.Output
+ ts *httptest.Server
+ dest *ftwhttp.Destination
+ tempFileName string
+}
+
+func TestRunCloudTestSuite(t *testing.T) {
+ suite.Run(t, new(runCloudTestSuite))
+}
+
+func (s *runCloudTestSuite) SetupTest() {
+ s.newTestCloudServer()
+ s.out = output.NewOutput("normal", os.Stdout)
+}
+
+func (s *runCloudTestSuite) TearDownTest() {
+ s.ts.Close()
+ if s.tempFileName != "" {
+ err := os.Remove(s.tempFileName)
+ s.NoError(err, "cannot remove test file")
+ s.tempFileName = ""
+ }
+}
+
+func (s *runCloudTestSuite) BeforeTest(_ string, name string) {
+ var err error
+
+ // if we have a destination for this test, use it
+ // else use the default destination
+ if s.dest == nil {
+ s.dest, err = ftwhttp.DestinationFromString(destinationMap[name])
+ s.NoError(err)
+ }
+
+ log.Info().Msgf("Using port %d and addr '%s'", s.dest.Port, s.dest.DestAddr)
+
+ // set up variables for template
+ vars := map[string]interface{}{
+ "TestPort": s.dest.Port,
+ "TestAddr": s.dest.DestAddr,
+ }
+
+ s.cfg = config.NewCloudConfig()
+ // get tests template from file
+ tmpl, err := template.ParseFiles(fmt.Sprintf("testdata/%s.yaml", name))
+ s.NoError(err)
+ // create a temporary file to hold the test
+ testFileContents, err := os.CreateTemp("testdata", "mock-test-*.yaml")
+ s.NoError(err, "cannot create temporary file")
+ err = tmpl.Execute(testFileContents, vars)
+ s.NoError(err, "cannot execute template")
+ // get tests from file
+ s.ftwTests, err = test.GetTestsFromFiles(testFileContents.Name())
+ s.NoError(err, "cannot get tests from file")
+ // save the name of the temporary file so we can delete it later
+ s.tempFileName = testFileContents.Name()
+}
+
+// Error checking omitted for brevity
+func (s *runCloudTestSuite) newTestCloudServer() {
+ var err error
+
+ s.ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ statusCode := http.StatusOK
+ if r.URL.Path != "/" {
+ statusCode, err = strconv.Atoi(r.URL.Path[1:])
+ if err != nil {
+ statusCode = http.StatusBadRequest
+ }
+ log.Debug().Msgf("Mock cloud server returning status code: %d", statusCode)
+ }
+ w.WriteHeader(statusCode)
+ _, _ = w.Write([]byte("Hello, client"))
+ }))
+
+ s.dest, err = ftwhttp.DestinationFromString((s.ts).URL)
+ s.Require().NoError(err, "cannot get destination from string")
+}
+
+func (s *runCloudTestSuite) TestCloudRun() {
+ s.Run("don't show time and execute all", func() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
+ ShowTime: true,
+ Output: output.Quiet,
+ }, s.out)
+ s.NoError(err)
+ s.Equalf(res.Stats.TotalFailed(), 0, "Oops, %d tests failed to run!", res.Stats.TotalFailed())
+ })
+}
diff --git a/runner/run_input_override_test.go b/runner/run_input_override_test.go
new file mode 100644
index 0000000..5363725
--- /dev/null
+++ b/runner/run_input_override_test.go
@@ -0,0 +1,319 @@
+package runner
+
+import (
+ "bytes"
+ "errors"
+ "runtime"
+ "strconv"
+ "strings"
+ "testing"
+ "text/template"
+
+ "github.com/Masterminds/sprig"
+ "github.com/coreruleset/go-ftw/config"
+ "github.com/coreruleset/go-ftw/ftwhttp"
+ "github.com/coreruleset/go-ftw/test"
+ "github.com/stretchr/testify/suite"
+)
+
+type inputOverrideTestSuite struct {
+ suite.Suite
+ cfg *config.FTWConfiguration
+ logFilePath string
+}
+
+var configTemplate = `
+---
+testoverride:
+ input:
+ {{ with .StopMagic }}stop_magic: {{ . }}{{ end }}
+ {{ with .BrokenConfig }}this_does_not_exist: "test"{{ end }}
+ {{ with .Port }}port: {{ . }}{{ end }}
+ {{ with .DestAddr }}dest_addr: {{ . }}{{ end }}
+ {{ with .Version }}version: {{ . }}{{ end }}
+ {{ with .URI }}uri: {{ . }}{{ end }}
+ {{ with .Method }}method: {{ . }}{{ end }}
+ {{ with .Protocol }}protocol: {{ . }}{{ end }}
+ {{ with .Data }}data: {{ . }}{{ end }}
+ {{ with .EncodedRequest }}encoded_request: {{ . }}{{ end }}
+ {{ with .RawRequest }}raw_request: {{ . }}{{ end }}
+ {{ with .Headers }}
+ headers:
+ {{ with .Host }}Host: {{ . }}{{ end }}
+ {{ with .UniqueID }}unique_id: {{ . }}{{ end }}
+ {{ end }}
+ {{ with .OverrideEmptyHostHeader }}override_empty_host_header: {{ . }}{{ end }}
+`
+
+var overrideConfigMap = map[string]interface{}{
+ "TestSetHostFromDestAddr": map[string]interface{}{
+ "DestAddr": "address.org",
+ "Port": 80,
+ },
+ "TestSetHostFromHostHeaderOverride": map[string]interface{}{
+ "DestAddr": "wrong.org",
+ "Headers": map[string]string{
+ "Host": "override.com",
+ },
+ "OverrideEmptyHostHeader": true,
+ },
+ "TestSetHeaderOverridingExistingOne": map[string]interface{}{
+ "Headers": map[string]string{
+ "Host": "address.org",
+ "UniqueID": "override",
+ },
+ },
+ "TestApplyInputOverrides": map[string]interface{}{
+ "Headers": map[string]string{
+ "Host": "address.org",
+ "UniqueID": "override",
+ },
+ },
+ "TestApplyInputOverrideURI": map[string]interface{}{
+ "URI": "/override",
+ },
+ "TestApplyInputOverrideVersion": map[string]interface{}{
+ "Version": "HTTP/1.1",
+ },
+ "TestApplyInputOverrideMethod": map[string]interface{}{
+ "Method": "MERGE",
+ },
+ "TestApplyInputOverrideData": map[string]interface{}{
+ "Data": "override",
+ },
+ "TestApplyInputOverrideEncodedRequest": map[string]interface{}{
+ "EncodedRequest": "overrideb64",
+ },
+ "TestApplyInputOverrideRAWRequest": map[string]interface{}{
+ "RawRequest": "overrideraw",
+ },
+ "TestApplyInputOverrideProtocol": map[string]interface{}{
+ "Protocol": "HTTP/1.1",
+ },
+ "TestApplyInputOverrideStopMagic": map[string]interface{}{
+ "StopMagic": "true",
+ },
+}
+
+// getOverrideConfigValue is useful to not repeat the text in the test itself
+func getOverrideConfigValue(key string) (string, error) {
+ pc, _, _, ok := runtime.Caller(1)
+ details := runtime.FuncForPC(pc)
+ if ok && details != nil {
+ caller := strings.Split(details.Name(), ".")
+ name := caller[len(caller)-1]
+ if overrideConfigMap[name] == nil {
+ return "", errors.New("cannot get override config value: be sure the caller is a test function, and the key is correct")
+ }
+ if strings.Contains(key, ".") {
+ keyParts := strings.Split(key, ".")
+ return overrideConfigMap[name].(map[string]interface{})[keyParts[0]].(map[string]string)[keyParts[1]], nil
+ }
+ return overrideConfigMap[name].(map[string]interface{})[key].(string), nil
+ }
+ return "", errors.New("failed to determine calling function")
+}
+
+func TestInputOverrideTestSuite(t *testing.T) {
+ suite.Run(t, new(inputOverrideTestSuite))
+}
+
+func (s *inputOverrideTestSuite) SetupTest() {
+}
+
+func (s *inputOverrideTestSuite) BeforeTest(_ string, name string) {
+ var err error
+
+ // set up configuration from template
+ tmpl := template.New("input-override").Funcs(sprig.TxtFuncMap())
+ configTmpl, err := tmpl.Parse(configTemplate)
+ s.NoError(err, "cannot parse template")
+ buf := &bytes.Buffer{}
+ err = configTmpl.Execute(buf, overrideConfigMap[name])
+ s.NoError(err, "cannot execute template")
+ s.cfg, err = config.NewConfigFromString(buf.String())
+ s.NoError(err, "cannot get config from string")
+ if s.logFilePath != "" {
+ s.cfg.WithLogfile(s.logFilePath)
+ }
+}
+
+func (s *inputOverrideTestSuite) TestSetHostFromDestAddr() {
+ originalHost := "original.com"
+ overrideHost, err := getOverrideConfigValue("DestAddr")
+ s.NoError(err, "cannot get override value")
+
+ testInput := test.Input{
+ DestAddr: &originalHost,
+ }
+ cfg := &config.FTWConfiguration{
+ TestOverride: config.FTWTestOverride{
+ Overrides: test.Overrides{
+ DestAddr: &overrideHost,
+ OverrideEmptyHostHeader: true,
+ },
+ },
+ }
+
+ err = applyInputOverride(cfg.TestOverride, &testInput)
+ s.NoError(err, "Failed to apply input overrides")
+
+ s.Equal(overrideHost, *testInput.DestAddr, "`dest_addr` should have been overridden")
+
+ s.NotNil(testInput.Headers, "Header map must exist after overriding `dest_addr`")
+
+ hostHeader := testInput.Headers.Get("Host")
+ s.NotEqual("", hostHeader, "Host header must be set after overriding `dest_addr`")
+ s.Equal(overrideHost, hostHeader, "Host header must be identical to `dest_addr` after overrding `dest_addr`")
+}
+
+func (s *inputOverrideTestSuite) TestSetHostFromHostHeaderOverride() {
+ originalDestAddr := "original.com"
+ overrideHostHeader, err := getOverrideConfigValue("Headers.Host")
+ s.NoError(err, "cannot get override value")
+
+ testInput := test.Input{
+ DestAddr: &originalDestAddr,
+ }
+
+ err = applyInputOverride(s.cfg.TestOverride, &testInput)
+ s.NoError(err, "Failed to apply input overrides")
+
+ hostHeader := testInput.Headers.Get("Host")
+ s.NotEqual("", hostHeader, "Host header must be set after overriding the `Host` header")
+ if hostHeader == overrideHostHeader {
+ s.Equal(overrideHostHeader, hostHeader, "Host header override must take precence over OverrideEmptyHostHeader")
+ } else {
+ s.Equal(overrideHostHeader, hostHeader, "Host header must be identical to overridden `Host` header.")
+ }
+}
+
+func (s *inputOverrideTestSuite) TestSetHeaderOverridingExistingOne() {
+ originalHeaderValue := "original"
+ overrideHeaderValue, err := getOverrideConfigValue("Headers.UniqueID")
+ s.NoError(err, "cannot get override value")
+
+ testInput := test.Input{
+ Headers: ftwhttp.Header{"unique_id": originalHeaderValue},
+ }
+
+ s.NotNil(testInput.Headers, "Header map must exist before overriding any header")
+
+ err = applyInputOverride(s.cfg.TestOverride, &testInput)
+ s.NoError(err, "Failed to apply input overrides")
+
+ overriddenHeader := testInput.Headers.Get("unique_id")
+ s.NotEqual("", overriddenHeader, "unique_id header must be set after overriding it")
+ s.Equal(overrideHeaderValue, overriddenHeader, "Host header must be identical to overridden `Host` header.")
+}
+
+func (s *inputOverrideTestSuite) TestApplyInputOverrides() {
+ originalHeaderValue := "original"
+ overrideHeaderValue, err := getOverrideConfigValue("Headers.UniqueID")
+ s.NoError(err, "cannot get override value")
+
+ testInput := test.Input{
+ Headers: ftwhttp.Header{"unique_id": originalHeaderValue},
+ }
+
+ s.NotNil(testInput.Headers, "Header map must exist before overriding any header")
+
+ err = applyInputOverride(s.cfg.TestOverride, &testInput)
+ s.NoError(err, "Failed to apply input overrides")
+
+ overriddenHeader := testInput.Headers.Get("unique_id")
+ s.NotEqual("", overriddenHeader, "unique_id header must be set after overriding it")
+ s.Equal(overrideHeaderValue, overriddenHeader, "Host header must be identical to overridden `Host` header.")
+}
+
+func (s *inputOverrideTestSuite) TestApplyInputOverrideURI() {
+ originalURI := "/original"
+ overrideURI, err := getOverrideConfigValue("URI")
+ s.NoError(err, "cannot get override value")
+
+ testInput := test.Input{
+ URI: &originalURI,
+ }
+
+ err = applyInputOverride(s.cfg.TestOverride, &testInput)
+ s.NoError(err, "Failed to apply input overrides")
+ s.Equal(overrideURI, *testInput.URI, "`URI` should have been overridden")
+}
+
+func (s *inputOverrideTestSuite) TestApplyInputOverrideVersion() {
+ originalVersion := "HTTP/0.9"
+ overrideVersion, err := getOverrideConfigValue("Version")
+ s.NoError(err, "cannot get override value")
+
+ testInput := test.Input{
+ Version: &originalVersion,
+ }
+ err = applyInputOverride(s.cfg.TestOverride, &testInput)
+ s.NoError(err, "Failed to apply input overrides")
+ s.Equal(overrideVersion, *testInput.Version, "`Version` should have been overridden")
+}
+
+func (s *inputOverrideTestSuite) TestApplyInputOverrideMethod() {
+ originalMethod := "POST"
+ overrideMethod, err := getOverrideConfigValue("Method")
+ s.NoError(err, "cannot get override value")
+
+ testInput := test.Input{
+ Method: &originalMethod,
+ }
+ err = applyInputOverride(s.cfg.TestOverride, &testInput)
+ s.NoError(err, "Failed to apply input overrides")
+ s.Equal(overrideMethod, *testInput.Method, "`Method` should have been overridden")
+}
+
+func (s *inputOverrideTestSuite) TestApplyInputOverrideData() {
+ originalData := "data"
+ overrideData, err := getOverrideConfigValue("Data")
+ s.NoError(err, "cannot get override value")
+
+ testInput := test.Input{
+ Data: &originalData,
+ }
+ err = applyInputOverride(s.cfg.TestOverride, &testInput)
+ s.NoError(err, "Failed to apply input overrides")
+ s.Equal(overrideData, *testInput.Data, "`Data` should have been overridden")
+}
+
+func (s *inputOverrideTestSuite) TestApplyInputOverrideStopMagic() {
+ stopMagicBool, err := getOverrideConfigValue("StopMagic")
+ s.NoError(err, "cannot get override value")
+ overrideStopMagic, err := strconv.ParseBool(stopMagicBool)
+ s.NoError(err, "Failed to parse `StopMagic` override value")
+ testInput := test.Input{
+ StopMagic: false,
+ }
+ err = applyInputOverride(s.cfg.TestOverride, &testInput)
+ s.NoError(err, "Failed to apply input overrides")
+ s.Equal(overrideStopMagic, testInput.StopMagic, "`StopMagic` should have been overridden")
+}
+
+func (s *inputOverrideTestSuite) TestApplyInputOverrideEncodedRequest() {
+ originalEncodedRequest := "originalbase64"
+ overrideEncodedRequest, err := getOverrideConfigValue("EncodedRequest")
+ s.NoError(err, "cannot get override value")
+ testInput := test.Input{
+ EncodedRequest: originalEncodedRequest,
+ }
+ err = applyInputOverride(s.cfg.TestOverride, &testInput)
+ s.NoError(err, "Failed to apply input overrides")
+ s.Equal(overrideEncodedRequest, testInput.EncodedRequest, "`EncodedRequest` should have been overridden")
+}
+
+func (s *inputOverrideTestSuite) TestApplyInputOverrideRAWRequest() {
+ originalRAWRequest := "original"
+ overrideRAWRequest, err := getOverrideConfigValue("RawRequest")
+ s.NoError(err, "cannot get override value")
+
+ testInput := test.Input{
+ RAWRequest: originalRAWRequest,
+ }
+
+ err = applyInputOverride(s.cfg.TestOverride, &testInput)
+ s.NoError(err, "Failed to apply input overrides")
+ s.Equal(overrideRAWRequest, testInput.RAWRequest, "`RAWRequest` should have been overridden")
+}
diff --git a/runner/run_test.go b/runner/run_test.go
index 95ab8e0..6d7421d 100644
--- a/runner/run_test.go
+++ b/runner/run_test.go
@@ -1,1026 +1,346 @@
package runner
import (
+ "bytes"
"fmt"
"net/http"
"net/http/httptest"
"os"
"regexp"
"testing"
-
- "github.com/coreruleset/go-ftw/output"
-
- "github.com/stretchr/testify/assert"
+ "text/template"
"github.com/rs/zerolog/log"
+ "github.com/stretchr/testify/suite"
- "github.com/coreruleset/go-ftw/check"
"github.com/coreruleset/go-ftw/config"
"github.com/coreruleset/go-ftw/ftwhttp"
+ "github.com/coreruleset/go-ftw/output"
"github.com/coreruleset/go-ftw/test"
)
-var yamlConfig = `
----
+var logText = `[Tue Jan 05 02:21:09.637165 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Pattern match "\\\\b(?:keep-alive|close),\\\\s?(?:keep-alive|close)\\\\b" at REQUEST_HEADERS:Connection. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "339"] [id "920210"] [msg "Multiple/Conflicting Connection Header Data Found"] [data "close,close"] [severity "WARNING"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
+[Tue Jan 05 02:21:09.637731 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Match of "pm AppleWebKit Android" against "REQUEST_HEADERS:User-Agent" required. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1230"] [id "920300"] [msg "Request Missing an Accept Header"] [severity "NOTICE"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [tag "PCI/6.5.10"] [tag "paranoia-level/2"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
+[Tue Jan 05 02:21:09.638572 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:anomaly_score. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "91"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
+[Tue Jan 05 02:21:09.647668 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:inbound_anomaly_score. [file "/etc/modsecurity.d/owasp-crs/rules/RESPONSE-980-CORRELATION.conf"] [line "87"] [id "980130"] [msg "Inbound Anomaly Score Exceeded (Total Inbound Score: 5 - SQLI=0,XSS=0,RFI=0,LFI=0,RCE=0,PHPI=0,HTTP=0,SESS=0): individual paranoia level scores: 3, 2, 0, 0"] [ver "OWASP_CRS/3.3.0"] [tag "event-correlation"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]`
+
+var testConfigMap = map[string]string{
+ "BaseConfig": `---
testoverride:
ignore:
"920400-1": "This test result must be ignored"
-`
-
-var yamlConfigPortOverride = `
----
-testoverride:
- input:
- dest_addr: "TEST_ADDR"
- port: %d
- protocol: "http"
-`
-
-var yamlConfigEmptyHostHeaderOverride = `
----
-testoverride:
- input:
- dest_addr: %s
- headers:
- Host: %s
- override_empty_host_header: true
-`
-
-var yamlConfigHostHeaderOverride = `
----
+`,
+ "TestDisabledRun": `---
+mode: 'cloud'
+`,
+ "TestBrokenOverrideRun": `---
testoverride:
input:
- dest_addr: address.org
- headers:
- unique_id: %s
-`
-
-var yamlConfigHeaderOverride = `
----
-testoverride:
- input:
- dest_addr: address.org
- headers:
- unique_id: %s
-`
-
-var yamlConfigURIOverride = `
----
-testoverride:
- input:
- uri: %s
-`
-
-var yamlConfigVersionOverride = `
----
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ this_does_not_exist: "test"
+`,
+ "TestBrokenPortOverrideRun": `---
testoverride:
input:
- version: %s
-`
-
-var yamlConfigMethodOverride = `
----
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ protocol: "http"`,
+ "TestIgnoredTestsRun": `---
testoverride:
- input:
- method: %s
-`
-
-var yamlConfigDataOverride = `
----
+ ignore:
+ "001": "This test result must be ignored"
+ forcefail:
+ "008": "This test should pass, but it is going to fail"
+ forcepass:
+ "099": "This test failed, but it shall pass!"
+`,
+ "TestOverrideRun": `---
testoverride:
input:
- data: %s
-`
-
-var yamlConfigStopMagicOverride = `
----
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ protocol: "http"
+`,
+ "TestApplyInputOverrideMethod": `---
testoverride:
input:
- stop_magic: %t
-`
-
-var yamlConfigEncodedRequestOverride = `
----
+ method: %s
+`,
+ "TestApplyInputOverrideData": `---
testoverride:
input:
- encoded_request: %s
-`
-
-var yamlConfigRAWRequestOverride = `
----
+ data: %s
+`,
+ "TestApplyInputOverrideStopMagic": `---
testoverride:
input:
- raw_request: %s
-`
-
-var yamlConfigOverride = `
----
+ stop_magic: %t
+`,
+ "TestApplyInputOverrideEncodedRequest": `---
testoverride:
input:
- dest_addr: "TEST_ADDR"
- # -1 designates port value must be replaced by test setup
- port: -1
- protocol: "http"
-`
-
-var yamlBrokenConfigOverride = `
----
+ encoded_request: %s
+`,
+ "TestApplyInputOverrideRAWRequest": `---
testoverride:
input:
- dest_addr: "TEST_ADDR"
- # -1 designates port value must be replaced by test setup
- port: -1
- this_does_not_exist: "test"
-`
-
-var yamlConfigIgnoreTests = `
----
-testoverride:
- ignore:
- "001": "This test result must be ignored"
- forcefail:
- "008": "This test should pass, but it is going to fail"
- forcepass:
- "099": "This test failed, but it shall pass!"
-`
-
-var yamlCloudConfig = `
----
-mode: cloud
-`
-
-var logText = `
-[Tue Jan 05 02:21:09.637165 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Pattern match "\\\\b(?:keep-alive|close),\\\\s?(?:keep-alive|close)\\\\b" at REQUEST_HEADERS:Connection. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "339"] [id "920210"] [msg "Multiple/Conflicting Connection Header Data Found"] [data "close,close"] [severity "WARNING"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
-[Tue Jan 05 02:21:09.637731 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Match of "pm AppleWebKit Android" against "REQUEST_HEADERS:User-Agent" required. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1230"] [id "920300"] [msg "Request Missing an Accept Header"] [severity "NOTICE"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [tag "PCI/6.5.10"] [tag "paranoia-level/2"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
-[Tue Jan 05 02:21:09.638572 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:anomaly_score. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "91"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
-[Tue Jan 05 02:21:09.647668 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:inbound_anomaly_score. [file "/etc/modsecurity.d/owasp-crs/rules/RESPONSE-980-CORRELATION.conf"] [line "87"] [id "980130"] [msg "Inbound Anomaly Score Exceeded (Total Inbound Score: 5 - SQLI=0,XSS=0,RFI=0,LFI=0,RCE=0,PHPI=0,HTTP=0,SESS=0): individual paranoia level scores: 3, 2, 0, 0"] [ver "OWASP_CRS/3.3.0"] [tag "event-correlation"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
-`
-
-var yamlTest = `---
-meta:
- author: "tester"
- enabled: true
- name: "gotest-ftw.yaml"
- description: "Example Test"
-tests:
- - test_title: "001"
- description: "access real external site"
- stages:
- - stage:
- input:
- dest_addr: "TEST_ADDR"
- # -1 designates port value must be replaced by test setup
- port: -1
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Accept: "*/*"
- Host: "TEST_ADDR"
- output:
- expect_error: False
- status: [200]
- - test_title: "008"
- description: "this test is number 8"
- stages:
- - stage:
- input:
- dest_addr: "TEST_ADDR"
- # -1 designates port value must be replaced by test setup
- port: -1
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Accept: "*/*"
- Host: "localhost"
- output:
- status: [200]
- - test_title: "010"
- stages:
- - stage:
- input:
- dest_addr: "TEST_ADDR"
- # -1 designates port value must be replaced by test setup
- port: -1
- version: "HTTP/1.1"
- method: "OTHER"
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Accept: "*/*"
- Host: "localhost"
- output:
- response_contains: "Hello, client"
- - test_title: "101"
- description: "this tests exceptions (connection timeout)"
- stages:
- - stage:
- input:
- dest_addr: "TEST_ADDR"
- port: 8090
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Accept: "*/*"
- Host: "none.host"
- output:
- expect_error: True
- - test_title: "102"
- description: "this tests exceptions (connection timeout)"
- stages:
- - stage:
- input:
- dest_addr: "TEST_ADDR"
- port: 8090
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Host: "none.host"
- Accept: "*/*"
- encoded_request: "UE9TVCAvaW5kZXguaHRtbCBIVFRQLzEuMQ0KSG9zdDogMTkyLjE2OC4xLjIzDQpVc2VyLUFnZW50OiBjdXJsLzcuNDMuMA0KQWNjZXB0OiAqLyoNCkNvbnRlbnQtTGVuZ3RoOiA2NA0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQNCkNvbm5lY3Rpb246IGNsb3NlDQoNCmQ9MTsyOzM7NDs1XG4xO0BTVU0oMSsxKSpjbWR8JyBwb3dlcnNoZWxsIElFWCh3Z2V0IDByLnBlL3ApJ1whQTA7Mw=="
- output:
- expect_error: True
-`
-
-var yamlTestMultipleMatches = `---
-meta:
- author: "tester"
- enabled: true
- name: "gotest-ftw.yaml"
- description: "Example Test with multiple expected outputs per single rule"
-tests:
- - test_title: "001"
- description: "access real external site"
- stages:
- - stage:
- input:
- dest_addr: "TEST_ADDR"
- # -1 designates port value must be replaced by test setup
- port: -1
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Accept: "*/*"
- Host: "TEST_ADDR"
- output:
- status: [200]
- response_contains: "Not contains this"
-`
-
-var yamlTestOverride = `
----
-meta:
- author: "tester"
- enabled: true
- name: "gotest-ftw.yaml"
- description: "Example Override Test"
-tests:
- -
- test_title: "001"
- description: "access real external site"
- stages:
- -
- stage:
- input:
- dest_addr: "TEST_ADDR"
- # -1 designates port value must be replaced by test setup
- port: -1
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Host: "TEST_ADDR"
- output:
- expect_error: False
- status: [200]
-`
-
-var yamlTestOverrideWithNoPort = `
----
-meta:
- author: "tester"
- enabled: true
- name: "gotest-ftw.yaml"
- description: "Example Override Test"
-tests:
- -
- test_title: "001"
- description: "access real external site"
- stages:
- -
- stage:
- input:
- dest_addr: "TEST_ADDR"
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Host: "TEST_ADDR"
- output:
- expect_error: False
- status: [200]
-`
-
-var yamlDisabledTest = `
----
-meta:
- author: "tester"
- enabled: false
- name: "we do not care, this test is disabled"
- description: "Example Test"
-tests:
- -
- test_title: "001"
- description: "access real external site"
- stages:
- -
- stage:
- input:
- dest_addr: "TEST_ADDR"
- # -1 designates port value must be replaced by test setup
- port: -1
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Host: "TEST_ADDR"
- output:
- status: [1234]
-`
+ raw_request: %s
+`,
+}
-var yamlTestLogs = `---
-meta:
- author: "tester"
- enabled: true
- name: "gotest-ftw.yaml"
- description: "Example Test"
-tests:
- - test_title: "200"
- stages:
- - stage:
- input:
- dest_addr: "TEST_ADDR"
- # -1 designates port value must be replaced by test setup
- port: -1
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Accept: "*/*"
- Host: "localhost"
- output:
- log_contains: id \"949110\"
- - test_title: "201"
- stages:
- - stage:
- input:
- dest_addr: "TEST_ADDR"
- # -1 designates port value must be replaced by test setup
- port: -1
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Accept: "*/*"
- Host: "localhost"
- output:
- no_log_contains: ABCDE
-`
+var destinationMap = map[string]string{
+ "TestBrokenOverrideRun": "http://example.com:1234",
+ "TestDisabledRun": "http://example.com:1234",
+}
-var yamlFailedTest = `---
-meta:
- author: "tester"
- enabled: true
- name: "gotest-ftw.yaml"
- description: "Example Test"
-tests:
- - test_title: "990"
- description: test that fails
- stages:
- - stage:
- input:
- dest_addr: "TEST_ADDR"
- # -1 designates port value must be replaced by test setup
- port: -1
- headers:
- User-Agent: "ModSecurity CRS 3 Tests"
- Accept: "*/*"
- Host: "none.host"
- output:
- status: [413]
-`
+type runTestSuite struct {
+ suite.Suite
+ cfg *config.FTWConfiguration
+ ftwTests []test.FTWTest
+ logFilePath string
+ out *output.Output
+ ts *httptest.Server
+ dest *ftwhttp.Destination
+ tempFileName string
+}
// Error checking omitted for brevity
-func newTestServer(t *testing.T, cfg *config.FTWConfiguration, logLines string) (destination *ftwhttp.Destination, logFilePath string) {
- logFilePath = setUpLogFileForTestServer(t, cfg)
+func (s *runTestSuite) newTestServer(logLines string) {
+ var err error
+ s.setUpLogFileForTestServer()
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ s.ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("Hello, client"))
- writeTestServerLog(t, cfg, logLines, logFilePath, r)
- }))
-
- // close server after test
- t.Cleanup(ts.Close)
-
- dest, err := ftwhttp.DestinationFromString(ts.URL)
- if err != nil {
- assert.FailNow(t, "cannot get destination from string", err.Error())
- }
- return dest, logFilePath
-}
-
-// Error checking omitted for brevity
-func newTestServerForCloudTest(t *testing.T, responseStatus int) (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"))
+ s.writeTestServerLog(logLines, r)
}))
- // close server after test
- t.Cleanup(server.Close)
-
- dest, err := ftwhttp.DestinationFromString(server.URL)
- if err != nil {
- assert.FailNow(t, "cannot get destination from string", err.Error())
- }
-
- return server, dest
+ s.dest, err = ftwhttp.DestinationFromString((s.ts).URL)
+ s.Require().NoError(err, "cannot get destination from string")
}
-func setUpLogFileForTestServer(t *testing.T, cfg *config.FTWConfiguration) (logFilePath string) {
+func (s *runTestSuite) setUpLogFileForTestServer() {
// log to the configured file
- if cfg.RunMode == config.DefaultRunMode {
- logFilePath = cfg.LogFile
+ if s.cfg != nil && s.cfg.RunMode == config.DefaultRunMode {
+ s.logFilePath = s.cfg.LogFile
}
// if no file has been configured, create one and handle cleanup
- if logFilePath == "" {
+ if s.logFilePath == "" {
file, err := os.CreateTemp("", "go-ftw-test-*.log")
- assert.NoError(t, err)
- logFilePath = file.Name()
- t.Cleanup(func() {
- _ = os.Remove(logFilePath)
- log.Info().Msgf("Deleting temporary file '%s'", logFilePath)
- })
+ s.NoError(err)
+ s.logFilePath = file.Name()
}
- return logFilePath
}
-func writeTestServerLog(t *testing.T, cfg *config.FTWConfiguration, logLines string, logFilePath string, r *http.Request) {
+func (s *runTestSuite) writeTestServerLog(logLines 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(cfg.LogMarkerHeaderName) != "" {
+ if r.Header.Get(s.cfg.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 {
- assert.FailNow(t, "cannot open file", err.Error())
- }
+ file, err := os.OpenFile(s.logFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
+ s.NoError(err, "cannot open file")
+
defer file.Close()
- _, err = file.WriteString(logMessage)
- if err != nil {
- assert.FailNow(t, "cannot write log message to file", err.Error())
- }
+ n, err := file.WriteString(logMessage)
+ s.Len(logMessage, n, "cannot write log message to file")
+ s.NoError(err, "cannot write log message to file")
}
-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
- }
- }
+func (s *runTestSuite) SetupTest() {
+ s.cfg = config.NewDefaultConfig()
+ // setup test webserver (not a waf)
+ s.newTestServer(logText)
+ if s.logFilePath != "" {
+ s.cfg.WithLogfile(s.logFilePath)
}
+
+ s.out = output.NewOutput("normal", os.Stdout)
}
-func replaceDestinationInConfiguration(override *config.FTWTestOverride, dest ftwhttp.Destination) {
- replaceableAddress := "TEST_ADDR"
- replaceablePort := -1
+func (s *runTestSuite) TearDownTest() {
+ s.ts.Close()
+}
- overriddenInputs := &override.Overrides
- if overriddenInputs.DestAddr != nil && *overriddenInputs.DestAddr == replaceableAddress {
- overriddenInputs.DestAddr = &dest.DestAddr
+func (s *runTestSuite) BeforeTest(_ string, name string) {
+ var err error
+ var cfg string
+ var ok bool
+
+ // if we have a configuration for this test, use it
+ // else use the default configuration
+ if cfg, ok = testConfigMap[name]; !ok {
+ cfg = testConfigMap["BaseConfig"]
}
- if overriddenInputs.Port != nil && *overriddenInputs.Port == replaceablePort {
- overriddenInputs.Port = &dest.Port
+
+ // if we have a destination for this test, use it
+ // else use the default destination
+ if s.dest == nil {
+ s.dest, err = ftwhttp.DestinationFromString(destinationMap[name])
+ s.NoError(err)
}
-}
-func TestRun(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlConfig)
- assert.NoError(t, err)
+ log.Info().Msgf("Using port %d and addr '%s'", s.dest.Port, s.dest.DestAddr)
- out := output.NewOutput("normal", os.Stdout)
+ // set up variables for template
+ vars := map[string]interface{}{
+ "TestPort": s.dest.Port,
+ "TestAddr": s.dest.DestAddr,
+ }
- // setup test webserver (not a waf)
- dest, logFilePath := newTestServer(t, cfg, logText)
- cfg.WithLogfile(logFilePath)
- ftwTest, err := test.GetTestFromYaml([]byte(yamlTest))
- assert.NoError(t, err)
+ // set up configuration from template
+ configTmpl, err := template.New("config-test").Parse(cfg)
+ s.NoError(err, "cannot parse template")
+ buf := &bytes.Buffer{}
+ err = configTmpl.Execute(buf, vars)
+ s.NoError(err, "cannot execute template")
+ s.cfg, err = config.NewConfigFromString(buf.String())
+ s.NoError(err, "cannot get config from string")
+ if s.logFilePath != "" {
+ s.cfg.WithLogfile(s.logFilePath)
+ }
+ // get tests template from file
+ tmpl, err := template.ParseFiles(fmt.Sprintf("testdata/%s.yaml", name))
+ s.NoError(err)
+ // create a temporary file to hold the test
+ testFileContents, err := os.CreateTemp("testdata", "mock-test-*.yaml")
+ s.NoError(err, "cannot create temporary file")
+ err = tmpl.Execute(testFileContents, vars)
+ s.NoError(err, "cannot execute template")
+ // get tests from file
+ s.ftwTests, err = test.GetTestsFromFiles(testFileContents.Name())
+ s.NoError(err, "cannot get tests from file")
+ // save the name of the temporary file so we can delete it later
+ s.tempFileName = testFileContents.Name()
+}
- replaceDestinationInTest(&ftwTest, *dest)
+func (s *runTestSuite) AfterTest(_ string, _ string) {
+ err := os.Remove(s.logFilePath)
+ s.NoError(err, "cannot remove log file")
+ log.Info().Msgf("Deleting temporary file '%s'", s.logFilePath)
+ if s.tempFileName != "" {
+ err = os.Remove(s.tempFileName)
+ s.NoError(err, "cannot remove test file")
+ s.tempFileName = ""
+ }
+}
+
+func TestRunTestsTestSuite(t *testing.T) {
+ suite.Run(t, new(runTestSuite))
+}
- t.Run("show time and execute all", func(t *testing.T) {
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
+func (s *runTestSuite) TestRunTests_Run() {
+ s.Run("show time and execute all", func() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
ShowTime: true,
Output: output.Quiet,
- }, out)
- assert.NoError(t, err)
- assert.Equalf(t, res.Stats.TotalFailed(), 0, "Oops, %d tests failed to run!", res.Stats.TotalFailed())
+ }, s.out)
+ s.NoError(err)
+ s.Equalf(res.Stats.TotalFailed(), 0, "Oops, %d tests failed to run!", res.Stats.TotalFailed())
})
- t.Run("be verbose and execute all", func(t *testing.T) {
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
+ s.Run("be verbose and execute all", func() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
Include: regexp.MustCompile("0*"),
ShowTime: true,
- }, out)
- assert.NoError(t, err)
- assert.Equal(t, res.Stats.TotalFailed(), 0, "verbose and execute all failed")
+ }, s.out)
+ s.NoError(err)
+ s.Equal(res.Stats.TotalFailed(), 0, "verbose and execute all failed")
})
- t.Run("don't show time and execute all", func(t *testing.T) {
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
+ s.Run("don't show time and execute all", func() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
Include: regexp.MustCompile("0*"),
- }, out)
- assert.NoError(t, err)
- assert.Equal(t, res.Stats.TotalFailed(), 0, "do not show time and execute all failed")
+ }, s.out)
+ s.NoError(err)
+ s.Equal(res.Stats.TotalFailed(), 0, "do not show time and execute all failed")
})
- t.Run("execute only test 008 but exclude all", func(t *testing.T) {
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
+ s.Run("execute only test 008 but exclude all", func() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
Include: regexp.MustCompile("008"),
Exclude: regexp.MustCompile("0*"),
- }, out)
- assert.NoError(t, err)
- assert.Equal(t, res.Stats.TotalFailed(), 0, "do not show time and execute all failed")
+ }, s.out)
+ s.NoError(err)
+ s.Equal(res.Stats.TotalFailed(), 0, "do not show time and execute all failed")
})
- t.Run("exclude test 010", func(t *testing.T) {
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
+ s.Run("exclude test 010", func() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
Exclude: regexp.MustCompile("010"),
- }, out)
- assert.NoError(t, err)
- assert.Equal(t, res.Stats.TotalFailed(), 0, "failed to exclude test")
+ }, s.out)
+ s.NoError(err)
+ s.Equal(res.Stats.TotalFailed(), 0, "failed to exclude test")
})
- t.Run("test exceptions 1", func(t *testing.T) {
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
+ s.Run("test exceptions 1", func() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
Include: regexp.MustCompile("1*"),
Exclude: regexp.MustCompile("0*"),
Output: output.Quiet,
- }, out)
- assert.NoError(t, err)
- assert.Equal(t, res.Stats.TotalFailed(), 0, "failed to test exceptions")
+ }, s.out)
+ s.NoError(err)
+ s.Equal(res.Stats.TotalFailed(), 0, "failed to test exceptions")
})
}
-func TestRunMultipleMatches(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlConfig)
- assert.NoError(t, err)
-
- out := output.NewOutput("normal", os.Stdout)
-
- dest, logFilePath := newTestServer(t, cfg, logText)
- cfg.WithLogfile(logFilePath)
- ftwTest, err := test.GetTestFromYaml([]byte(yamlTestMultipleMatches))
- assert.NoError(t, err)
-
- replaceDestinationInTest(&ftwTest, *dest)
-
- t.Run("execute multiple...test", func(t *testing.T) {
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
+func (s *runTestSuite) TestRunMultipleMatches() {
+ s.Run("execute multiple...test", func() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
Output: output.Quiet,
- }, out)
- assert.NoError(t, err)
- assert.Equalf(t, res.Stats.TotalFailed(), 1, "Oops, %d tests failed to run! Expected 1 failing test", res.Stats.TotalFailed())
+ }, s.out)
+ s.NoError(err)
+ s.Equalf(res.Stats.TotalFailed(), 1, "Oops, %d tests failed to run! Expected 1 failing test", res.Stats.TotalFailed())
})
}
-func TestOverrideRun(t *testing.T) {
- // setup test webserver (not a waf)
- cfg, err := config.NewConfigFromString(yamlConfigOverride)
- assert.NoError(t, err)
-
- out := output.NewOutput("normal", os.Stdout)
-
- dest, logFilePath := newTestServer(t, cfg, logText)
-
- replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
- cfg.WithLogfile(logFilePath)
-
- // replace host and port with values that can be overridden by config
- fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234")
- if err != nil {
- assert.FailNow(t, err.Error(), "Failed to parse fake destination")
- }
-
- ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride))
- assert.NoError(t, err)
-
- replaceDestinationInTest(&ftwTest, *fakeDestination)
-
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
+func (s *runTestSuite) TestOverrideRun() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
Output: output.Quiet,
- }, out)
- assert.NoError(t, err)
- assert.LessOrEqual(t, 0, res.Stats.TotalFailed(), "Oops, test run failed!")
+ }, s.out)
+ s.NoError(err)
+ s.LessOrEqual(0, res.Stats.TotalFailed(), "Oops, test run failed!")
}
-func TestBrokenOverrideRun(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlBrokenConfigOverride)
- assert.NoError(t, err)
-
- out := output.NewOutput("normal", os.Stdout)
-
- dest, logFilePath := newTestServer(t, cfg, logText)
- cfg.WithLogfile(logFilePath)
-
- replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
-
- // replace host and port with values that can be overridden by config
- fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234")
- if err != nil {
- assert.FailNow(t, err.Error(), "Failed to parse fake destination")
- }
-
- ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride))
- assert.NoError(t, err)
-
- replaceDestinationInTest(&ftwTest, *fakeDestination)
-
+func (s *runTestSuite) TestBrokenOverrideRun() {
// the test should succeed, despite the unknown override property
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
- assert.NoError(t, err)
- assert.LessOrEqual(t, 0, res.Stats.TotalFailed(), "Oops, test run failed!")
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{}, s.out)
+ s.NoError(err)
+ s.LessOrEqual(0, res.Stats.TotalFailed(), "Oops, test run failed!")
}
-func TestBrokenPortOverrideRun(t *testing.T) {
- defaultConfig := config.NewDefaultConfig()
- // TestServer initialized first to retrieve the correct port number
- dest, logFilePath := newTestServer(t, defaultConfig, logText)
- // replace destination port inside the yaml with the retrieved one
- cfg, err := config.NewConfigFromString(fmt.Sprintf(yamlConfigPortOverride, dest.Port))
- assert.NoError(t, err)
-
- out := output.NewOutput("normal", os.Stdout)
-
- replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
- cfg.WithLogfile(logFilePath)
-
- // replace host and port with values that can be overridden by config
- fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234")
- if err != nil {
- assert.FailNow(t, err.Error(), "Failed to parse fake destination")
- }
-
- ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverrideWithNoPort))
- assert.NoError(t, err)
-
- replaceDestinationInTest(&ftwTest, *fakeDestination)
-
+func (s *runTestSuite) TestBrokenPortOverrideRun() {
// the test should succeed, despite the unknown override property
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
- assert.NoError(t, err)
- assert.LessOrEqual(t, 0, res.Stats.TotalFailed(), "Oops, test run failed!")
-}
-
-func TestDisabledRun(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlCloudConfig)
- assert.NoError(t, err)
- out := output.NewOutput("normal", os.Stdout)
-
- fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234")
- if err != nil {
- assert.FailNow(t, err.Error(), "Failed to parse fake destination")
- }
-
- ftwTest, err := test.GetTestFromYaml([]byte(yamlDisabledTest))
- assert.NoError(t, err)
- replaceDestinationInTest(&ftwTest, *fakeDestination)
-
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
- assert.NoError(t, err)
- assert.LessOrEqual(t, 0, res.Stats.TotalFailed(), "Oops, test run failed!")
-}
-
-func TestLogsRun(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlConfig)
- assert.NoError(t, err)
- // setup test webserver (not a waf)
- dest, logFilePath := newTestServer(t, cfg, logText)
-
- replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
- cfg.WithLogfile(logFilePath)
-
- out := output.NewOutput("normal", os.Stdout)
-
- ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs))
- assert.NoError(t, err)
- replaceDestinationInTest(&ftwTest, *dest)
-
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
- assert.NoError(t, err)
- assert.LessOrEqual(t, 0, res.Stats.TotalFailed(), "Oops, test run failed!")
-}
-
-func TestCloudRun(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlCloudConfig)
- assert.NoError(t, err)
- out := output.NewOutput("normal", os.Stdout)
- stats := NewRunStats()
-
- ftwTestDummy, err := test.GetTestFromYaml([]byte(yamlTestLogs))
- assert.NoError(t, 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))
- assert.NoError(t, err)
- testCase := &ftwTest.Tests[testCaseIndex]
- stage := &testCase.Stages[stageIndex].Stage
-
- ftwCheck := check.NewCheck(cfg)
-
- // this mirrors check.SetCloudMode()
- responseStatus := 200
- if stage.Output.LogContains != "" {
- responseStatus = 403
- } else if stage.Output.NoLogContains != "" {
- responseStatus = 405
- }
- server, dest := newTestServerForCloudTest(t, responseStatus)
-
- replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
-
- replaceDestinationInTest(&ftwTest, *dest)
- assert.NoError(t, err)
- client, err := ftwhttp.NewClient(ftwhttp.NewClientConfig())
- assert.NoError(t, err)
- runContext := TestRunContext{
- Config: cfg,
- Include: nil,
- Exclude: nil,
- ShowTime: false,
- Stats: stats,
- Output: out,
- Client: client,
- LogLines: nil,
- }
-
- err = RunStage(&runContext, ftwCheck, *testCase, *stage)
- assert.NoError(t, err)
- assert.LessOrEqual(t, 0, runContext.Stats.TotalFailed(), "Oops, test run failed!")
-
- server.Close()
- }
- }
- })
-}
-
-func TestFailedTestsRun(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlConfig)
- assert.NoError(t, err)
- dest, logFilePath := newTestServer(t, cfg, logText)
-
- out := output.NewOutput("normal", os.Stdout)
- replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
- cfg.WithLogfile(logFilePath)
-
- ftwTest, err := test.GetTestFromYaml([]byte(yamlFailedTest))
- assert.NoError(t, err)
- replaceDestinationInTest(&ftwTest, *dest)
-
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
- assert.NoError(t, err)
- assert.Equal(t, 1, res.Stats.TotalFailed())
-}
-
-func TestApplyInputOverrideHostFromDestAddr(t *testing.T) {
- originalHost := "original.com"
- overrideHost := "override.com"
- testInput := test.Input{
- DestAddr: &originalHost,
- }
- cfg := &config.FTWConfiguration{
- TestOverride: config.FTWTestOverride{
- Overrides: test.Overrides{
- DestAddr: &overrideHost,
- },
- },
- }
-
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
-
- assert.Equal(t, overrideHost, *testInput.DestAddr, "`dest_addr` should have been overridden")
-
- assert.NotNil(t, testInput.Headers, "Header map must exist after overriding `dest_addr`")
-
- hostHeader := testInput.Headers.Get("Host")
- assert.Equal(t, "", hostHeader, "Without OverrideEmptyHostHeader, Host header must not be set after overriding `dest_addr`")
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{}, s.out)
+ s.NoError(err)
+ s.LessOrEqual(0, res.Stats.TotalFailed(), "Oops, test run failed!")
}
-func TestApplyInputOverrideEmptyHostHeaderSetHostFromDestAddr(t *testing.T) {
- originalHost := "original.com"
- overrideHost := "override.com"
- testInput := test.Input{
- DestAddr: &originalHost,
- }
- cfg := &config.FTWConfiguration{
- TestOverride: config.FTWTestOverride{
- Overrides: test.Overrides{
- DestAddr: &overrideHost,
- OverrideEmptyHostHeader: true,
- },
- },
- }
-
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
-
- assert.Equal(t, overrideHost, *testInput.DestAddr, "`dest_addr` should have been overridden")
-
- assert.NotNil(t, testInput.Headers, "Header map must exist after overriding `dest_addr`")
-
- hostHeader := testInput.Headers.Get("Host")
- assert.NotEqual(t, "", hostHeader, "Host header must be set after overriding `dest_addr` and setting `override_empty_host_header` to `true`")
- assert.Equal(t, overrideHost, hostHeader, "Host header must be identical to `dest_addr` after overriding `dest_addr` and setting `override_emtpy_host_header` to `true`")
-}
-
-func TestApplyInputOverrideSetHostFromHostHeaderOverride(t *testing.T) {
- originalDestAddr := "original.com"
- overrideDestAddress := "wrong.org"
- overrideHostHeader := "override.com"
-
- cfg, err1 := config.NewConfigFromString(fmt.Sprintf(yamlConfigEmptyHostHeaderOverride, overrideDestAddress, overrideHostHeader))
- assert.NoError(t, err1)
-
- testInput := test.Input{
- DestAddr: &originalDestAddr,
- }
-
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
-
- hostHeader := testInput.Headers.Get("Host")
- assert.NotEqual(t, "", hostHeader, "Host header must be set after overriding the `Host` header")
- if hostHeader == overrideDestAddress {
- assert.Equal(t, overrideHostHeader, hostHeader, "Host header override must take precence over OverrideEmptyHostHeader")
- } else {
- assert.Equal(t, overrideHostHeader, hostHeader, "Host header must be identical to overridden `Host` header.")
- }
+func (s *runTestSuite) TestDisabledRun() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{}, s.out)
+ s.NoError(err)
+ s.LessOrEqual(0, res.Stats.TotalFailed(), "Oops, test run failed!")
}
-func TestApplyInputOverrideSetHeaderOverridingExistingOne(t *testing.T) {
- originalHeaderValue := "original"
- overrideHeaderValue := "override"
- cfg, err1 := config.NewConfigFromString(fmt.Sprintf(yamlConfigHostHeaderOverride, overrideHeaderValue))
- assert.NoError(t, err1)
-
- testInput := test.Input{
- Headers: ftwhttp.Header{"unique_id": originalHeaderValue},
- }
-
- assert.NotNil(t, testInput.Headers, "Header map must exist before overriding any header")
-
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
-
- overriddenHeader := testInput.Headers.Get("unique_id")
- assert.NotEqual(t, "", overriddenHeader, "unique_id header must be set after overriding it")
- assert.Equal(t, overrideHeaderValue, overriddenHeader, "Host header must be identical to overridden `Host` header.")
+func (s *runTestSuite) TestLogsRun() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{}, s.out)
+ s.NoError(err)
+ s.LessOrEqual(0, res.Stats.TotalFailed(), "Oops, test run failed!")
}
-func TestApplyInputOverrides(t *testing.T) {
- originalHeaderValue := "original"
- overrideHeaderValue := "override"
- cfg, err1 := config.NewConfigFromString(fmt.Sprintf(yamlConfigHeaderOverride, overrideHeaderValue))
- assert.NoError(t, err1)
-
- testInput := test.Input{
- Headers: ftwhttp.Header{"unique_id": originalHeaderValue},
- }
-
- assert.NotNil(t, testInput.Headers, "Header map must exist before overriding any header")
-
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
-
- overriddenHeader := testInput.Headers.Get("unique_id")
- assert.NotEqual(t, "", overriddenHeader, "unique_id header must be set after overriding it")
- assert.Equal(t, overrideHeaderValue, overriddenHeader, "Host header must be identical to overridden `Host` header.")
+func (s *runTestSuite) TestFailedTestsRun() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{}, s.out)
+ s.NoError(err)
+ s.Equal(1, res.Stats.TotalFailed())
}
-func TestApplyInputOverrideURI(t *testing.T) {
- originalURI := "original.com"
- overrideURI := "override.com"
- testInput := test.Input{
- URI: &originalURI,
- }
-
- cfg, err1 := config.NewConfigFromString(fmt.Sprintf(yamlConfigURIOverride, overrideURI))
- assert.NoError(t, err1)
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
- assert.Equal(t, overrideURI, *testInput.URI, "`URI` should have been overridden")
-}
-
-func TestApplyInputOverrideVersion(t *testing.T) {
- originalVersion := "HTTP/0.9"
- overrideVersion := "HTTP/1.1"
- testInput := test.Input{
- Version: &originalVersion,
- }
- cfg, err1 := config.NewConfigFromString(fmt.Sprintf(yamlConfigVersionOverride, overrideVersion))
- assert.NoError(t, err1)
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
- assert.Equal(t, overrideVersion, *testInput.Version, "`Version` should have been overridden")
-}
-
-func TestApplyInputOverrideMethod(t *testing.T) {
- originalMethod := "original.com"
- overrideMethod := "override.com"
- testInput := test.Input{
- Method: &originalMethod,
- }
- cfg, err1 := config.NewConfigFromString(fmt.Sprintf(yamlConfigMethodOverride, overrideMethod))
- assert.NoError(t, err1)
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
- assert.Equal(t, overrideMethod, *testInput.Method, "`Method` should have been overridden")
-}
-
-func TestApplyInputOverrideData(t *testing.T) {
- originalData := "data"
- overrideData := "new data"
- testInput := test.Input{
- Data: &originalData,
- }
- cfg, err1 := config.NewConfigFromString(fmt.Sprintf(yamlConfigDataOverride, overrideData))
- assert.NoError(t, err1)
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
- assert.Equal(t, overrideData, *testInput.Data, "`Data` should have been overridden")
-}
-
-func TestApplyInputOverrideStopMagic(t *testing.T) {
- overrideStopMagic := true
- testInput := test.Input{
- StopMagic: false,
- }
- cfg, err1 := config.NewConfigFromString(fmt.Sprintf(yamlConfigStopMagicOverride, overrideStopMagic))
- assert.NoError(t, err1)
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
- assert.Equal(t, overrideStopMagic, testInput.StopMagic, "`StopMagic` should have been overridden")
-}
-
-func TestApplyInputOverrideEncodedRequest(t *testing.T) {
- originalEncodedRequest := "originalbase64"
- overrideEncodedRequest := "modifiedbase64"
- testInput := test.Input{
- EncodedRequest: originalEncodedRequest,
- }
- cfg, err1 := config.NewConfigFromString(fmt.Sprintf(yamlConfigEncodedRequestOverride, overrideEncodedRequest))
- assert.NoError(t, err1)
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
- assert.Equal(t, overrideEncodedRequest, testInput.EncodedRequest, "`EncodedRequest` should have been overridden")
-}
-
-func TestApplyInputOverrideRAWRequest(t *testing.T) {
- originalRAWRequest := "original"
- overrideRAWRequest := "override"
- testInput := test.Input{
- RAWRequest: originalRAWRequest,
- }
- cfg, err1 := config.NewConfigFromString(fmt.Sprintf(yamlConfigRAWRequestOverride, overrideRAWRequest))
- assert.NoError(t, err1)
- err := applyInputOverride(cfg.TestOverride, &testInput)
- assert.NoError(t, err, "Failed to apply input overrides")
- assert.Equal(t, overrideRAWRequest, testInput.RAWRequest, "`RAWRequest` should have been overridden")
-}
-
-func TestIgnoredTestsRun(t *testing.T) {
- cfg, err := config.NewConfigFromString(yamlConfigIgnoreTests)
- dest, logFilePath := newTestServer(t, cfg, logText)
- assert.NoError(t, err)
-
- out := output.NewOutput("normal", os.Stdout)
-
- replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
- cfg.WithLogfile(logFilePath)
-
- ftwTest, err := test.GetTestFromYaml([]byte(yamlTest))
- assert.NoError(t, err)
-
- replaceDestinationInTest(&ftwTest, *dest)
-
- res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
- assert.NoError(t, err)
- assert.Equal(t, res.Stats.TotalFailed(), 1, "Oops, test run failed!")
+func (s *runTestSuite) TestIgnoredTestsRun() {
+ res, err := Run(s.cfg, s.ftwTests, RunnerConfig{}, s.out)
+ s.NoError(err)
+ s.Equal(res.Stats.TotalFailed(), 1, "Oops, test run failed!")
}
diff --git a/runner/testdata/TestBrokenOverrideRun.yaml b/runner/testdata/TestBrokenOverrideRun.yaml
new file mode 100644
index 0000000..ab6b84b
--- /dev/null
+++ b/runner/testdata/TestBrokenOverrideRun.yaml
@@ -0,0 +1,19 @@
+---
+meta:
+ author: "tester"
+ enabled: true
+ name: "TestBrokenOverrideRun.yaml"
+ description: "Example Override Test"
+tests:
+ - test_title: "001"
+ description: "access real external site"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Host: "{{ .TestAddr }}"
+ output:
+ expect_error: False
+ status: [200]
diff --git a/runner/testdata/TestBrokenPortOverrideRun.yaml b/runner/testdata/TestBrokenPortOverrideRun.yaml
new file mode 100644
index 0000000..b5dd26a
--- /dev/null
+++ b/runner/testdata/TestBrokenPortOverrideRun.yaml
@@ -0,0 +1,19 @@
+---
+meta:
+ author: "tester"
+ enabled: true
+ name: "TestBrokenPortOverrideRun.yaml"
+ description: "Example Override Test"
+tests:
+ - test_title: "001"
+ description: "access real external site"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Host: "{{ .TestAddr }}"
+ output:
+ expect_error: False
+ status: [200]
diff --git a/runner/testdata/TestCloudRun.yaml b/runner/testdata/TestCloudRun.yaml
new file mode 100644
index 0000000..7d7dedc
--- /dev/null
+++ b/runner/testdata/TestCloudRun.yaml
@@ -0,0 +1,106 @@
+---
+meta:
+ author: "tester"
+ enabled: true
+ name: "TestCloudRun.yaml"
+ description: "Example Test"
+tests:
+ - test_title: "001"
+ description: "access real external site"
+ stages:
+ - stage:
+ input:
+ uri: "/200"
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "{{ .TestAddr }}"
+ output:
+ expect_error: False
+ status: [200]
+ - test_title: "403"
+ description: "using log_contains should return a 403 response."
+ stages:
+ - stage:
+ input:
+ uri: "/403"
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "{{ .TestAddr }}"
+ output:
+ log_contains: "ModSecurity: Access denied with code 403"
+ - test_title: "405"
+ description: "using no_log_contains should return a 405 response."
+ stages:
+ - stage:
+ input:
+ uri: "/405"
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "{{ .TestAddr }}"
+ output:
+ no_log_contains: "ModSecurity: Access denied with code 403"
+ - test_title: "008"
+ description: "this test is number 8"
+ stages:
+ - stage:
+ input:
+ uri: "/200"
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "localhost"
+ output:
+ status: [200]
+ - test_title: "010"
+ stages:
+ - stage:
+ input:
+ uri: "/200"
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ version: "HTTP/1.1"
+ method: "OTHER"
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "localhost"
+ output:
+ response_contains: "Hello, client"
+ - test_title: "101"
+ description: "this tests exceptions (connection timeout)"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: 8090
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "none.host"
+ output:
+ expect_error: True
+ - test_title: "102"
+ description: "this tests exceptions (connection timeout)"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: 8090
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Host: "none.host"
+ Accept: "*/*"
+ encoded_request: "UE9TVCAvaW5kZXguaHRtbCBIVFRQLzEuMQ0KSG9zdDogMTkyLjE2OC4xLjIzDQpVc2VyLUFnZW50OiBjdXJsLzcuNDMuMA0KQWNjZXB0OiAqLyoNCkNvbnRlbnQtTGVuZ3RoOiA2NA0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQNCkNvbm5lY3Rpb246IGNsb3NlDQoNCmQ9MTsyOzM7NDs1XG4xO0BTVU0oMSsxKSpjbWR8JyBwb3dlcnNoZWxsIElFWCh3Z2V0IDByLnBlL3ApJ1whQTA7Mw=="
+ output:
+ expect_error: True
diff --git a/runner/testdata/TestDisabledRun.yaml b/runner/testdata/TestDisabledRun.yaml
new file mode 100644
index 0000000..0433bb9
--- /dev/null
+++ b/runner/testdata/TestDisabledRun.yaml
@@ -0,0 +1,19 @@
+---
+meta:
+ author: "tester"
+ enabled: false
+ name: "TestDisabledRun.yaml"
+ description: "we do not care, this test is disabled"
+tests:
+ - test_title: "001"
+ description: "access real external site"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Host: "{{ .TestAddr }}"
+ output:
+ status: [1234]
diff --git a/runner/testdata/TestFailedTestsRun.yaml b/runner/testdata/TestFailedTestsRun.yaml
new file mode 100644
index 0000000..54140df
--- /dev/null
+++ b/runner/testdata/TestFailedTestsRun.yaml
@@ -0,0 +1,21 @@
+---
+meta:
+ author: "tester"
+ enabled: true
+ name: "TestFailedTestsRun.yaml"
+ description: "Example Test"
+tests:
+ - test_title: "990"
+ description: test that fails
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ # -1 designates port value must be replaced by test setup
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "none.host"
+ output:
+ status: [413]
diff --git a/runner/testdata/TestIgnoredTestsRun.yaml b/runner/testdata/TestIgnoredTestsRun.yaml
new file mode 100644
index 0000000..8b5f5f9
--- /dev/null
+++ b/runner/testdata/TestIgnoredTestsRun.yaml
@@ -0,0 +1,75 @@
+---
+meta:
+ author: "tester"
+ enabled: true
+ name: "TestIgnoredTestsRun.yaml"
+ description: "Example Test"
+tests:
+ - test_title: "001"
+ description: "access real external site"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "{{ .TestAddr }}"
+ output:
+ expect_error: False
+ status: [200]
+ - test_title: "008"
+ description: "this test is number 8"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "localhost"
+ output:
+ status: [200]
+ - test_title: "010"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ version: "HTTP/1.1"
+ method: "OTHER"
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "localhost"
+ output:
+ response_contains: "Hello, client"
+ - test_title: "101"
+ description: "this tests exceptions (connection timeout)"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: 8090
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "none.host"
+ output:
+ expect_error: True
+ - test_title: "102"
+ description: "this tests exceptions (connection timeout)"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: 8090
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Host: "none.host"
+ Accept: "*/*"
+ encoded_request: "UE9TVCAvaW5kZXguaHRtbCBIVFRQLzEuMQ0KSG9zdDogMTkyLjE2OC4xLjIzDQpVc2VyLUFnZW50OiBjdXJsLzcuNDMuMA0KQWNjZXB0OiAqLyoNCkNvbnRlbnQtTGVuZ3RoOiA2NA0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQNCkNvbm5lY3Rpb246IGNsb3NlDQoNCmQ9MTsyOzM7NDs1XG4xO0BTVU0oMSsxKSpjbWR8JyBwb3dlcnNoZWxsIElFWCh3Z2V0IDByLnBlL3ApJ1whQTA7Mw=="
+ output:
+ expect_error: True
diff --git a/runner/testdata/TestLogsRun.yaml b/runner/testdata/TestLogsRun.yaml
new file mode 100644
index 0000000..5c62b86
--- /dev/null
+++ b/runner/testdata/TestLogsRun.yaml
@@ -0,0 +1,33 @@
+---
+meta:
+ author: "tester"
+ enabled: true
+ name: "TestLogsRun.yaml"
+ description: "Example Test"
+tests:
+ - test_title: "200"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ # -1 designates port value must be replaced by test setup
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "localhost"
+ output:
+ log_contains: id \"949110\"
+ - test_title: "201"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ # -1 designates port value must be replaced by test setup
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "localhost"
+ output:
+ no_log_contains: ABCDE
diff --git a/runner/testdata/TestOverrideRun.yaml b/runner/testdata/TestOverrideRun.yaml
new file mode 100644
index 0000000..08c448e
--- /dev/null
+++ b/runner/testdata/TestOverrideRun.yaml
@@ -0,0 +1,21 @@
+---
+meta:
+ author: "tester"
+ enabled: true
+ name: "TestOverrideRun.yaml"
+ description: "Example Override Test"
+tests:
+ - test_title: "001"
+ description: "access real external site"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ # -1 designates port value must be replaced by test setup
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Host: "{{ .TestAddr }}
+ output:
+ expect_error: False
+ status: [200]
diff --git a/runner/testdata/TestRunMultipleMatches.yaml b/runner/testdata/TestRunMultipleMatches.yaml
new file mode 100644
index 0000000..a9c64ab
--- /dev/null
+++ b/runner/testdata/TestRunMultipleMatches.yaml
@@ -0,0 +1,22 @@
+---
+meta:
+ author: "tester"
+ enabled: true
+ name: "TestRunMultipleMatches.yaml"
+ description: "Example Test with multiple expected outputs per single rule"
+tests:
+ - test_title: "001"
+ description: "access real external site"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ # -1 designates port value must be replaced by test setup
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "{{ .TestAddr }}"
+ output:
+ status: [200]
+ response_contains: "Not contains this"
diff --git a/runner/testdata/TestRunTests_Run.yaml b/runner/testdata/TestRunTests_Run.yaml
new file mode 100644
index 0000000..82b506f
--- /dev/null
+++ b/runner/testdata/TestRunTests_Run.yaml
@@ -0,0 +1,75 @@
+---
+meta:
+ author: "tester"
+ enabled: true
+ name: "TestRunTests_Run.yaml"
+ description: "Example Test"
+tests:
+ - test_title: "001"
+ description: "access real external site"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "{{ .TestAddr }}"
+ output:
+ expect_error: False
+ status: [200]
+ - test_title: "008"
+ description: "this test is number 8"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "localhost"
+ output:
+ status: [200]
+ - test_title: "010"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: {{ .TestPort }}
+ version: "HTTP/1.1"
+ method: "OTHER"
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "localhost"
+ output:
+ response_contains: "Hello, client"
+ - test_title: "101"
+ description: "this tests exceptions (connection timeout)"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: 8090
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Accept: "*/*"
+ Host: "none.host"
+ output:
+ expect_error: True
+ - test_title: "102"
+ description: "this tests exceptions (connection timeout)"
+ stages:
+ - stage:
+ input:
+ dest_addr: "{{ .TestAddr }}"
+ port: 8090
+ headers:
+ User-Agent: "ModSecurity CRS 3 Tests"
+ Host: "none.host"
+ Accept: "*/*"
+ encoded_request: "UE9TVCAvaW5kZXguaHRtbCBIVFRQLzEuMQ0KSG9zdDogMTkyLjE2OC4xLjIzDQpVc2VyLUFnZW50OiBjdXJsLzcuNDMuMA0KQWNjZXB0OiAqLyoNCkNvbnRlbnQtTGVuZ3RoOiA2NA0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQNCkNvbm5lY3Rpb246IGNsb3NlDQoNCmQ9MTsyOzM7NDs1XG4xO0BTVU0oMSsxKSpjbWR8JyBwb3dlcnNoZWxsIElFWCh3Z2V0IDByLnBlL3ApJ1whQTA7Mw=="
+ output:
+ expect_error: True
diff --git a/test/data_test.go b/test/data_test.go
index 7c60129..d452485 100644
--- a/test/data_test.go
+++ b/test/data_test.go
@@ -3,14 +3,22 @@ package test
import (
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
"github.com/goccy/go-yaml"
)
var repeatTestSprig = `foo=%3d++++++++++++++++++++++++++++++++++`
-func TestGetDataFromYAML(t *testing.T) {
+type dataTestSuite struct {
+ suite.Suite
+}
+
+func TestDataTestSuite(t *testing.T) {
+ suite.Run(t, new(dataTestSuite))
+}
+
+func (s *dataTestSuite) TestGetDataFromYAML() {
yamlString := `
dest_addr: "127.0.0.1"
method: "POST"
@@ -26,11 +34,11 @@ uri: "/"
`
input := Input{}
err := yaml.Unmarshal([]byte(yamlString), &input)
- assert.NoError(t, err)
- assert.True(t, input.StopMagic)
+ s.NoError(err)
+ s.True(input.StopMagic)
}
-func TestGetPartialDataFromYAML(t *testing.T) {
+func (s *dataTestSuite) TestGetPartialDataFromYAML() {
yamlString := `
dest_addr: "127.0.0.1"
method: ""
@@ -47,11 +55,11 @@ uri: "/"
`
input := Input{}
err := yaml.Unmarshal([]byte(yamlString), &input)
- assert.NoError(t, err)
- assert.Empty(t, *input.Version)
+ s.NoError(err)
+ s.Empty(*input.Version)
}
-func TestDataTemplateFromYAML(t *testing.T) {
+func (s *dataTestSuite) TestDataTemplateFromYAML() {
yamlString := `
dest_addr: "127.0.0.1"
method: ""
@@ -70,7 +78,7 @@ uri: "/"
var data []byte
err := yaml.Unmarshal([]byte(yamlString), &input)
- assert.NoError(t, err)
+ s.NoError(err)
data = input.ParseData()
- assert.Equal(t, []byte(repeatTestSprig), data)
+ s.Equal([]byte(repeatTestSprig), data)
}
diff --git a/test/defaults_test.go b/test/defaults_test.go
index 2afb507..074da0d 100644
--- a/test/defaults_test.go
+++ b/test/defaults_test.go
@@ -4,10 +4,19 @@ import (
"bytes"
"testing"
+ "github.com/stretchr/testify/suite"
+
"github.com/coreruleset/go-ftw/ftwhttp"
- "github.com/stretchr/testify/assert"
)
+type defaultsTestSuite struct {
+ suite.Suite
+}
+
+func TestDefaultsTestSuite(t *testing.T) {
+ suite.Run(t, new(defaultsTestSuite))
+}
+
func getTestInputDefaults() *Input {
data := "My Data"
@@ -73,54 +82,54 @@ User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0
return &inputTest
}
-func TestBasicGetters(t *testing.T) {
+func (s *defaultsTestSuite) TestBasicGetters() {
input := getTestExampleInput()
dest := input.GetDestAddr()
- assert.Equal(t, "192.168.0.1", dest)
+ s.Equal("192.168.0.1", dest)
method := input.GetMethod()
- assert.Equal(t, "REPORT", method)
+ s.Equal("REPORT", method)
version := input.GetVersion()
- assert.Equal(t, "HTTP/1.1", version)
+ s.Equal("HTTP/1.1", version)
port := input.GetPort()
- assert.Equal(t, 8080, port)
+ s.Equal(8080, port)
proto := input.GetProtocol()
- assert.Equal(t, "http", proto)
+ s.Equal("http", proto)
uri := input.GetURI()
- assert.Equal(t, "/test", uri)
+ s.Equal("/test", uri)
request, _ := input.GetRawRequest()
- assert.Equal(t, []byte("My Data\n"), request)
+ s.Equal([]byte("My Data\n"), request)
}
-func TestDefaultGetters(t *testing.T) {
+func (s *defaultsTestSuite) TestDefaultGetters() {
inputDefaults := getTestInputDefaults()
val := inputDefaults.GetDestAddr()
- assert.Equal(t, "localhost", val)
+ s.Equal("localhost", val)
val = inputDefaults.GetMethod()
- assert.Equal(t, "GET", val)
+ s.Equal("GET", val)
val = inputDefaults.GetVersion()
- assert.Equal(t, "HTTP/1.1", val)
+ s.Equal("HTTP/1.1", val)
port := inputDefaults.GetPort()
- assert.Equal(t, 80, port)
+ s.Equal(80, port)
val = inputDefaults.GetProtocol()
- assert.Equal(t, "http", val)
+ s.Equal("http", val)
val = inputDefaults.GetURI()
- assert.Equal(t, "/", val)
+ s.Equal("/", val)
- assert.Equal(t, []byte("My Data"), []byte(*inputDefaults.Data))
+ s.Equal([]byte("My Data"), []byte(*inputDefaults.Data))
}
-func TestRaw(t *testing.T) {
+func (s *defaultsTestSuite) TestRaw() {
raw := getRawInput()
- assert.True(t, raw.StopMagic)
+ s.True(raw.StopMagic)
request, _ := raw.GetRawRequest()
- assert.NotEqual(t, 2, bytes.Index(request, []byte("Acunetix")))
+ s.NotEqual(2, bytes.Index(request, []byte("Acunetix")))
}
diff --git a/test/errors_test.go b/test/errors_test.go
index d2a5eed..472930b 100644
--- a/test/errors_test.go
+++ b/test/errors_test.go
@@ -3,7 +3,7 @@ package test
import (
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
"github.com/coreruleset/go-ftw/utils"
)
@@ -44,12 +44,20 @@ var errorsTest = `---
no_log_contains: "id \"911100\""
`
-func TestGetLinesFromTestName(t *testing.T) {
+type errorsTestSuite struct {
+ suite.Suite
+}
+
+func TestErrorsTestSuite(t *testing.T) {
+ suite.Run(t, new(errorsTestSuite))
+}
+
+func (s *errorsTestSuite) TestGetLinesFromTestName() {
filename, _ := utils.CreateTempFileWithContent(errorsTest, "test-yaml-*")
tests, _ := GetTestsFromFiles(filename)
for _, ft := range tests {
line, _ := ft.GetLinesFromTest("911100-2")
- assert.Equal(t, 22, line, "Not getting the proper line.")
+ s.Equal(22, line, "Not getting the proper line.")
}
}
diff --git a/test/files_test.go b/test/files_test.go
index b192010..f8e26f3 100644
--- a/test/files_test.go
+++ b/test/files_test.go
@@ -4,8 +4,9 @@ import (
"regexp"
"testing"
+ "github.com/stretchr/testify/suite"
+
"github.com/coreruleset/go-ftw/utils"
- "github.com/stretchr/testify/assert"
)
var yamlTest = `
@@ -49,26 +50,34 @@ var wrongYamlTest = `
this is not yaml
`
-func TestGetTestFromYAML(t *testing.T) {
+type filesTestSuite struct {
+ suite.Suite
+}
+
+func TestFilesTestSuite(t *testing.T) {
+ suite.Run(t, new(filesTestSuite))
+}
+
+func (s *filesTestSuite) TestGetTestFromYAML() {
filename, _ := utils.CreateTempFileWithContent(yamlTest, "test-yaml-*")
tests, _ := GetTestsFromFiles(filename)
for _, ft := range tests {
- assert.Equal(t, filename, ft.FileName)
- assert.Equal(t, "tester", ft.Meta.Author)
- assert.Equal(t, "911100.yaml", ft.Meta.Name)
+ s.Equal(filename, ft.FileName)
+ s.Equal("tester", ft.Meta.Author)
+ s.Equal("911100.yaml", ft.Meta.Name)
re := regexp.MustCompile("911100*")
for _, test := range ft.Tests {
- assert.True(t, re.MatchString(test.TestTitle), "Can't read test title")
+ s.True(re.MatchString(test.TestTitle), "Can't read test title")
}
}
}
-func TestGetFromBadYAML(t *testing.T) {
+func (s *filesTestSuite) TestGetFromBadYAML() {
filename, _ := utils.CreateTempFileWithContent(wrongYamlTest, "test-yaml-*")
_, err := GetTestsFromFiles(filename)
- assert.NotNil(t, err, "reading yaml should fail")
+ s.Error(err, "reading yaml should fail")
}
diff --git a/utils/empty_test.go b/utils/empty_test.go
index 5598449..fc578be 100644
--- a/utils/empty_test.go
+++ b/utils/empty_test.go
@@ -3,50 +3,58 @@ package utils
import (
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
)
-func TestIsEmpty(t *testing.T) {
+type emptyTestSuite struct {
+ suite.Suite
+}
+
+func TestEmptyTestSuite(t *testing.T) {
+ suite.Run(t, new(emptyTestSuite))
+}
+
+func (s *emptyTestSuite) TestIsEmpty() {
data := ""
- assert.True(t, IsEmpty(data))
+ s.True(IsEmpty(data))
}
-func TestIsEmptyStringPointer(t *testing.T) {
+func (s *emptyTestSuite) TestIsEmptyStringPointer() {
var empty *string = nil
- assert.True(t, IsEmpty(empty))
+ s.True(IsEmpty(empty))
}
-func TestIsEmptyByte(t *testing.T) {
+func (s *emptyTestSuite) TestIsEmptyByte() {
data := []byte{}
- assert.True(t, IsEmpty(data))
+ s.True(IsEmpty(data))
}
-func TestIsNotEmpty(t *testing.T) {
+func (s *emptyTestSuite) TestIsNotEmpty() {
data := "Not Empty"
- assert.True(t, IsNotEmpty(data))
+ s.True(IsNotEmpty(data))
}
-func TestIsNotEmptyByte(t *testing.T) {
+func (s *emptyTestSuite) TestIsNotEmptyByte() {
data := []byte("Not Empty")
- assert.True(t, IsNotEmpty(data))
+ s.True(IsNotEmpty(data))
}
-func TestStringPEmpty(t *testing.T) {
- var s *string
- assert.True(t, IsEmpty(s))
+func (s *emptyTestSuite) TestStringPEmpty() {
+ var str *string
+ s.True(IsEmpty(str))
}
-func TestStringPNotEmpty(t *testing.T) {
- s := string("Empty")
- assert.True(t, IsNotEmpty(&s))
+func (s *emptyTestSuite) TestStringPNotEmpty() {
+ str := string("Empty")
+ s.True(IsNotEmpty(&str))
}
-func TestAnythingNotEmpty(t *testing.T) {
+func (s *emptyTestSuite) TestAnythingNotEmpty() {
data := make([]int, 1, 2)
- assert.False(t, IsEmpty(data))
+ s.False(IsEmpty(data))
}
-func TestAnythingEmpty(t *testing.T) {
+func (s *emptyTestSuite) TestAnythingEmpty() {
data := make([]int, 1, 2)
- assert.False(t, IsNotEmpty(data), "[]int is not implemented so it should return false")
+ s.False(IsNotEmpty(data), "[]int is not implemented so it should return false")
}
diff --git a/utils/tests_test.go b/utils/tests_test.go
index 8da2e35..c7c8c29 100644
--- a/utils/tests_test.go
+++ b/utils/tests_test.go
@@ -4,23 +4,31 @@ import (
"os"
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
)
var content = `This is the content`
-func TestCreateTempFile(t *testing.T) {
+type testFilesTestSuite struct {
+ suite.Suite
+}
+
+func TestFilesTestSuite(t *testing.T) {
+ suite.Run(t, new(testFilesTestSuite))
+}
+
+func (s *testFilesTestSuite) TestCreateTempFile() {
filename, err := CreateTempFileWithContent(content, "test-content-*")
// Remember to clean up the file afterwards
defer os.Remove(filename)
- assert.NoError(t, err)
+ s.NoError(err)
}
-func TestCreateBadTempFile(t *testing.T) {
+func (s *testFilesTestSuite) TestCreateBadTempFile() {
filename, err := CreateTempFileWithContent(content, "/dev/null/*")
// Remember to clean up the file afterwards
defer os.Remove(filename)
- assert.NotNil(t, err)
+ s.Error(err)
}
diff --git a/utils/time_test.go b/utils/time_test.go
index 9c0ae0a..b197a05 100644
--- a/utils/time_test.go
+++ b/utils/time_test.go
@@ -3,11 +3,19 @@ package utils
import (
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
)
-func TestGetFormattedTime(t *testing.T) {
+type timeTestSuite struct {
+ suite.Suite
+}
+
+func TestTimeTestSuite(t *testing.T) {
+ suite.Run(t, new(timeTestSuite))
+}
+
+func (s *timeTestSuite) TestGetFormattedTime() {
ftm := GetFormattedTime("2021-01-05T00:30:26.371Z")
- assert.Equal(t, 2021, ftm.Year())
+ s.Equal(2021, ftm.Year())
}
diff --git a/waflog/read_test.go b/waflog/read_test.go
index d139903..f49b15c 100644
--- a/waflog/read_test.go
+++ b/waflog/read_test.go
@@ -7,16 +7,29 @@ import (
"strings"
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
"github.com/coreruleset/go-ftw/config"
"github.com/coreruleset/go-ftw/utils"
)
-func TestReadCheckLogForMarkerNoMarkerAtEnd(t *testing.T) {
+type readTestSuite struct {
+ suite.Suite
+ filename string
+}
+
+func TestReadTestSuite(t *testing.T) {
+ suite.Run(t, new(readTestSuite))
+}
+
+func (s *readTestSuite) TearDownSuite() {
+ os.Remove(s.filename)
+}
+
+func (s *readTestSuite) TestReadCheckLogForMarkerNoMarkerAtEnd() {
cfg, err := config.NewConfigFromEnv()
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+ s.NoError(err)
+ s.NotNil(cfg)
stageID := "dead-beaf-deadbeef-deadbeef-dead"
markerLine := "X-cRs-TeSt: " + stageID
@@ -26,23 +39,22 @@ func TestReadCheckLogForMarkerNoMarkerAtEnd(t *testing.T) {
[Tue Jan 05 02:21:09.637731 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Match of "pm AppleWebKit Android" against "REQUEST_HEADERS:User-Agent" required. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1230"] [id "920300"] [msg "Request Missing an Accept Header"] [severity "NOTICE"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [tag "PCI/6.5.10"] [tag "paranoia-level/2"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
[Tue Jan 05 02:21:09.638572 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:anomaly_score. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "91"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
`
- filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
- assert.NoError(t, err)
+ s.filename, err = utils.CreateTempFileWithContent(logLines, "test-errorlog-")
+ s.NoError(err)
- cfg.LogFile = filename
- t.Cleanup(func() { os.Remove(filename) })
+ cfg.LogFile = s.filename
ll, err := NewFTWLogLines(cfg)
- assert.NoError(t, err)
+ s.NoError(err)
ll.WithStartMarker([]byte(markerLine))
marker := ll.CheckLogForMarker(stageID, 100)
- assert.Equal(t, string(marker), strings.ToLower(markerLine), "unexpectedly found marker")
+ s.Equal(string(marker), strings.ToLower(markerLine), "unexpectedly found marker")
}
-func TestReadCheckLogForMarkerWithMarkerAtEnd(t *testing.T) {
+func (s *readTestSuite) TestReadCheckLogForMarkerWithMarkerAtEnd() {
cfg, err := config.NewConfigFromEnv()
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+ s.NoError(err)
+ s.NotNil(cfg)
stageID := "dead-beaf-deadbeef-deadbeef-dead"
markerLine := "X-cRs-TeSt: " + stageID
@@ -51,26 +63,25 @@ func TestReadCheckLogForMarkerWithMarkerAtEnd(t *testing.T) {
[Tue Jan 05 02:21:09.637731 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Match of "pm AppleWebKit Android" against "REQUEST_HEADERS:User-Agent" required. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1230"] [id "920300"] [msg "Request Missing an Accept Header"] [severity "NOTICE"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [tag "PCI/6.5.10"] [tag "paranoia-level/2"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
[Tue Jan 05 02:21:09.638572 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:anomaly_score. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "91"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
` + markerLine
- filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
- assert.NoError(t, err)
+ s.filename, err = utils.CreateTempFileWithContent(logLines, "test-errorlog-")
+ s.NoError(err)
- cfg.LogFile = filename
- t.Cleanup(func() { os.Remove(filename) })
+ cfg.LogFile = s.filename
ll, err := NewFTWLogLines(cfg)
ll.WithStartMarker([]byte(markerLine))
- assert.NoError(t, err)
+ s.NoError(err)
marker := ll.CheckLogForMarker(stageID, 100)
- assert.NotNil(t, marker, "no marker found")
+ s.NotNil(marker, "no marker found")
- assert.Equal(t, marker, bytes.ToLower([]byte(markerLine)), "found unexpected marker")
+ s.Equal(marker, bytes.ToLower([]byte(markerLine)), "found unexpected marker")
}
-func TestReadGetMarkedLines(t *testing.T) {
+func (s *readTestSuite) TestReadGetMarkedLines() {
cfg, err := config.NewConfigFromEnv()
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+ s.NoError(err)
+ s.NotNil(cfg)
stageID := "dead-beaf-deadbeef-deadbeef-dead"
startMarkerLine := "X-cRs-TeSt: " + stageID + " -start"
@@ -80,14 +91,13 @@ func TestReadGetMarkedLines(t *testing.T) {
[Tue Jan 05 02:21:09.637731 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Match of "pm AppleWebKit Android" against "REQUEST_HEADERS:User-Agent" required. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1230"] [id "920300"] [msg "Request Missing an Accept Header"] [severity "NOTICE"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [tag "PCI/6.5.10"] [tag "paranoia-level/2"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
[Tue Jan 05 02:21:09.638572 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:anomaly_score. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "91"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]`
logLines := fmt.Sprintf("%s\n%s\n%s", startMarkerLine, logLinesOnly, endMarkerLine)
- filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
- assert.NoError(t, err)
+ s.filename, err = utils.CreateTempFileWithContent(logLines, "test-errorlog-")
+ s.NoError(err)
- cfg.LogFile = filename
- t.Cleanup(func() { os.Remove(filename) })
+ cfg.LogFile = s.filename
ll, err := NewFTWLogLines(cfg)
- assert.NoError(t, err)
+ s.NoError(err)
ll.WithStartMarker(bytes.ToLower([]byte(startMarkerLine)))
ll.WithEndMarker(bytes.ToLower([]byte(endMarkerLine)))
@@ -98,17 +108,17 @@ func TestReadGetMarkedLines(t *testing.T) {
foundLines[i], foundLines[j] = foundLines[j], foundLines[i]
}
- assert.Equal(t, len(foundLines), 3, "found unexpected number of log lines")
+ s.Equal(len(foundLines), 3, "found unexpected number of log lines")
for index, line := range strings.Split(logLinesOnly, "\n") {
- assert.Equalf(t, foundLines[index], []byte(line), "log lines don't match: \n%s\n%s", line, string(foundLines[index]))
+ s.Equalf(foundLines[index], []byte(line), "log lines don't match: \n%s\n%s", line, string(foundLines[index]))
}
}
-func TestReadGetMarkedLinesWithTrailingEmptyLines(t *testing.T) {
+func (s *readTestSuite) TestReadGetMarkedLinesWithTrailingEmptyLines() {
cfg, err := config.NewConfigFromEnv()
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+ s.NoError(err)
+ s.NotNil(cfg)
stageID := "dead-beaf-deadbeef-deadbeef-dead"
startMarkerLine := "X-cRs-TeSt: " + stageID + " -start"
@@ -118,14 +128,13 @@ func TestReadGetMarkedLinesWithTrailingEmptyLines(t *testing.T) {
[Tue Jan 05 02:21:09.637731 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Match of "pm AppleWebKit Android" against "REQUEST_HEADERS:User-Agent" required. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1230"] [id "920300"] [msg "Request Missing an Accept Header"] [severity "NOTICE"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [tag "PCI/6.5.10"] [tag "paranoia-level/2"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
[Tue Jan 05 02:21:09.638572 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:anomaly_score. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "91"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]`
logLines := fmt.Sprintf("%s\n%s\n%s\n\n\n", startMarkerLine, logLinesOnly, endMarkerLine)
- filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
- assert.NoError(t, err)
+ s.filename, err = utils.CreateTempFileWithContent(logLines, "test-errorlog-")
+ s.NoError(err)
- cfg.LogFile = filename
- t.Cleanup(func() { os.Remove(filename) })
+ cfg.LogFile = s.filename
ll, err := NewFTWLogLines(cfg)
- assert.NoError(t, err)
+ s.NoError(err)
ll.WithStartMarker(bytes.ToLower([]byte(startMarkerLine)))
ll.WithEndMarker(bytes.ToLower([]byte(endMarkerLine)))
@@ -136,17 +145,17 @@ func TestReadGetMarkedLinesWithTrailingEmptyLines(t *testing.T) {
foundLines[i], foundLines[j] = foundLines[j], foundLines[i]
}
- assert.Len(t, foundLines, 6, "found unexpected number of log lines")
+ s.Len(foundLines, 6, "found unexpected number of log lines")
for index, line := range strings.Split(logLinesOnly, "\n") {
- assert.Equalf(t, foundLines[index], []byte(line), "log lines don't match: \n%s\n%s", line, string(foundLines[index]))
+ s.Equalf(foundLines[index], []byte(line), "log lines don't match: \n%s\n%s", line, string(foundLines[index]))
}
}
-func TestReadGetMarkedLinesWithPrecedingLines(t *testing.T) {
+func (s *readTestSuite) TestReadGetMarkedLinesWithPrecedingLines() {
cfg, err := config.NewConfigFromEnv()
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+ s.NoError(err)
+ s.NotNil(cfg)
stageID := "dead-beaf-deadbeef-deadbeef-dead"
startMarkerLine := "X-cRs-TeSt: " + stageID + " -start"
@@ -159,14 +168,13 @@ func TestReadGetMarkedLinesWithPrecedingLines(t *testing.T) {
[Tue Jan 05 02:21:09.637731 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Match of "pm AppleWebKit Android" against "REQUEST_HEADERS:User-Agent" required. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1230"] [id "920300"] [msg "Request Missing an Accept Header"] [severity "NOTICE"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [tag "PCI/6.5.10"] [tag "paranoia-level/2"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
[Tue Jan 05 02:21:09.638572 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:anomaly_score. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "91"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]`
logLines := fmt.Sprintf("%s\n%s\n%s\n%s\n", precedingLines, startMarkerLine, logLinesOnly, endMarkerLine)
- filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
- assert.NoError(t, err)
+ s.filename, err = utils.CreateTempFileWithContent(logLines, "test-errorlog-")
+ s.NoError(err)
- cfg.LogFile = filename
- t.Cleanup(func() { os.Remove(filename) })
+ cfg.LogFile = s.filename
ll, err := NewFTWLogLines(cfg)
- assert.NoError(t, err)
+ s.NoError(err)
ll.WithStartMarker(bytes.ToLower([]byte(startMarkerLine)))
ll.WithEndMarker(bytes.ToLower([]byte(endMarkerLine)))
@@ -177,17 +185,17 @@ func TestReadGetMarkedLinesWithPrecedingLines(t *testing.T) {
foundLines[i], foundLines[j] = foundLines[j], foundLines[i]
}
- assert.Len(t, foundLines, 4, "found unexpected number of log lines")
+ s.Len(foundLines, 4, "found unexpected number of log lines")
for index, line := range strings.Split(logLinesOnly, "\n") {
- assert.Equalf(t, foundLines[index], []byte(line), "log lines don't match: \n%s\n%s", line, string(foundLines[index]))
+ s.Equalf(foundLines[index], []byte(line), "log lines don't match: \n%s\n%s", line, string(foundLines[index]))
}
}
-func TestFTWLogLines_Contains(t *testing.T) {
+func (s *readTestSuite) TestFTWLogLines_Contains() {
cfg, err := config.NewConfigFromEnv()
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+ s.NoError(err)
+ s.NotNil(cfg)
stageID := "dead-beaf-deadbeef-deadbeef-dead"
markerLine := "X-cRs-TeSt: " + stageID
@@ -196,14 +204,12 @@ func TestFTWLogLines_Contains(t *testing.T) {
[Tue Jan 05 02:21:09.637731 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Match of "pm AppleWebKit Android" against "REQUEST_HEADERS:User-Agent" required. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1230"] [id "920300"] [msg "Request Missing an Accept Header"] [severity "NOTICE"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [tag "PCI/6.5.10"] [tag "paranoia-level/2"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
[Tue Jan 05 02:21:09.638572 2021] [:error] [pid 76:tid 139683434571520] [client 172.23.0.1:58998] [client 172.23.0.1] ModSecurity: Warning. Operator GE matched 5 at TX:anomaly_score. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "91"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "localhost"] [uri "/"] [unique_id "X-PNFSe1VwjCgYRI9FsbHgAAAIY"]
` + markerLine
- filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
- assert.NoError(t, err)
+ s.filename, err = utils.CreateTempFileWithContent(logLines, "test-errorlog-")
+ s.NoError(err)
- cfg.LogFile = filename
- log, err := os.Open(filename)
- assert.NoError(t, err)
-
- t.Cleanup(func() { os.Remove(filename) })
+ cfg.LogFile = s.filename
+ log, err := os.Open(s.filename)
+ s.NoError(err)
type fields struct {
logFile *os.File
@@ -245,7 +251,7 @@ func TestFTWLogLines_Contains(t *testing.T) {
},
}
for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
+ s.Run(tt.name, func() {
ll := &FTWLogLines{
logFile: tt.fields.logFile,
LogMarkerHeaderName: bytes.ToLower(tt.fields.LogMarkerHeaderName),
@@ -253,15 +259,15 @@ func TestFTWLogLines_Contains(t *testing.T) {
EndMarker: bytes.ToLower(tt.fields.EndMarker),
}
got := ll.Contains(tt.args.match)
- assert.Equalf(t, tt.want, got, "Contains() = %v, want %v", got, tt.want)
+ s.Equalf(tt.want, got, "Contains() = %v, want %v", got, tt.want)
})
}
}
-func TestFTWLogLines_ContainsIn404(t *testing.T) {
+func (s *readTestSuite) TestFTWLogLines_ContainsIn404() {
cfg, err := config.NewConfigFromEnv()
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+ s.NoError(err)
+ s.NotNil(cfg)
stageID := "dead-beaf-deadbeef-deadbeef-dead"
markerLine := fmt.Sprint(`[2022-11-12 23:08:18.012572] [-:error] 127.0.0.1:36126 Y3AZUo3Gja4gB-tPE9uasgAAAA4 [client 127.0.0.1] ModSecurity: Warning. Unconditional match in SecAction. [file "/apache/conf/httpd.conf_pod_2022-11-12_22:23"] [line "265"] [id "999999"] [msg "`,
@@ -272,13 +278,11 @@ func TestFTWLogLines_ContainsIn404(t *testing.T) {
`[2022-11-12 23:08:18.013007] [core:info] 127.0.0.1:36126 Y3AZUo3Gja4gB-tPE9uasgAAAA4 AH00128: File does not exist: /apache/htdocs/status/200`,
"\n", markerLine)
filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
- assert.NoError(t, err)
+ s.NoError(err)
cfg.LogFile = filename
log, err := os.Open(filename)
- assert.NoError(t, err)
-
- t.Cleanup(func() { os.Remove(filename) })
+ s.NoError(err)
type fields struct {
logFile *os.File
@@ -312,7 +316,7 @@ func TestFTWLogLines_ContainsIn404(t *testing.T) {
},
}
for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
+ s.Run(tt.name, func() {
ll := &FTWLogLines{
logFile: tt.fields.logFile,
LogMarkerHeaderName: bytes.ToLower(tt.fields.LogMarkerHeaderName),
@@ -320,15 +324,15 @@ func TestFTWLogLines_ContainsIn404(t *testing.T) {
EndMarker: bytes.ToLower(tt.fields.EndMarker),
}
got := ll.Contains(tt.args.match)
- assert.Equalf(t, tt.want, got, "Contains() = %v, want %v", got, tt.want)
+ s.Equalf(tt.want, got, "Contains() = %v, want %v", got, tt.want)
})
}
}
-func TestFTWLogLines_CheckForLogMarkerIn404(t *testing.T) {
+func (s *readTestSuite) TestFTWLogLines_CheckForLogMarkerIn404() {
cfg, err := config.NewConfigFromEnv()
- assert.NoError(t, err)
- assert.NotNil(t, cfg)
+ s.NoError(err)
+ s.NotNil(cfg)
stageID := "dead-beaf-deadbeef-deadbeef-dead"
markerLine := fmt.Sprint(`[2022-11-12 23:08:18.012572] [-:error] 127.0.0.1:36126 Y3AZUo3Gja4gB-tPE9uasgAAAA4 [client 127.0.0.1] ModSecurity: Warning. Unconditional match in SecAction. [file "/apache/conf/httpd.conf_pod_2022-11-12_22:23"] [line "265"] [id "999999"] [msg "`,
@@ -339,13 +343,11 @@ func TestFTWLogLines_CheckForLogMarkerIn404(t *testing.T) {
`[2022-11-12 23:08:18.013007] [core:info] 127.0.0.1:36126 Y3AZUo3Gja4gB-tPE9uasgAAAA4 AH00128: File does not exist: /apache/htdocs/status/200`,
"\n", markerLine)
filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
- assert.NoError(t, err)
+ s.NoError(err)
cfg.LogFile = filename
log, err := os.Open(filename)
- assert.NoError(t, err)
-
- t.Cleanup(func() { os.Remove(filename) })
+ s.NoError(err)
ll := &FTWLogLines{
logFile: log,
@@ -354,5 +356,5 @@ func TestFTWLogLines_CheckForLogMarkerIn404(t *testing.T) {
EndMarker: bytes.ToLower([]byte(markerLine)),
}
foundMarker := ll.CheckLogForMarker(stageID, 100)
- assert.Equal(t, strings.ToLower(markerLine), strings.ToLower(string(foundMarker)))
+ s.Equal(strings.ToLower(markerLine), strings.ToLower(string(foundMarker)))
}
diff --git a/waflog/waflog_test.go b/waflog/waflog_test.go
index 3495237..ae77405 100644
--- a/waflog/waflog_test.go
+++ b/waflog/waflog_test.go
@@ -3,22 +3,30 @@ package waflog
import (
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
"github.com/coreruleset/go-ftw/config"
)
-func TestNewFTWLogLines(t *testing.T) {
+type waflogTestSuite struct {
+ suite.Suite
+}
+
+func TestWafLogTestSuite(t *testing.T) {
+ suite.Run(t, new(waflogTestSuite))
+}
+
+func (s *waflogTestSuite) TestNewFTWLogLines() {
cfg := config.NewDefaultConfig()
- assert.NotNil(t, cfg)
+ s.NotNil(cfg)
// Don't call NewFTWLogLines to avoid opening the file.
ll := &FTWLogLines{}
ll.WithStartMarker([]byte("#"))
ll.WithEndMarker([]byte("#"))
- assert.NotNil(t, ll.StartMarker, "Failed! StartMarker must be set")
- assert.NotNil(t, ll.EndMarker, "Failed! EndMarker must be set")
+ s.NotNil(ll.StartMarker, "Failed! StartMarker must be set")
+ s.NotNil(ll.EndMarker, "Failed! EndMarker must be set")
err := ll.Cleanup()
- assert.NoError(t, err)
+ s.NoError(err)
}