diff --git a/util/csv_test.go b/util/csv_test.go new file mode 100644 index 00000000..575d83d9 --- /dev/null +++ b/util/csv_test.go @@ -0,0 +1,38 @@ +// This file is Free Software under the Apache-2.0 License +// without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +// +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: 2022 German Federal Office for Information Security (BSI) +// Software-Engineering: 2022 Intevation GmbH + +package util + +import ( + "bytes" + "testing" +) + +func TestCSV(t *testing.T) { + buf := new(bytes.Buffer) + csvWriter := NewFullyQuotedCSWWriter(buf) + for _, x := range [][]string{{"a", "b", "c"}, {"d", "e", "f"}} { + if err := csvWriter.Write(x); err != nil { + t.Error(err) + } + } + + csvWriter.Flush() + if err := csvWriter.Error(); err != nil { + t.Error(err) + } + for _, want := range []string{`"a","b","c"`, `"d","e","f"`} { + got, err := buf.ReadString('\n') + if err != nil { + t.Error(err) + } + if got[:len(got)-1] != want { + t.Errorf("FullyQuotedCSWWriter: Expected %q but got %q.", want, got) + } + } +} diff --git a/util/file_test.go b/util/file_test.go index 3f648b8b..28c51967 100644 --- a/util/file_test.go +++ b/util/file_test.go @@ -10,6 +10,8 @@ package util import ( "bytes" + "os" + "path/filepath" "testing" ) @@ -55,8 +57,51 @@ func TestConformingFileName(t *testing.T) { } } -func TestNWriter(t *testing.T) { +func TestIDMatchesFilename(t *testing.T) { + pathEval := NewPathEval() + + doc := make(map[string]any) + doc["document"] = map[string]any{ + "tracking": map[string]any{ + "id": "valid.json", + }, + } + + if err := IDMatchesFilename(pathEval, doc, "valid.json"); err != nil { + t.Errorf("IDMatchesFilename: Expected nil, got %q", err) + } + + if err := IDMatchesFilename(pathEval, doc, "different_file_name.json"); err == nil { + t.Error("IDMatchesFilename: Expected error, got nil") + } + doc["document"] = map[string]any{ + "tracking": map[string]any{}, + } + if err := IDMatchesFilename(pathEval, doc, "valid.json"); err == nil { + t.Error("IDMatchesFilename: Expected error, got nil") + } +} + +func TestPathExists(t *testing.T) { + got, err := PathExists("/this/path/does/not/exist") + if err != nil { + t.Error(err) + } + if got != false { + t.Error("PathExists: Expected false, got true") + } + dir := t.TempDir() + got, err = PathExists(dir) + if err != nil { + t.Error(err) + } + if got != true { + t.Error("PathExists: Expected true, got false") + } +} + +func TestNWriter(t *testing.T) { msg := []byte("Gruß!\n") first, second := msg[:len(msg)/2], msg[len(msg)/2:] @@ -78,3 +123,93 @@ func TestNWriter(t *testing.T) { t.Errorf("Expected %q, but got %q", msg, out) } } + +func TestWriteToFile(t *testing.T) { + filename := filepath.Join(t.TempDir(), "test_file") + wt := bytes.NewBufferString("test_data") + if err := WriteToFile(filename, wt); err != nil { + t.Error(err) + } + fileData, err := os.ReadFile(filename) + if err != nil { + t.Error(err) + } + if !bytes.Equal(fileData, []byte("test_data")) { + t.Errorf("DeepCopy: Expected test_data, got %v", fileData) + } +} + +func TestMakeUniqFile(t *testing.T) { + dir := t.TempDir() + _, file, err := MakeUniqFile(dir) + if err != nil { + t.Error(err) + } + if _, err = file.Write([]byte("test_data")); err != nil { + t.Error(err) + } + if err = file.Close(); err != nil { + t.Error(err) + } +} + +func Test_mkUniq(t *testing.T) { + dir := t.TempDir() + name, err := mkUniq(dir+"/", func(name string) error { + return nil + }) + if err != nil { + t.Error(err) + } + firstTime := true + name1, err := mkUniq(dir+"/", func(_ string) error { + if firstTime { + firstTime = false + return os.ErrExist + } + return nil + }) + if err != nil { + t.Error(err) + } + if name == name1 { + t.Errorf("mkUniq: Expected unique names, got %v and %v", name, name1) + } +} + +func TestDeepCopy(t *testing.T) { + dir := t.TempDir() + if err := os.MkdirAll(filepath.Join(dir, "src/folder0"), 0755); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(dir, "dst"), 0755); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(dir, "dst1"), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "src/folder0/test_file"), []byte("test_data"), 0755); err != nil { + t.Fatal(err) + } + + if err := DeepCopy(filepath.Join(dir, "dst"), filepath.Join(dir, "src")); err != nil { + t.Error(err) + } + + fileData, err := os.ReadFile(filepath.Join(dir, "dst/folder0/test_file")) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(fileData, []byte("test_data")) { + t.Errorf("DeepCopy: Expected test_data, got %v", fileData) + } + + if err = DeepCopy("/path/does/not/exist", filepath.Join(dir, "src")); err == nil { + t.Error("DeepCopy: Expected error, got nil") + } + + if err = DeepCopy(filepath.Join(dir, "dst1"), "/path/does/not/exist"); err == nil { + t.Error("DeepCopy: Expected error, got nil") + } +} diff --git a/util/hash_test.go b/util/hash_test.go new file mode 100644 index 00000000..d6908915 --- /dev/null +++ b/util/hash_test.go @@ -0,0 +1,107 @@ +// This file is Free Software under the Apache-2.0 License +// without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +// +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: 2022 German Federal Office for Information Security (BSI) +// Software-Engineering: 2022 Intevation GmbH + +package util + +import ( + "hash" + "os" + "path/filepath" + "reflect" + "strings" + "testing" +) + +func TestHashFromReader(t *testing.T) { + r := strings.NewReader("deadbeef") + want := []byte{0xde, 0xad, 0xbe, 0xef} + if got, err := HashFromReader(r); !reflect.DeepEqual(want, got) { + if err != nil { + t.Error(err) + } + t.Errorf("HashFromReader: Expected %v, got %v", want, got) + } +} + +func TestHashFromFile(t *testing.T) { + dir := t.TempDir() + filePath := filepath.Join(dir, "test_file") + testFile, err := os.Create(filePath) + if err != nil { + t.Error(err) + } + + testFile.WriteString("deadbeef") + want := []byte{0xde, 0xad, 0xbe, 0xef} + + testFile.Close() + + if got, err := HashFromFile(filePath); !reflect.DeepEqual(want, got) { + if err != nil { + t.Error(err) + } + t.Errorf("HashFromFile: Expected %v, got %v", want, got) + } +} + +type deadbeefHash struct { + hash.Hash +} + +func (deadbeefHash) Write(p []byte) (int, error) { return len(p), nil } +func (deadbeefHash) Sum(_ []byte) []byte { return []byte{0xde, 0xad, 0xbe, 0xef} } + +func TestWriteHashToFile(t *testing.T) { + dir := t.TempDir() + filePath := filepath.Join(dir, "test_file") + + hashArg := deadbeefHash{} + nameArg := "name" + want := "deadbeef " + nameArg + "\n" + + if err := WriteHashToFile(filePath, nameArg, hashArg, []byte{}); err != nil { + t.Error(err) + } + testFile, err := os.Open(filePath) + if err != nil { + t.Error(err) + } + defer testFile.Close() + fileContent, err := os.ReadFile(filePath) + if err != nil { + t.Error(err) + } + if got := string(fileContent); got != want { + t.Errorf("WriteHashToFile: Expected %v, got %v", want, got) + } +} + +func TestWriteHashSumToFile(t *testing.T) { + dir := t.TempDir() + filePath := filepath.Join(dir, "test_file") + + sum := []byte{0xde, 0xad, 0xbe, 0xef} + nameArg := "name" + want := "deadbeef " + nameArg + "\n" + + if err := WriteHashSumToFile(filePath, nameArg, sum); err != nil { + t.Error(err) + } + testFile, err := os.Open(filePath) + if err != nil { + t.Error(err) + } + defer testFile.Close() + fileContent, err := os.ReadFile(filePath) + if err != nil { + t.Error(err) + } + if got := string(fileContent); got != want { + t.Errorf("WriteHashSumToFile: Expected %v, got %v", want, got) + } +} diff --git a/util/json_test.go b/util/json_test.go new file mode 100644 index 00000000..ba18171c --- /dev/null +++ b/util/json_test.go @@ -0,0 +1,196 @@ +// This file is Free Software under the Apache-2.0 License +// without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +// +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: 2022 German Federal Office for Information Security (BSI) +// Software-Engineering: 2022 Intevation GmbH + +package util + +import ( + "context" + "reflect" + "testing" + "time" +) + +func TestPathEval_Compile(t *testing.T) { + pathEval := NewPathEval() + eval, err := pathEval.Compile("foo") + if err != nil { + t.Error(err) + } + + // Check caching + eval1, err := pathEval.Compile("foo") + if err != nil { + t.Error(err) + } + if reflect.ValueOf(eval).Pointer() != reflect.ValueOf(eval1).Pointer() { + t.Error("PathEval_Compile: Expected cached eval") + } + + got, err := eval.EvalInt(context.Background(), map[string]any{"foo": 5}) + if err != nil { + t.Error(err) + } + if got != 5 { + t.Errorf("PathEval_Compile: Expected 5, got %v", got) + } +} + +func TestPathEval_Eval(t *testing.T) { + pathEval := NewPathEval() + _, err := pathEval.Eval("foo", nil) + if err == nil { + t.Error("PathEval_Eval: Expected error, got nil") + } + got, err := pathEval.Eval("foo", map[string]any{"foo": 5}) + if err != nil { + t.Error(err) + } + if got != 5 { + t.Errorf("PathEval_Compile: Expected 5, got %v", got) + } +} + +func TestReMarshalMatcher(t *testing.T) { + var intDst int + var uintSrc uint = 2 + remarshalFunc := ReMarshalMatcher(&intDst) + if err := remarshalFunc(uintSrc); err != nil { + t.Error(err) + } + if intDst != 2 { + t.Errorf("ReMarshalMatcher: Expected %v, got %v", uintSrc, intDst) + } +} + +func TestBoolMatcher(t *testing.T) { + var boolDst bool + boolFunc := BoolMatcher(&boolDst) + if err := boolFunc(true); err != nil { + t.Error(err) + } + + if boolDst != true { + t.Error("BoolMatcher: Expected true got false") + } + + if err := boolFunc(1); err == nil { + t.Error("BoolMatcher: Expected error, got nil") + } +} + +func TestStringMatcher(t *testing.T) { + var stringDst string + stringFunc := StringMatcher(&stringDst) + if err := stringFunc("test"); err != nil { + t.Error(err) + } + + if stringDst != "test" { + t.Errorf("StringMatcher: Expected test, got %v", stringDst) + } + + if err := stringFunc(1); err == nil { + t.Error("StringMatcher: Expected error, got nil") + } +} + +func TestStringTreeMatcher(t *testing.T) { + var stringTreeDst []string + stringTreeFunc := StringTreeMatcher(&stringTreeDst) + if err := stringTreeFunc([]any{"a", "a", "b"}); err != nil { + t.Error(err) + } + + wantAnySlice := []any{"a", "b"} + if reflect.DeepEqual(stringTreeDst, wantAnySlice) { + t.Errorf("StringTreeMatcher: Expected %v, got %v", wantAnySlice, stringTreeDst) + } + + if err := stringTreeFunc([]string{"a", "a", "b"}); err == nil { + t.Error("StringTreeMatcher: Expected error, got nil") + } + + if err := stringTreeFunc(1); err == nil { + t.Error("StringTreeMatcher: Expected error, got nil") + } +} + +func TestTimeMatcher(t *testing.T) { + var timeDst time.Time + timeFunc := TimeMatcher(&timeDst, time.RFC3339) + if err := timeFunc("2024-03-18T12:57:48.236Z"); err != nil { + t.Error(err) + } + wantTime := time.Date(2024, time.March, 18, 12, 57, 48, 236_000_000, time.UTC) + if timeDst != wantTime { + t.Errorf("TimeMatcher: Expected %v, got %v", wantTime, timeDst) + } + + if err := timeFunc(""); err == nil { + t.Error("TimeMatcher: Expected error, got nil") + } + + if err := timeFunc(1); err == nil { + t.Error("TimeMatcher: Expected error, got nil") + } +} + +func TestPathEval_Extract(t *testing.T) { + pathEval := NewPathEval() + var result string + matcher := StringMatcher(&result) + if err := pathEval.Extract("foo", matcher, true, map[string]any{"foo": "bar"}); err != nil { + t.Error(err) + } + if result != "bar" { + t.Errorf("PathEval_Extract: Expected bar, got %v", result) + } +} + +func TestPathEval_Match(t *testing.T) { + var got string + doc := map[string]any{"foo": "bar"} + + pe := NewPathEval() + pem := PathEvalMatcher{Expr: "foo", Action: StringMatcher(&got)} + + if err := pe.Match([]PathEvalMatcher{pem}, doc); err != nil { + t.Error(err) + } + if got != "bar" { + t.Errorf("PathEval_Match: Expected bar, got %v", got) + } +} + +func TestPathEval_Strings(t *testing.T) { + pe := NewPathEval() + doc := map[string]any{"foo": "bar"} + want := []string{"bar"} + + got, err := pe.Strings([]string{"foo"}, true, doc) + if err != nil { + t.Error(err) + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("PathEval_Strings: Expected %v, got %v", want, got) + } +} + +func TestAsStrings(t *testing.T) { + arg := []any{"foo", "bar"} + want := []string{"foo", "bar"} + + got, valid := AsStrings(arg) + if !valid { + t.Error("AsStrings: Expected true, got false") + } + if !reflect.DeepEqual(got, want) { + t.Errorf("AsStrings: Expected %v, got %v", want, got) + } +} diff --git a/util/set_test.go b/util/set_test.go new file mode 100644 index 00000000..a28878e6 --- /dev/null +++ b/util/set_test.go @@ -0,0 +1,65 @@ +// This file is Free Software under the Apache-2.0 License +// without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +// +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: 2022 German Federal Office for Information Security (BSI) +// Software-Engineering: 2022 Intevation GmbH + +package util + +import ( + "reflect" + "sort" + "testing" +) + +func TestSet(t *testing.T) { + s := Set[int]{} + if s.Contains(0) { + t.Error("Set.Contains: Expected false got true") + } + s.Add(0) + if !s.Contains(0) { + t.Error("Set.Contains: Expected true got false") + } + + s0 := Set[int]{} + s1 := Set[int]{} + + s0.Add(0) + s0.Add(1) + + s1.Add(0) + s1.Add(1) + s1.Add(2) + + diff0 := s0.Difference(s1) + diff1 := s1.Difference(s0) + + if reflect.DeepEqual(diff0, diff1) { + t.Errorf("Set.Difference: %q and %q are different", diff0, diff1) + } + + if s0.ContainsAll(s1) { + t.Error("Set.ContainsAll: Expected false got true") + } + + if !s1.ContainsAll(s0) { + t.Error("Set.ContainsAll: Expected true got false") + } + + s2 := Set[int]{} + s2.Add(0) + s2.Add(1) + s2.Add(2) + s2.Add(3) + + wantKeys := []int{0, 1, 2, 3} + gotKeys := s2.Keys() + sort.Ints(gotKeys) + + if !reflect.DeepEqual(wantKeys, gotKeys) { + t.Errorf("Set.Keys: Expected %q got %q", wantKeys, gotKeys) + } +} diff --git a/util/url_test.go b/util/url_test.go new file mode 100644 index 00000000..dec73dce --- /dev/null +++ b/util/url_test.go @@ -0,0 +1,36 @@ +// This file is Free Software under the Apache-2.0 License +// without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +// +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: 2022 German Federal Office for Information Security (BSI) +// Software-Engineering: 2022 Intevation GmbH + +package util + +import ( + "net/url" + "testing" +) + +func TestBaseUrl(t *testing.T) { + for _, x := range [][2]string{ + {`http://example.com`, `http://example.com/`}, + {`scheme://example.com`, `scheme://example.com/`}, + {`https://example.com`, `https://example.com/`}, + {`https://example.com:8080/`, `https://example.com:8080/`}, + {`https://user@example.com:8080/`, `https://user@example.com:8080/`}, + {`https://user@example.com:8080/resource`, `https://user@example.com:8080/`}, + {`https://user@example.com:8080/resource/`, `https://user@example.com:8080/resource/`}, + {`https://user@example.com:8080/resource/#fragment`, `https://user@example.com:8080/resource/`}, + {`https://user@example.com:8080/resource/?query=test#fragment`, `https://user@example.com:8080/resource/`}, + } { + url, _ := url.Parse(x[0]) + if got, err := BaseURL(url); got != x[1] { + if err != nil { + t.Error(err) + } + t.Errorf("%q: Expected %q but got %q.", x[0], x[1], got) + } + } +}