diff --git a/internal/datasources/structured/decoders_test.go b/internal/datasources/structured/decoders_test.go new file mode 100644 index 0000000000..99da3f4ce8 --- /dev/null +++ b/internal/datasources/structured/decoders_test.go @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors +// SPDX-License-Identifier: Apache-2.0 + +package structured + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestJsonDecoder(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + data []byte + mustErr bool + expect any + }{ + {name: "normal", data: []byte(`{"a":1, "b":"abc"}`), mustErr: false}, + {name: "invalid_json", data: []byte(`a 1`), mustErr: true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + var b bytes.Buffer + _, err := b.Write(tc.data) + require.NoError(t, err) + + dec := jsonDecoder{} + res, err := dec.Parse(&b) + if tc.mustErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.NotNil(t, res) + }) + dec := jsonDecoder{} + _, err := dec.Parse(nil) + require.Error(t, err) + + } +} + +func TestYamlDecoder(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + data []byte + mustErr bool + expect any + }{ + {name: "normal", data: []byte("---\na: 1\nb:\n - \"Hey\"\n - \"Bye\"\n"), mustErr: false}, + {name: "invalid_yaml", data: []byte(" a 1\na: 2\n"), mustErr: true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + var b bytes.Buffer + _, err := b.Write(tc.data) + require.NoError(t, err) + + dec := yamlDecoder{} + res, err := dec.Parse(&b) + if tc.mustErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.NotNil(t, res) + }) + dec := yamlDecoder{} + _, err := dec.Parse(nil) + require.Error(t, err) + + } +} + +func TestTomlDecoder(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + data []byte + mustErr bool + expect any + }{ + {name: "normal", data: []byte("title = \"TOML Example\"\n\n[owner]\nname = \"Tom Preston-Werner\""), mustErr: false}, + {name: "invalid_toml", data: []byte(" a 1\na: 2\n"), mustErr: true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + var b bytes.Buffer + _, err := b.Write(tc.data) + require.NoError(t, err) + + dec := tomlDecoder{} + res, err := dec.Parse(&b) + if tc.mustErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.NotNil(t, res) + }) + dec := tomlDecoder{} + _, err := dec.Parse(nil) + require.Error(t, err) + + } +} diff --git a/internal/datasources/structured/handler_test.go b/internal/datasources/structured/handler_test.go new file mode 100644 index 0000000000..c20f4e1765 --- /dev/null +++ b/internal/datasources/structured/handler_test.go @@ -0,0 +1,230 @@ +// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors +// SPDX-License-Identifier: Apache-2.0 + +package structured + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/memfs" + "github.com/stretchr/testify/require" + + minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1" + v1datasources "github.com/mindersec/minder/pkg/datasources/v1" + "github.com/mindersec/minder/pkg/engine/v1/interfaces" +) + +func writeFSFile(t *testing.T, fs billy.Filesystem, path string, data []byte) { + t.Helper() + require.NoError(t, fs.MkdirAll(filepath.Dir(path), os.FileMode(0o755))) + f, err := fs.Create(path) + require.NoError(t, err) + _, err = f.Write(data) + require.NoError(t, err) + require.NoError(t, f.Close()) +} + +func TestOpenFirstAlternative(t *testing.T) { + t.Parallel() + + genFS := func(t *testing.T) billy.Filesystem { + t.Helper() + fs := memfs.New() + writeFSFile(t, fs, "./test1.json", []byte("hello")) + writeFSFile(t, fs, "/dir/test2.json", []byte("hello")) + writeFSFile(t, fs, "dir2/test3.json", []byte("hello")) + return fs + } + for _, tc := range []struct { + name string + createFS func(t *testing.T) billy.Filesystem + mainPath string + alternatives []string + expectedFile string + mustErr bool + }{ + { + name: "mainpath", + createFS: genFS, + mainPath: "./test1.json", + alternatives: []string{"dir/test2.json", "dir2/test.json"}, + expectedFile: "test1.json", + mustErr: false, + }, + { + name: "dir-must-be-ignored", + createFS: func(t *testing.T) billy.Filesystem { + t.Helper() + fs := memfs.New() + writeFSFile(t, fs, "./file.json", []byte("hello")) + require.NoError(t, fs.MkdirAll("./dir", os.FileMode(0o755))) + return fs + }, + mainPath: "./dir", + alternatives: []string{"file.json"}, + expectedFile: "file.json", + mustErr: false, + }, + { + name: "first-alternative", + createFS: genFS, + mainPath: "./non-existent", + alternatives: []string{"dir/test2.json", "dir2/test.json"}, + expectedFile: "dir/test2.json", + mustErr: false, + }, + { + name: "second-alternative", + createFS: genFS, + mainPath: "./non-existent2", + alternatives: []string{"./non-existent", "dir2/test3.json"}, + expectedFile: "dir2/test3.json", + mustErr: false, + }, + { + name: "no-main", + createFS: genFS, + mainPath: "", + alternatives: []string{"dir2/test3.json"}, + expectedFile: "dir2/test3.json", + mustErr: false, + }, + { + name: "no-valid-files", + createFS: genFS, + mainPath: "non-existing", + alternatives: []string{"also-non-existing.txt"}, + expectedFile: "", + mustErr: true, + }, + { + name: "no-inputs", + createFS: genFS, + mainPath: "", + alternatives: []string{}, + expectedFile: "", + mustErr: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + fs := tc.createFS(t) + f, err := openFirstAlternative(fs, tc.mainPath, tc.alternatives) + if tc.mustErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.expectedFile, f.Name()) + }) + } +} + +func TestParseFileAlternatives(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + main string + mustErr bool + }{ + {"fn-success", "test1.json", false}, + {"fn-fails", "", true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + fs := memfs.New() + writeFSFile(t, fs, "./test1.json", []byte("{ \"a\": \"b\"}")) + res, err := parseFileAlternatives(fs, tc.main, []string{}) + if tc.mustErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.NotNil(t, res) + }) + } +} + +func TestNew(t *testing.T) { + t.Parallel() + h, err := newHandlerFromDef(&minderv1.StructDataSource_Def{ + Path: &minderv1.StructDataSource_Def_Path{ + FileName: "test.txt", + }, + }) + require.NoError(t, err) + require.NotNil(t, h) + + _, err = newHandlerFromDef(nil) + require.Error(t, err) +} + +func TestCall(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + buildContext func(t *testing.T) context.Context + def *minderv1.StructDataSource_Def + mustErr bool + }{ + { + "success", + func(t *testing.T) context.Context { + t.Helper() + fs := memfs.New() + writeFSFile(t, fs, "./test1.json", []byte("{ \"a\": \"b\"}")) + + return context.WithValue( + context.Background(), + v1datasources.ContextKey{}, + v1datasources.Context{ + Ingest: &interfaces.Result{Fs: fs}, + }, + ) + }, + &minderv1.StructDataSource_Def{ + Path: &minderv1.StructDataSource_Def_Path{ + FileName: "test1.json", + }, + }, + false, + }, + { + "no-datasource-context", + func(t *testing.T) context.Context { + t.Helper() + return context.Background() + }, + &minderv1.StructDataSource_Def{}, + true, + }, + {"ctx-no-fs", + func(t *testing.T) context.Context { + t.Helper() + return context.WithValue( + context.Background(), + v1datasources.ContextKey{}, + v1datasources.Context{}, + ) + }, + &minderv1.StructDataSource_Def{}, + true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + ctx := tc.buildContext(t) + handler, err := newHandlerFromDef(tc.def) + require.NoError(t, err) + _, err = handler.Call(ctx, []string{}) + if tc.mustErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/internal/datasources/structured/structured_test.go b/internal/datasources/structured/structured_test.go new file mode 100644 index 0000000000..c1f06e5144 --- /dev/null +++ b/internal/datasources/structured/structured_test.go @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors +// SPDX-License-Identifier: Apache-2.0 + +package structured + +import ( + "testing" + + "github.com/stretchr/testify/require" + + minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1" +) + +func TestNewStructDataSource(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + sds *minderv1.StructDataSource + mustErr bool + }{ + {"nil-def", nil, true}, + {"no-def", &minderv1.StructDataSource{}, true}, + {"invalid-def", &minderv1.StructDataSource{ + Def: map[string]*minderv1.StructDataSource_Def{"test": nil}, + }, true}, + {"success", &minderv1.StructDataSource{ + Def: map[string]*minderv1.StructDataSource_Def{ + "test": { + Path: &minderv1.StructDataSource_Def_Path{FileName: "test.yaml"}, + }, + }, + }, false}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + _, err := NewStructDataSource(tc.sds) + if tc.mustErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +}