CI: refactoring pkg/csplugin tests (#2247)

This commit is contained in:
mmetc 2023-06-01 10:33:08 +02:00 committed by GitHub
parent f6544962ea
commit 12c32d507c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 449 additions and 550 deletions

2
go.mod
View file

@ -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

4
go.sum
View file

@ -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=

View file

@ -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 {

View file

@ -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
}

View file

@ -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 setPluginPermTo722(t *testing.T) {
setPluginPermTo(t, "722")
}
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)
assert.Equal(t, tc.want, got)
assert.Equal(t, tc.want1, got1)
})
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 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)
func (s *PluginSuite) readconfig() (PluginConfig) {
var config PluginConfig
t := s.T()
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("listFilesAtPath() = %v, want %v", got, tc.want)
}
})
}
orig, err := os.ReadFile(s.pluginConfig)
require.NoError(t, err,"unable to read config file %s", s.pluginConfig)
err = yaml.Unmarshal(orig, &config)
require.NoError(t, err,"unable to unmarshal config file")
return config
}
func TestBrokerInit(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping test on windows")
}
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 (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",
})
assert.NoError(t, err)
tomb := tomb.Tomb{}
s.writeconfig(cfg)
pb, err := s.InitBroker(nil)
assert.NoError(t, err)
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",
})
assert.NoError(t, err)
tomb := tomb.Tomb{}
s.writeconfig(cfg)
pb, err := s.InitBroker(nil)
assert.NoError(t, err)
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",
})
assert.NoError(t, err)
tomb := tomb.Tomb{}
s.writeconfig(cfg)
pb, err := s.InitBroker(nil)
assert.NoError(t, err)
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",
})
assert.NoError(t, err)
tomb := tomb.Tomb{}
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")
@ -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")
}

View file

@ -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
pkg/csplugin/listfiles.go Normal file
View file

@ -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
}

View file

@ -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)
})
}
}

View file

@ -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)
})
}
}

View file

@ -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)
})
}
}