Pārlūkot izejas kodu

CI: refactoring pkg/csplugin tests (#2247)

mmetc 2 gadi atpakaļ
vecāks
revīzija
12c32d507c

+ 1 - 1
go.mod

@@ -71,7 +71,7 @@ require (
 	github.com/bluele/gcache v0.0.2
 	github.com/cespare/xxhash/v2 v2.1.2
 	github.com/coreos/go-systemd/v22 v22.5.0
-	github.com/crowdsecurity/go-cs-lib v0.0.0-20230522120244-fa545c12e7ee
+	github.com/crowdsecurity/go-cs-lib v0.0.0-20230531105801-4c1535c2b3bd
 	github.com/goccy/go-yaml v1.9.7
 	github.com/gofrs/uuid v4.0.0+incompatible
 	github.com/golang-jwt/jwt/v4 v4.2.0

+ 2 - 2
go.sum

@@ -172,8 +172,8 @@ github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
 github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26 h1:r97WNVC30Uen+7WnLs4xDScS/Ex988+id2k6mDf8psU=
 github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26/go.mod h1:zpv7r+7KXwgVUZnUNjyP22zc/D7LKjyoY02weH2RBbk=
-github.com/crowdsecurity/go-cs-lib v0.0.0-20230522120244-fa545c12e7ee h1:YD/SYJ0otjlyAxuLRzJNXTQyXcpccefTvaHextBT9mQ=
-github.com/crowdsecurity/go-cs-lib v0.0.0-20230522120244-fa545c12e7ee/go.mod h1:9JJLSpGj1ZXnROV3xAcJvS/HTaUvuA8K3gGOpO4tfVc=
+github.com/crowdsecurity/go-cs-lib v0.0.0-20230531105801-4c1535c2b3bd h1:Y70ceDKAKYFXTnxEjXuBDSh07umvDhbX3PCCYhdtsZ0=
+github.com/crowdsecurity/go-cs-lib v0.0.0-20230531105801-4c1535c2b3bd/go.mod h1:9JJLSpGj1ZXnROV3xAcJvS/HTaUvuA8K3gGOpO4tfVc=
 github.com/crowdsecurity/grokky v0.2.1 h1:t4VYnDlAd0RjDM2SlILalbwfCrQxtJSMGdQOR0zwkE4=
 github.com/crowdsecurity/grokky v0.2.1/go.mod h1:33usDIYzGDsgX1kHAThCbseso6JuWNJXOzRQDGXHtWM=
 github.com/crowdsecurity/machineid v1.0.2 h1:wpkpsUghJF8Khtmn/tg6GxgdhLA1Xflerh5lirI+bdc=

+ 0 - 17
pkg/csplugin/broker.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"io"
 	"os"
-	"path/filepath"
 	"strings"
 	"sync"
 	"text/template"
@@ -375,22 +374,6 @@ func setRequiredFields(pluginCfg *PluginConfig) {
 
 }
 
-// helper which gives paths to all files in the given directory non-recursively
-func listFilesAtPath(path string) ([]string, error) {
-	filePaths := make([]string, 0)
-	files, err := os.ReadDir(path)
-	if err != nil {
-		return nil, err
-	}
-	for _, file := range files {
-		if file.IsDir() {
-			continue
-		}
-		filePaths = append(filePaths, filepath.Join(path, file.Name()))
-	}
-	return filePaths, nil
-}
-
 func getUUID() (string, error) {
 	uuidv4, err := uuid.NewRandom()
 	if err != nil {

+ 164 - 0
pkg/csplugin/broker_suite_test.go

@@ -0,0 +1,164 @@
+package csplugin
+
+import (
+	"io"
+	"os"
+	"os/exec"
+	"path"
+	"runtime"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"github.com/stretchr/testify/suite"
+
+	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
+)
+
+
+type PluginSuite struct {
+	suite.Suite
+
+	// where the plugin is built - temporary directory for the suite
+	buildDir string
+	// full path to the built plugin binary
+	builtBinary string
+
+	runDir string		// temporary directory for each test
+	pluginDir  string	// (config_paths.plugin_dir)
+	notifDir string		// (config_paths.notification_dir)
+	pluginBinary string	// full path to the plugin binary (unique for each test)
+	pluginConfig string	// full path to the notification config (unique for each test)
+
+	pluginBroker *PluginBroker
+}
+
+
+func TestPluginSuite(t *testing.T) {
+	suite.Run(t, new(PluginSuite))
+}
+
+
+func (s *PluginSuite) SetupSuite() {
+	var err error
+
+	t := s.T()
+
+	s.buildDir, err = os.MkdirTemp("", "cs_plugin_test_build")
+	require.NoError(t, err)
+
+	s.builtBinary = path.Join(s.buildDir, "notification-dummy")
+
+	if runtime.GOOS == "windows" {
+		s.builtBinary += ".exe"
+	}
+
+	cmd := exec.Command("go", "build", "-o", s.builtBinary, "../../plugins/notifications/dummy/")
+	err = cmd.Run()
+	require.NoError(t, err, "while building dummy plugin")
+}
+
+
+func (s *PluginSuite) TearDownSuite() {
+	t := s.T()
+	err := os.RemoveAll(s.buildDir)
+	require.NoError(t, err)
+}
+
+
+func copyFile(src string, dst string) error {
+	s, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	defer s.Close()
+
+	d, err := os.Create(dst)
+	if err != nil {
+		return err
+	}
+	defer d.Close()
+
+	_, err = io.Copy(d, s)
+	if err != nil {
+		return err
+	}
+
+	err = d.Sync()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *PluginSuite) SetupTest() {
+	s.SetupSubTest()
+}
+
+func (s *PluginSuite) TearDownTest() {
+	s.TearDownSubTest()
+}
+
+
+func (s *PluginSuite) SetupSubTest() {
+	var err error
+	t := s.T()
+
+	s.runDir, err = os.MkdirTemp("", "cs_plugin_test")
+	require.NoError(t, err)
+
+	s.pluginDir = path.Join(s.runDir, "bin")
+	err = os.MkdirAll(path.Join(s.runDir, "bin"), 0o755)
+	require.NoError(t, err, "while creating bin dir")
+
+	s.notifDir = path.Join(s.runDir, "config")
+	err = os.MkdirAll(s.notifDir, 0o755)
+	require.NoError(t, err, "while creating config dir")
+
+	s.pluginBinary = path.Join(s.pluginDir, "notification-dummy")
+
+	if runtime.GOOS == "windows" {
+		s.pluginBinary += ".exe"
+	}
+
+	err = copyFile(s.builtBinary, s.pluginBinary)
+	require.NoError(t, err, "while copying built binary")
+	err = os.Chmod(s.pluginBinary, 0o744)
+	require.NoError(t, err, "chmod 0744 %s", s.pluginBinary)
+	
+	s.pluginConfig = path.Join(s.notifDir, "dummy.yaml")
+	err = copyFile("testdata/dummy.yaml", s.pluginConfig)
+	require.NoError(t, err, "while copying plugin config")
+}
+
+func (s *PluginSuite) TearDownSubTest() {
+	t := s.T()
+	if s.pluginBroker != nil {
+		s.pluginBroker.Kill()
+		s.pluginBroker = nil
+	}
+
+	err := os.RemoveAll(s.runDir)
+	if runtime.GOOS != "windows" {
+		require.NoError(t, err)
+	}
+
+	os.Remove("./out")
+}
+
+func (s *PluginSuite) InitBroker(procCfg *csconfig.PluginCfg) (*PluginBroker, error) {
+	pb := PluginBroker{}
+	if procCfg == nil {
+		procCfg = &csconfig.PluginCfg{}
+	}
+	profiles := csconfig.NewDefaultConfig().API.Server.Profiles
+	profiles = append(profiles, &csconfig.ProfileCfg{
+		Notifications: []string{"dummy_default"},
+	})
+	err := pb.Init(procCfg, profiles, &csconfig.ConfigurationPaths{
+		PluginDir:       s.pluginDir,
+		NotificationDir: s.notifDir,
+	})
+	s.pluginBroker = &pb
+	return s.pluginBroker, err
+}

+ 76 - 323
pkg/csplugin/broker_test.go

@@ -5,11 +5,6 @@ package csplugin
 import (
 	"encoding/json"
 	"os"
-	"os/exec"
-	"path"
-	"path/filepath"
-	"reflect"
-	"runtime"
 	"testing"
 	"time"
 
@@ -25,116 +20,39 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/models"
 )
 
-var testPath string
 
-func setPluginPermTo744(t *testing.T) {
-	setPluginPermTo(t, "744")
+func (s *PluginSuite) permissionSetter(perm os.FileMode) func(*testing.T) {
+	return func(t *testing.T) {
+		err := os.Chmod(s.pluginBinary, perm)
+		require.NoError(t, err, "chmod %s %s", perm, s.pluginBinary)
+	}
 }
 
-func setPluginPermTo722(t *testing.T) {
-	setPluginPermTo(t, "722")
-}
+func (s *PluginSuite) readconfig() (PluginConfig) {
+	var config PluginConfig
+	t := s.T()
 
-func setPluginPermTo724(t *testing.T) {
-	setPluginPermTo(t, "724")
-}
-func TestGetPluginNameAndTypeFromPath(t *testing.T) {
-	setUp(t)
-	defer tearDown(t)
-	type args struct {
-		path string
-	}
-	tests := []struct {
-		name    string
-		args    args
-		want    string
-		want1   string
-		expectedErr string
-	}{
-		{
-			name: "valid plugin name, single dash",
-			args: args{
-				path: path.Join(testPath, "notification-gitter"),
-			},
-			want:    "notification",
-			want1:   "gitter",
-		},
-		{
-			name: "invalid plugin name",
-			args: args{
-				path: "./tests/gitter",
-			},
-			expectedErr: "plugin name ./tests/gitter is invalid. Name should be like {type-name}",
-		},
-		{
-			name: "valid plugin name, multiple dash",
-			args: args{
-				path: "./tests/notification-instant-slack",
-			},
-			want:    "notification-instant",
-			want1:   "slack",
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			got, got1, err := getPluginTypeAndSubtypeFromPath(tc.args.path)
-			cstest.RequireErrorContains(t, err, tc.expectedErr)
+	orig, err := os.ReadFile(s.pluginConfig)
+	require.NoError(t, err,"unable to read config file %s", s.pluginConfig)
 
-			assert.Equal(t, tc.want, got)
-			assert.Equal(t, tc.want1, got1)
-		})
-	}
+	err = yaml.Unmarshal(orig, &config)
+	require.NoError(t, err,"unable to unmarshal config file")
+	
+	return config
 }
 
-func TestListFilesAtPath(t *testing.T) {
-	setUp(t)
-	defer tearDown(t)
-	type args struct {
-		path string
-	}
-	tests := []struct {
-		name    string
-		args    args
-		want    []string
-		expectedErr string
-	}{
-		{
-			name: "valid directory",
-			args: args{
-				path: testPath,
-			},
-			want: []string{
-				filepath.Join(testPath, "notification-gitter"),
-				filepath.Join(testPath, "slack"),
-			},
-		},
-		{
-			name: "invalid directory",
-			args: args{
-				path: "./foo/bar/",
-			},
-			expectedErr: "open ./foo/bar/: " + cstest.FileNotFoundMessage,
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			got, err := listFilesAtPath(tc.args.path)
-			cstest.RequireErrorContains(t, err, tc.expectedErr)
 
-			if !reflect.DeepEqual(got, tc.want) {
-				t.Errorf("listFilesAtPath() = %v, want %v", got, tc.want)
-			}
-		})
-	}
+func (s *PluginSuite) writeconfig(config PluginConfig) {
+	t := s.T()
+	data, err := yaml.Marshal(&config)
+	require.NoError(t, err,"unable to marshal config file")
+
+	err = os.WriteFile(s.pluginConfig, data, 0644)
+	require.NoError(t, err,"unable to write config file %s", s.pluginConfig)
 }
 
-func TestBrokerInit(t *testing.T) {
-	if runtime.GOOS == "windows" {
-		t.Skip("Skipping test on windows")
-	}
 
+func (s *PluginSuite) TestBrokerInit() {
 	tests := []struct {
 		name        string
 		action      func(*testing.T)
@@ -143,28 +61,30 @@ func TestBrokerInit(t *testing.T) {
 	}{
 		{
 			name:   "valid config",
-			action: setPluginPermTo744,
 		},
 		{
 			name:        "group writable binary",
 			expectedErr: "notification-dummy is world writable",
-			action:      setPluginPermTo722,
+			action:      s.permissionSetter(0o722),
 		},
 		{
 			name:        "group writable binary",
 			expectedErr: "notification-dummy is group writable",
-			action:      setPluginPermTo724,
+			action:      s.permissionSetter(0o724),
 		},
 		{
 			name:        "no plugin dir",
 			expectedErr: cstest.FileNotFoundMessage,
-			action:      tearDown,
+			action: func(t *testing.T) {
+				err := os.RemoveAll(s.runDir)
+				require.NoError(t, err)
+			},
 		},
 		{
 			name:        "no plugin binary",
 			expectedErr: "binary for plugin dummy_default not found",
 			action: func(t *testing.T) {
-				err := os.Remove(path.Join(testPath, "notification-dummy"))
+				err := os.Remove(s.pluginBinary)
 				require.NoError(t, err)
 			},
 		},
@@ -174,7 +94,6 @@ func TestBrokerInit(t *testing.T) {
 			procCfg: csconfig.PluginCfg{
 				User: "123445555551122toto",
 			},
-			action: setPluginPermTo744,
 		},
 		{
 			name:        "only specify group",
@@ -182,7 +101,6 @@ func TestBrokerInit(t *testing.T) {
 			procCfg: csconfig.PluginCfg{
 				Group: "123445555551122toto",
 			},
-			action: setPluginPermTo744,
 		},
 		{
 			name:        "Fails to run as root",
@@ -191,7 +109,6 @@ func TestBrokerInit(t *testing.T) {
 				User:  "root",
 				Group: "root",
 			},
-			action: setPluginPermTo744,
 		},
 		{
 			name:        "Invalid user and group",
@@ -200,7 +117,6 @@ func TestBrokerInit(t *testing.T) {
 				User:  "toto1234",
 				Group: "toto1234",
 			},
-			action: setPluginPermTo744,
 		},
 		{
 			name:        "Valid user and invalid group",
@@ -209,79 +125,33 @@ func TestBrokerInit(t *testing.T) {
 				User:  "nobody",
 				Group: "toto1234",
 			},
-			action: setPluginPermTo744,
 		},
 	}
 
 	for _, tc := range tests {
 		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			defer tearDown(t)
-			buildDummyPlugin(t)
+		s.Run(tc.name, func() {
+			t := s.T()
 			if tc.action != nil {
 				tc.action(t)
 			}
-			pb := PluginBroker{}
-			profiles := csconfig.NewDefaultConfig().API.Server.Profiles
-			profiles = append(profiles, &csconfig.ProfileCfg{
-				Notifications: []string{"dummy_default"},
-			})
-			err := pb.Init(&tc.procCfg, profiles, &csconfig.ConfigurationPaths{
-				PluginDir:       testPath,
-				NotificationDir: "./tests/notifications",
-			})
-			defer pb.Kill()
+			_, err := s.InitBroker(&tc.procCfg)
 			cstest.RequireErrorContains(t, err, tc.expectedErr)
 		})
 	}
 }
 
-func readconfig(t *testing.T, path string) ([]byte, PluginConfig) {
-	var config PluginConfig
-	orig, err := os.ReadFile("tests/notifications/dummy.yaml")
-	require.NoError(t, err,"unable to read config file %s", path)
-
-	err = yaml.Unmarshal(orig, &config)
-	require.NoError(t, err,"unable to unmarshal config file")
-	
-	return orig, config
-}
-
-func writeconfig(t *testing.T, config PluginConfig, path string) {
-	data, err := yaml.Marshal(&config)
-	require.NoError(t, err,"unable to marshal config file")
-
-	err = os.WriteFile(path, data, 0644)
-	require.NoError(t, err,"unable to write config file %s", path)
-}
-
-func TestBrokerNoThreshold(t *testing.T) {
+func (s *PluginSuite) TestBrokerNoThreshold() {
 	var alerts []models.Alert
 	DefaultEmptyTicker = 50 * time.Millisecond
 
-	buildDummyPlugin(t)
-	setPluginPermTo744(t)
-	defer tearDown(t)
-
-	// init
-	pluginCfg := csconfig.PluginCfg{}
-	pb := PluginBroker{}
-	profiles := csconfig.NewDefaultConfig().API.Server.Profiles
-	profiles = append(profiles, &csconfig.ProfileCfg{
-		Notifications: []string{"dummy_default"},
-	})
-
-	// default config
-	err := pb.Init(&pluginCfg, profiles, &csconfig.ConfigurationPaths{
-		PluginDir:       testPath,
-		NotificationDir: "./tests/notifications",
-	})
+	t := s.T()
 
+	pb, err := s.InitBroker(nil)
 	assert.NoError(t, err)
-	tomb := tomb.Tomb{}
 
+	tomb := tomb.Tomb{}
 	go pb.Run(&tomb)
-	defer pb.Kill()
 
 	// send one item, it should be processed right now
 	pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}}
@@ -292,7 +162,7 @@ func TestBrokerNoThreshold(t *testing.T) {
 	require.NoError(t, err, "Error reading file")
 
 	err = json.Unmarshal(content, &alerts)
-	assert.NoError(t, err)
+	require.NoError(t, err)
 	assert.Len(t, alerts, 1)
 
 	// remove it
@@ -313,34 +183,24 @@ func TestBrokerNoThreshold(t *testing.T) {
 	assert.Len(t, alerts, 1)
 }
 
-func TestBrokerRunGroupAndTimeThreshold_TimeFirst(t *testing.T) {
+func (s *PluginSuite) TestBrokerRunGroupAndTimeThreshold_TimeFirst() {
 	// test grouping by "time"
 	DefaultEmptyTicker = 50 * time.Millisecond
-	buildDummyPlugin(t)
-	setPluginPermTo744(t)
-	defer tearDown(t)
-
-	// init
-	pluginCfg := csconfig.PluginCfg{}
-	pb := PluginBroker{}
-	profiles := csconfig.NewDefaultConfig().API.Server.Profiles
-	profiles = append(profiles, &csconfig.ProfileCfg{
-		Notifications: []string{"dummy_default"},
-	})
+
+	t := s.T()
+
 	// set groupwait and groupthreshold, should honor whichever comes first
-	raw, cfg := readconfig(t, "tests/notifications/dummy.yaml")
+	cfg := s.readconfig()
 	cfg.GroupThreshold = 4
 	cfg.GroupWait = 1 * time.Second
-	writeconfig(t, cfg, "tests/notifications/dummy.yaml")
-	err := pb.Init(&pluginCfg, profiles, &csconfig.ConfigurationPaths{
-		PluginDir:       testPath,
-		NotificationDir: "./tests/notifications",
-	})
+	s.writeconfig(cfg)
+
+	pb, err := s.InitBroker(nil)
 	assert.NoError(t, err)
-	tomb := tomb.Tomb{}
 
+	tomb := tomb.Tomb{}
 	go pb.Run(&tomb)
-	defer pb.Kill()
+
 	// send data
 	pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}}
 	pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}}
@@ -357,40 +217,24 @@ func TestBrokerRunGroupAndTimeThreshold_TimeFirst(t *testing.T) {
 	err = json.Unmarshal(content, &alerts)
 	assert.NoError(t, err)
 	assert.Len(t, alerts, 3)
-
-	// restore config
-	err = os.WriteFile("tests/notifications/dummy.yaml", raw, 0644)
-	require.NoError(t, err,"unable to write config file")
 }
 
-func TestBrokerRunGroupAndTimeThreshold_CountFirst(t *testing.T) {
+func (s *PluginSuite) TestBrokerRunGroupAndTimeThreshold_CountFirst() {
 	DefaultEmptyTicker = 50 * time.Millisecond
-	buildDummyPlugin(t)
-	setPluginPermTo(t, "744")
-	defer tearDown(t)
-
-	// init
-	pluginCfg := csconfig.PluginCfg{}
-	pb := PluginBroker{}
-	profiles := csconfig.NewDefaultConfig().API.Server.Profiles
-	profiles = append(profiles, &csconfig.ProfileCfg{
-		Notifications: []string{"dummy_default"},
-	})
+
+	t := s.T()
 
 	// set groupwait and groupthreshold, should honor whichever comes first
-	raw, cfg := readconfig(t, "tests/notifications/dummy.yaml")
+	cfg := s.readconfig()
 	cfg.GroupThreshold = 4
 	cfg.GroupWait = 4 * time.Second
-	writeconfig(t, cfg, "tests/notifications/dummy.yaml")
-	err := pb.Init(&pluginCfg, profiles, &csconfig.ConfigurationPaths{
-		PluginDir:       testPath,
-		NotificationDir: "./tests/notifications",
-	})
+	s.writeconfig(cfg)
+
+	pb, err := s.InitBroker(nil)
 	assert.NoError(t, err)
-	tomb := tomb.Tomb{}
 
+	tomb := tomb.Tomb{}
 	go pb.Run(&tomb)
-	defer pb.Kill()
 
 	// send data
 	pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}}
@@ -411,41 +255,24 @@ func TestBrokerRunGroupAndTimeThreshold_CountFirst(t *testing.T) {
 	err = json.Unmarshal(content, &alerts)
 	assert.NoError(t, err)
 	assert.Len(t, alerts, 4)
-
-	// restore config
-	err = os.WriteFile("tests/notifications/dummy.yaml", raw, 0644)
-	require.NoError(t, err,"unable to write config file")
 }
 
-func TestBrokerRunGroupThreshold(t *testing.T) {
+func (s *PluginSuite) TestBrokerRunGroupThreshold() {
 	// test grouping by "size"
 	DefaultEmptyTicker = 50 * time.Millisecond
-	buildDummyPlugin(t)
-	setPluginPermTo(t, "744")
-	defer tearDown(t)
-
-	// init
-	pluginCfg := csconfig.PluginCfg{}
-	pb := PluginBroker{}
-	profiles := csconfig.NewDefaultConfig().API.Server.Profiles
-	profiles = append(profiles, &csconfig.ProfileCfg{
-		Notifications: []string{"dummy_default"},
-	})
+
+	t := s.T()
 
 	// set groupwait
-	raw, cfg := readconfig(t, "tests/notifications/dummy.yaml")
+	cfg := s.readconfig()
 	cfg.GroupThreshold = 4
-	writeconfig(t, cfg, "tests/notifications/dummy.yaml")
-	err := pb.Init(&pluginCfg, profiles, &csconfig.ConfigurationPaths{
-		PluginDir:       testPath,
-		NotificationDir: "./tests/notifications",
-	})
+	s.writeconfig(cfg)
 
+	pb, err := s.InitBroker(nil)
 	assert.NoError(t, err)
-	tomb := tomb.Tomb{}
 
+	tomb := tomb.Tomb{}
 	go pb.Run(&tomb)
-	defer pb.Kill()
 
 	// send data
 	pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}}
@@ -466,39 +293,23 @@ func TestBrokerRunGroupThreshold(t *testing.T) {
 	err = json.Unmarshal(content, &alerts)
 	assert.NoError(t, err)
 	assert.Len(t, alerts, 4)
-
-	// restore config
-	err = os.WriteFile("tests/notifications/dummy.yaml", raw, 0644)
-	require.NoError(t, err, "unable to write config file")
 }
 
-func TestBrokerRunTimeThreshold(t *testing.T) {
+func (s *PluginSuite) TestBrokerRunTimeThreshold() {
 	DefaultEmptyTicker = 50 * time.Millisecond
-	buildDummyPlugin(t)
-	setPluginPermTo(t, "744")
-	defer tearDown(t)
-
-	// init
-	pluginCfg := csconfig.PluginCfg{}
-	pb := PluginBroker{}
-	profiles := csconfig.NewDefaultConfig().API.Server.Profiles
-	profiles = append(profiles, &csconfig.ProfileCfg{
-		Notifications: []string{"dummy_default"},
-	})
+
+	t := s.T()
 
 	// set groupwait
-	raw, cfg := readconfig(t, "tests/notifications/dummy.yaml")
+	cfg := s.readconfig()
 	cfg.GroupWait = 1 * time.Second
-	writeconfig(t, cfg, "tests/notifications/dummy.yaml")
-	err := pb.Init(&pluginCfg, profiles, &csconfig.ConfigurationPaths{
-		PluginDir:       testPath,
-		NotificationDir: "./tests/notifications",
-	})
+	s.writeconfig(cfg)
+
+	pb, err := s.InitBroker(nil)
 	assert.NoError(t, err)
-	tomb := tomb.Tomb{}
 
+	tomb := tomb.Tomb{}
 	go pb.Run(&tomb)
-	defer pb.Kill()
 
 	// send data
 	pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}}
@@ -516,32 +327,18 @@ func TestBrokerRunTimeThreshold(t *testing.T) {
 	err = json.Unmarshal(content, &alerts)
 	assert.NoError(t, err)
 	assert.Len(t, alerts, 1)
-
-	// restore config
-	err = os.WriteFile("tests/notifications/dummy.yaml", raw, 0644)
-	require.NoError(t, err, "unable to write config file %s", err)
 }
 
-func TestBrokerRunSimple(t *testing.T) {
+func (s *PluginSuite) TestBrokerRunSimple() {
 	DefaultEmptyTicker = 50 * time.Millisecond
-	buildDummyPlugin(t)
-	setPluginPermTo(t, "744")
-	defer tearDown(t)
-	pluginCfg := csconfig.PluginCfg{}
-	pb := PluginBroker{}
-	profiles := csconfig.NewDefaultConfig().API.Server.Profiles
-	profiles = append(profiles, &csconfig.ProfileCfg{
-		Notifications: []string{"dummy_default"},
-	})
-	err := pb.Init(&pluginCfg, profiles, &csconfig.ConfigurationPaths{
-		PluginDir:       testPath,
-		NotificationDir: "./tests/notifications",
-	})
+
+	t := s.T()
+	
+	pb, err := s.InitBroker(nil)
 	assert.NoError(t, err)
-	tomb := tomb.Tomb{}
 
+	tomb := tomb.Tomb{}
 	go pb.Run(&tomb)
-	defer pb.Kill()
 
 	assert.NoFileExists(t, "./out")
 
@@ -559,47 +356,3 @@ func TestBrokerRunSimple(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Len(t, alerts, 2)
 }
-
-func buildDummyPlugin(t *testing.T) {
-	dir, err := os.MkdirTemp("./tests", "cs_plugin_test")
-	require.NoError(t, err)
-
-	cmd := exec.Command("go", "build", "-o", path.Join(dir, "notification-dummy"), "../../plugins/notifications/dummy/")
-	err = cmd.Run()
-	require.NoError(t, err, "while building dummy plugin")
-
-	testPath = dir
-	os.Remove("./out")
-}
-
-func setPluginPermTo(t *testing.T, perm string) {
-	if runtime.GOOS != "windows" {
-		err := exec.Command("chmod", perm, path.Join(testPath, "notification-dummy")).Run()
-		require.NoError(t, err, "chmod 744 %s", path.Join(testPath, "notification-dummy"))
-	}
-}
-
-func setUp(t *testing.T) {
-	dir, err := os.MkdirTemp("./", "cs_plugin_test")
-	require.NoError(t, err)
-
-	f, err := os.Create(path.Join(dir, "slack"))
-	require.NoError(t, err)
-
-	f.Close()
-	f, err = os.Create(path.Join(dir, "notification-gitter"))
-	require.NoError(t, err)
-
-	f.Close()
-	err = os.Mkdir(path.Join(dir, "dummy_dir"), 0666)
-	require.NoError(t, err)
-
-	testPath = dir
-}
-
-func tearDown(t *testing.T) {
-	err := os.RemoveAll(testPath)
-	require.NoError(t, err)
-
-	os.Remove("./out")
-}

+ 26 - 204
pkg/csplugin/broker_win_test.go

@@ -3,16 +3,12 @@
 package csplugin
 
 import (
-	"log"
 	"os"
-	"os/exec"
-	"path"
-	"path/filepath"
-	"reflect"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 	"gopkg.in/tomb.v2"
 
 	"github.com/crowdsecurity/go-cs-lib/pkg/cstest"
@@ -27,188 +23,55 @@ Due to the complexity of file permission modification with go on windows, we onl
 not if it will actually reject plugins with invalid permissions
 */
 
-var testPath string
-
-func TestGetPluginNameAndTypeFromPath(t *testing.T) {
-	setUp()
-	defer tearDown()
-	type args struct {
-		path string
-	}
-	tests := []struct {
-		name    string
-		args    args
-		want    string
-		want1   string
-		wantErr bool
-	}{
-		{
-			name: "valid plugin name, single dash",
-			args: args{
-				path: path.Join(testPath, "notification-gitter"),
-			},
-			want:    "notification",
-			want1:   "gitter",
-			wantErr: false,
-		},
-		{
-			name: "invalid plugin name",
-			args: args{
-				path: ".\\tests\\gitter.exe",
-			},
-			want:    "",
-			want1:   "",
-			wantErr: true,
-		},
-		{
-			name: "valid plugin name, multiple dash",
-			args: args{
-				path: ".\\tests\\notification-instant-slack.exe",
-			},
-			want:    "notification-instant",
-			want1:   "slack",
-			wantErr: false,
-		},
-	}
-	for _, tt := range tests {
-		tt := tt
-		t.Run(tt.name, func(t *testing.T) {
-			got, got1, err := getPluginTypeAndSubtypeFromPath(tt.args.path)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("getPluginNameAndTypeFromPath() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if got != tt.want {
-				t.Errorf("getPluginNameAndTypeFromPath() got = %v, want %v", got, tt.want)
-			}
-			if got1 != tt.want1 {
-				t.Errorf("getPluginNameAndTypeFromPath() got1 = %v, want %v", got1, tt.want1)
-			}
-		})
-	}
-}
-
-func TestListFilesAtPath(t *testing.T) {
-	setUp()
-	defer tearDown()
-	type args struct {
-		path string
-	}
-	tests := []struct {
-		name    string
-		args    args
-		want    []string
-		wantErr bool
-	}{
-		{
-			name: "valid directory",
-			args: args{
-				path: testPath,
-			},
-			want: []string{
-				filepath.Join(testPath, "notification-gitter"),
-				filepath.Join(testPath, "slack"),
-			},
-		},
-		{
-			name: "invalid directory",
-			args: args{
-				path: "./foo/bar/",
-			},
-			wantErr: true,
-		},
-	}
-	for _, tt := range tests {
-		tt := tt
-		t.Run(tt.name, func(t *testing.T) {
-			got, err := listFilesAtPath(tt.args.path)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("listFilesAtPath() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("listFilesAtPath() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-func TestBrokerInit(t *testing.T) {
+func (s *PluginSuite) TestBrokerInit() {
 	tests := []struct {
 		name        string
-		action      func()
-		errContains string
-		wantErr     bool
+		action      func(*testing.T)
 		procCfg     csconfig.PluginCfg
+		expectedErr string
 	}{
 		{
 			name:    "valid config",
-			wantErr: false,
 		},
 		{
 			name:        "no plugin dir",
-			wantErr:     true,
-			errContains: cstest.FileNotFoundMessage,
-			action:      tearDown,
+			expectedErr: cstest.PathNotFoundMessage,
+			action: func(t *testing.T) {
+				err := os.RemoveAll(s.runDir)
+				require.NoError(t, err)
+			},
 		},
 		{
 			name:        "no plugin binary",
-			wantErr:     true,
-			errContains: "binary for plugin dummy_default not found",
-			action: func() {
-				err := os.Remove(path.Join(testPath, "notification-dummy.exe"))
-				if err != nil {
-					t.Fatal(err)
-				}
+			expectedErr: "binary for plugin dummy_default not found",
+			action: func(t *testing.T) {
+				err := os.Remove(s.pluginBinary)
+				require.NoError(t, err)
 			},
 		},
 	}
 
-	for _, test := range tests {
-		test := test
-		t.Run(test.name, func(t *testing.T) {
-			defer tearDown()
-			buildDummyPlugin()
-			if test.action != nil {
-				test.action()
+	for _, tc := range tests {
+		tc := tc
+		s.Run(tc.name, func() {
+			t := s.T()
+			if tc.action != nil {
+				tc.action(t)
 			}
-			pb := PluginBroker{}
-			profiles := csconfig.NewDefaultConfig().API.Server.Profiles
-			profiles = append(profiles, &csconfig.ProfileCfg{
-				Notifications: []string{"dummy_default"},
-			})
-			err := pb.Init(&test.procCfg, profiles, &csconfig.ConfigurationPaths{
-				PluginDir:       testPath,
-				NotificationDir: "./tests/notifications",
-			})
-			defer pb.Kill()
-			if test.wantErr {
-				assert.ErrorContains(t, err, test.errContains)
-			} else {
-				assert.NoError(t, err)
-			}
-
+			_, err := s.InitBroker(&tc.procCfg)
+			cstest.RequireErrorContains(t, err, tc.expectedErr)
 		})
 	}
 }
 
-func TestBrokerRun(t *testing.T) {
-	buildDummyPlugin()
-	defer tearDown()
-	procCfg := csconfig.PluginCfg{}
-	pb := PluginBroker{}
-	profiles := csconfig.NewDefaultConfig().API.Server.Profiles
-	profiles = append(profiles, &csconfig.ProfileCfg{
-		Notifications: []string{"dummy_default"},
-	})
-	err := pb.Init(&procCfg, profiles, &csconfig.ConfigurationPaths{
-		PluginDir:       testPath,
-		NotificationDir: "./tests/notifications",
-	})
+func (s *PluginSuite) TestBrokerRun() {
+	t := s.T()
+
+	pb, err := s.InitBroker(nil)
 	assert.NoError(t, err)
+
 	tomb := tomb.Tomb{}
 	go pb.Run(&tomb)
-	defer pb.Kill()
 
 	assert.NoFileExists(t, "./out")
 	defer os.Remove("./out")
@@ -220,44 +83,3 @@ func TestBrokerRun(t *testing.T) {
 	assert.FileExists(t, ".\\out")
 	assert.Equal(t, types.GetLineCountForFile(".\\out"), 2)
 }
-
-func buildDummyPlugin() {
-	dir, err := os.MkdirTemp(".\\tests", "cs_plugin_test")
-	if err != nil {
-		log.Fatal(err)
-	}
-	cmd := exec.Command("go", "build", "-o", path.Join(dir, "notification-dummy.exe"), "../../plugins/notifications/dummy/")
-	if err := cmd.Run(); err != nil {
-		log.Fatal(err)
-	}
-	testPath = dir
-}
-
-func setUp() {
-	dir, err := os.MkdirTemp("./", "cs_plugin_test")
-	if err != nil {
-		log.Fatal(err)
-	}
-	f, err := os.Create(path.Join(dir, "slack"))
-	if err != nil {
-		log.Fatal(err)
-	}
-	f.Close()
-	f, err = os.Create(path.Join(dir, "notification-gitter"))
-	if err != nil {
-		log.Fatal(err)
-	}
-	f.Close()
-	err = os.Mkdir(path.Join(dir, "dummy_dir"), 0666)
-	if err != nil {
-		log.Fatal(err)
-	}
-	testPath = dir
-}
-
-func tearDown() {
-	err := os.RemoveAll(testPath)
-	if err != nil {
-		log.Fatal(err)
-	}
-}

+ 22 - 0
pkg/csplugin/listfiles.go

@@ -0,0 +1,22 @@
+package csplugin
+
+import (
+	"os"
+	"path/filepath"
+)
+
+// helper which gives paths to all files in the given directory non-recursively
+func listFilesAtPath(path string) ([]string, error) {
+	filePaths := make([]string, 0)
+	files, err := os.ReadDir(path)
+	if err != nil {
+		return nil, err
+	}
+	for _, file := range files {
+		if ! file.IsDir() {
+			filePaths = append(filePaths, filepath.Join(path, file.Name()))
+		}
+	}
+	return filePaths, nil
+}
+

+ 57 - 0
pkg/csplugin/listfiles_test.go

@@ -0,0 +1,57 @@
+package csplugin
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/crowdsecurity/go-cs-lib/pkg/cstest"
+)
+
+func TestListFilesAtPath(t *testing.T) {
+	dir, err := os.MkdirTemp("", "test-listfiles")
+	require.NoError(t, err)
+	t.Cleanup(func() {
+		os.RemoveAll(dir)
+	})
+	_, err = os.Create(filepath.Join(dir, "notification-gitter"))
+	require.NoError(t, err)
+	_, err = os.Create(filepath.Join(dir, "slack"))
+	require.NoError(t, err)
+	err = os.Mkdir(filepath.Join(dir, "somedir"), 0755)
+	require.NoError(t, err)
+	_, err = os.Create(filepath.Join(dir, "somedir", "inner"))
+	require.NoError(t, err)
+
+	tests := []struct {
+		name    string
+		path	string
+		want    []string
+		expectedErr string
+	}{
+		{
+			name: "valid directory",
+			path: dir,
+			want: []string{
+				filepath.Join(dir, "notification-gitter"),
+				filepath.Join(dir, "slack"),
+			},
+		},
+		{
+			name: "invalid directory",
+			path: "./foo/bar/",
+			expectedErr: "open ./foo/bar/: " + cstest.PathNotFoundMessage,
+		},
+	}
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			got, err := listFilesAtPath(tc.path)
+			cstest.RequireErrorContains(t, err, tc.expectedErr)
+			assert.ElementsMatch(t, tc.want, got)
+		})
+	}
+}

+ 0 - 0
pkg/csplugin/tests/notifications/dummy.yaml → pkg/csplugin/testdata/dummy.yaml


+ 49 - 0
pkg/csplugin/utils_test.go

@@ -0,0 +1,49 @@
+//go:build linux || freebsd || netbsd || openbsd || solaris || !windows
+
+package csplugin
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/crowdsecurity/go-cs-lib/pkg/cstest"
+)
+
+func TestGetPluginNameAndTypeFromPath(t *testing.T) {
+	tests := []struct {
+		name        string
+		path        string
+		want        string
+		want1       string
+		expectedErr string
+	}{
+		{
+			name:  "valid plugin name, single dash",
+			path:  "/path/to/notification-gitter",
+			want:  "notification",
+			want1: "gitter",
+		},
+		{
+			name:        "invalid plugin name",
+			path:        "/path/to/gitter",
+			expectedErr: "plugin name /path/to/gitter is invalid. Name should be like {type-name}",
+		},
+		{
+			name:  "valid plugin name, multiple dash",
+			path:  "/path/to/notification-instant-slack",
+			want:  "notification-instant",
+			want1: "slack",
+		},
+	}
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			got, got1, err := getPluginTypeAndSubtypeFromPath(tc.path)
+			cstest.RequireErrorContains(t, err, tc.expectedErr)
+
+			assert.Equal(t, tc.want, got)
+			assert.Equal(t, tc.want1, got1)
+		})
+	}
+}

+ 49 - 0
pkg/csplugin/utils_windows_test.go

@@ -0,0 +1,49 @@
+//go:build windows
+
+package csplugin
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/crowdsecurity/go-cs-lib/pkg/cstest"
+)
+
+func TestGetPluginNameAndTypeFromPath(t *testing.T) {
+	tests := []struct {
+		name        string
+		path        string
+		want        string
+		want1       string
+		expectedErr string
+	}{
+		{
+			name:  "valid plugin name, single dash",
+			path:  "c:\\path\\to\\notification-gitter",
+			want:  "notification",
+			want1: "gitter",
+		},
+		{
+			name:        "invalid plugin name",
+			path:        "c:\\path\\to\\gitter.exe",
+			expectedErr: "plugin name c:\\path\\to\\gitter.exe is invalid. Name should be like {type-name}",
+		},
+		{
+			name:  "valid plugin name, multiple dash",
+			path:  "c:\\path\\to\\notification-instant-slack.exe",
+			want:  "notification-instant",
+			want1: "slack",
+		},
+	}
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			got, got1, err := getPluginTypeAndSubtypeFromPath(tc.path)
+			cstest.RequireErrorContains(t, err, tc.expectedErr)
+
+			assert.Equal(t, tc.want, got)
+			assert.Equal(t, tc.want1, got1)
+		})
+	}
+}