From ac4a61aab25801e3907830dd702f693a58ef8f86 Mon Sep 17 00:00:00 2001 From: "Yusuf A. Hasan Miyan" Date: Tue, 15 Oct 2024 16:57:00 +0400 Subject: [PATCH] feat: added tests for allowed list [DEVOPS-646] - fixed disallowed logic --- .gitignore | 1 + README.md | 2 +- cmd/gen/analyseplugin.go | 10 + pkg/analyse/allowedlist.go | 22 +- pkg/analyse/allowedlist_test.go | 255 ++++++++++++++++++ .../templates/analyseplugin_test.go.tmpl | 11 + 6 files changed, 291 insertions(+), 10 deletions(-) create mode 100644 pkg/analyse/allowedlist_test.go create mode 100644 pkg/analyse/templates/analyseplugin_test.go.tmpl diff --git a/.gitignore b/.gitignore index 443ac2a..dae6769 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Generated files *_gen.go +*_gen_test.go build dist/ diff --git a/README.md b/README.md index cf4d8d8..5956a97 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ COPY --from=ghcr.io/salsadigitalauorg/shipshape:latest /usr/local/bin/shipshape ``` ## Documentation -Check out our documentation at https://salsadigitalauorg.github.io/shipshape/. Keep in mind that this is still a work in progress, so please go easy. +Check out our documentation at https://salsadigitalauorg.github.io/shipshape/. ## Local development diff --git a/cmd/gen/analyseplugin.go b/cmd/gen/analyseplugin.go index 2d8097a..9ed5573 100644 --- a/cmd/gen/analyseplugin.go +++ b/cmd/gen/analyseplugin.go @@ -11,6 +11,7 @@ func AnalysePlugin(plugins []string) { log.Println("Generating analyse plugin funcs -", strings.Join(plugins, ",")) tmplPath := filepath.Join("..", "..", "pkg", "analyse", "templates", "analyseplugin.go.tmpl") + tmplTestPath := filepath.Join("..", "..", "pkg", "analyse", "templates", "analyseplugin_test.go.tmpl") for _, p := range plugins { pluginFile := strings.ToLower(p) + "_gen.go" @@ -20,5 +21,14 @@ func AnalysePlugin(plugins []string) { } templateToFile(tmplPath, struct{ Plugin string }{p}, pluginFullFilePath) + + // Test file. + pluginTestFile := strings.ToLower(p) + "_gen_test.go" + pluginFullTestFilePath := filepath.Join(getScriptPath(), "..", "..", "pkg", "analyse", pluginTestFile) + if err := os.Remove(pluginTestFile); err != nil && !os.IsNotExist(err) { + log.Fatalln(err) + } + + templateToFile(tmplTestPath, struct{ Plugin string }{p}, pluginFullTestFilePath) } } diff --git a/pkg/analyse/allowedlist.go b/pkg/analyse/allowedlist.go index 7857a58..f6ad81d 100644 --- a/pkg/analyse/allowedlist.go +++ b/pkg/analyse/allowedlist.go @@ -52,21 +52,21 @@ func (p *AllowedList) Analyse() { continue } - if !p.isAllowed(v) { + if p.isDeprecated(v) { breach.EvaluateTemplate(p, &breach.KeyValueBreach{ KeyLabel: "key", Key: k, - ValueLabel: "disallowed", + ValueLabel: "deprecated", Value: v, }) continue } - if p.isDeprecated(v) { + if !p.isAllowed(v) { breach.EvaluateTemplate(p, &breach.KeyValueBreach{ KeyLabel: "key", Key: k, - ValueLabel: "deprecated", + ValueLabel: "disallowed", Value: v, }) continue @@ -75,26 +75,30 @@ func (p *AllowedList) Analyse() { case data.FormatMapListString: inputData := data.AsMapListString(p.input.GetData()) for k, listV := range inputData { - if p.isExcludedKey(k) || p.isIgnored(k) { + if p.isExcludedKey(k) { continue } for _, v := range listV { - if !p.isAllowed(v) { + if p.isIgnored(v) { + continue + } + + if p.isDeprecated(v) { breach.EvaluateTemplate(p, &breach.KeyValueBreach{ KeyLabel: "key", Key: k, - ValueLabel: "disallowed", + ValueLabel: "deprecated", Value: v, }) continue } - if p.isDeprecated(v) { + if !p.isAllowed(v) { breach.EvaluateTemplate(p, &breach.KeyValueBreach{ KeyLabel: "key", Key: k, - ValueLabel: "deprecated", + ValueLabel: "disallowed", Value: v, }) continue diff --git a/pkg/analyse/allowedlist_test.go b/pkg/analyse/allowedlist_test.go new file mode 100644 index 0000000..f62c844 --- /dev/null +++ b/pkg/analyse/allowedlist_test.go @@ -0,0 +1,255 @@ +package analyse_test + +import ( + "io" + "testing" + + . "github.com/salsadigitalauorg/shipshape/pkg/analyse" + "github.com/salsadigitalauorg/shipshape/pkg/breach" + "github.com/salsadigitalauorg/shipshape/pkg/data" + "github.com/salsadigitalauorg/shipshape/pkg/fact" + "github.com/salsadigitalauorg/shipshape/pkg/fact/testdata" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func TestAllowedListInit(t *testing.T) { + + assert := assert.New(t) + + // Test that the yaml:key plugin is registered. + plugin := Registry["allowed:list"]("testAllowedList") + assert.NotNil(plugin) + analyser, ok := plugin.(*AllowedList) + assert.True(ok) + assert.Equal("testAllowedList", analyser.Id) +} + +func TestAllowedListPluginName(t *testing.T) { + instance := AllowedList{Id: "testAllowedList"} + assert.Equal(t, "allowed:list", instance.PluginName()) +} + +func TestAllowedListAnalyse(t *testing.T) { + tt := []struct { + name string + input fact.Facter + allowed []string + deprecated []string + excludeKeys []string + ignore []string + expectedBreaches []breach.Breach + }{ + { + name: "mapStringNoBreaches", + input: &testdata.TestFacter{ + Name: "testFacter", + TestInputDataFormat: data.FormatMapString, + TestInputData: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + }, + }, + allowed: []string{"value1", "value2"}, + expectedBreaches: []breach.Breach{}, + }, + { + name: "mapStringExcludedIgnored", + input: &testdata.TestFacter{ + Name: "testFacter", + TestInputDataFormat: data.FormatMapString, + TestInputData: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + "key4": "value4", + }, + }, + allowed: []string{"value1", "value2"}, + excludeKeys: []string{"key3"}, + ignore: []string{"value4"}, + expectedBreaches: []breach.Breach{}, + }, + { + name: "mapStringNotAllowed", + input: &testdata.TestFacter{ + Name: "testFacter", + TestInputDataFormat: data.FormatMapString, + TestInputData: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + }, + allowed: []string{"value1", "value2"}, + expectedBreaches: []breach.Breach{ + &breach.KeyValueBreach{ + BreachType: "key-value", + CheckName: "testAllowedList", + KeyLabel: "key", + Key: "key3", + ValueLabel: "disallowed", + Value: "value3", + }, + }, + }, + { + name: "mapStringDeprecated", + input: &testdata.TestFacter{ + Name: "testFacter", + TestInputDataFormat: data.FormatMapString, + TestInputData: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + }, + allowed: []string{"value1", "value2"}, + deprecated: []string{"value3"}, + expectedBreaches: []breach.Breach{ + &breach.KeyValueBreach{ + BreachType: "key-value", + CheckName: "testAllowedList", + KeyLabel: "key", + Key: "key3", + ValueLabel: "deprecated", + Value: "value3", + }, + }, + }, + + // Test data with map of list of strings. + { + name: "mapListStringNoBreaches", + input: &testdata.TestFacter{ + Name: "testFacter", + TestInputDataFormat: data.FormatMapListString, + TestInputData: map[string][]string{ + "key1": {"value1"}, + "key2": {"value2"}, + }, + }, + allowed: []string{"value1", "value2"}, + expectedBreaches: []breach.Breach{}, + }, + { + name: "mapListStringExcludedIgnored", + input: &testdata.TestFacter{ + Name: "testFacter", + TestInputDataFormat: data.FormatMapListString, + TestInputData: map[string][]string{ + "key1": {"value1"}, + "key2": {"value2"}, + "key3": {"value3"}, + "key4": {"value4"}, + }, + }, + allowed: []string{"value1", "value2"}, + excludeKeys: []string{"key3"}, + ignore: []string{"value4"}, + expectedBreaches: []breach.Breach{}, + }, + { + name: "mapListStringSomeIgnored", + input: &testdata.TestFacter{ + Name: "testFacter", + TestInputDataFormat: data.FormatMapListString, + TestInputData: map[string][]string{ + "key1": {"value1"}, + "key2": {"value2"}, + "key3": {"value3", "value4"}, + }, + }, + allowed: []string{"value1", "value2"}, + ignore: []string{"value4"}, + expectedBreaches: []breach.Breach{ + &breach.KeyValueBreach{ + BreachType: "key-value", + CheckName: "testAllowedList", + KeyLabel: "key", + Key: "key3", + ValueLabel: "disallowed", + Value: "value3", + }, + }, + }, + { + name: "mapListStringDeprecated", + input: &testdata.TestFacter{ + Name: "testFacter", + TestInputDataFormat: data.FormatMapListString, + TestInputData: map[string][]string{ + "key1": {"value1"}, + "key2": {"value2", "value4"}, + }, + }, + allowed: []string{"value1", "value2"}, + deprecated: []string{"value4"}, + expectedBreaches: []breach.Breach{ + &breach.KeyValueBreach{ + BreachType: "key-value", + CheckName: "testAllowedList", + KeyLabel: "key", + Key: "key2", + ValueLabel: "deprecated", + Value: "value4", + }, + }, + }, + { + name: "mapListStringDisallowed", + input: &testdata.TestFacter{ + Name: "testFacter", + TestInputDataFormat: data.FormatMapListString, + TestInputData: map[string][]string{ + "key1": {"value1", "value3"}, + "key2": {"value2", "value4"}, + }, + }, + allowed: []string{"value1", "value2"}, + expectedBreaches: []breach.Breach{ + &breach.KeyValueBreach{ + BreachType: "key-value", + CheckName: "testAllowedList", + KeyLabel: "key", + Key: "key1", + ValueLabel: "disallowed", + Value: "value3", + }, + &breach.KeyValueBreach{ + BreachType: "key-value", + CheckName: "testAllowedList", + KeyLabel: "key", + Key: "key2", + ValueLabel: "disallowed", + Value: "value4", + }, + }, + }, + } + + for _, tc := range tt { + assert := assert.New(t) + + currLogOut := logrus.StandardLogger().Out + defer logrus.SetOutput(currLogOut) + logrus.SetOutput(io.Discard) + + t.Run(tc.name, func(t *testing.T) { + analyser := AllowedList{ + Id: "testAllowedList", + Allowed: tc.allowed, + Deprecated: tc.deprecated, + ExcludeKeys: tc.excludeKeys, + Ignore: tc.ignore, + } + + tc.input.Collect() + analyser.SetInput(tc.input) + analyser.Analyse() + + assert.Len(analyser.Result.Breaches, len(tc.expectedBreaches)) + assert.ElementsMatch(tc.expectedBreaches, analyser.Result.Breaches) + }) + } +} diff --git a/pkg/analyse/templates/analyseplugin_test.go.tmpl b/pkg/analyse/templates/analyseplugin_test.go.tmpl new file mode 100644 index 0000000..36e0d62 --- /dev/null +++ b/pkg/analyse/templates/analyseplugin_test.go.tmpl @@ -0,0 +1,11 @@ +package analyse + +import ( + "github.com/salsadigitalauorg/shipshape/pkg/fact" +) + +// Functions that are only used in tests. + +func (p *{{ .Plugin }}) SetInput(input fact.Facter) { + p.input = input +}