From 765eb05ce9eee8a958ac69c14dcc21c43f453510 Mon Sep 17 00:00:00 2001 From: Dillon Lees Date: Mon, 11 Dec 2023 14:01:32 -0500 Subject: [PATCH] test: create cases for omitempty and stripepmtyentries --- go.mod | 4 + models/utils.go | 3 +- models/utils_test.go | 207 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 models/utils_test.go diff --git a/go.mod b/go.mod index 62f015d..809beee 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/spf13/cobra v1.3.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.10.1 + github.com/stretchr/testify v1.7.0 github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a go.uber.org/mock v0.2.0 golang.org/x/net v0.17.0 @@ -20,12 +21,14 @@ require ( require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -34,4 +37,5 @@ require ( golang.org/x/text v0.13.0 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/models/utils.go b/models/utils.go index 0b15231..491e0f9 100644 --- a/models/utils.go +++ b/models/utils.go @@ -26,9 +26,8 @@ func StripEmptyEntries(data map[string]any) { for _, item := range slice { if mapValue, ok := item.(map[string]any); ok { StripEmptyEntries(mapValue) - } else { - data[key] = append(data[key].([]any), item) } + data[key] = append(data[key].([]any), item) } } else { data[key] = value diff --git a/models/utils_test.go b/models/utils_test.go new file mode 100644 index 0000000..e77606b --- /dev/null +++ b/models/utils_test.go @@ -0,0 +1,207 @@ +package models_test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/bloodhoundad/azurehound/v2/models" + "github.com/stretchr/testify/require" +) + +type Foo struct { + Bar string +} + +func TestStripEmptyEntries(t *testing.T) { + t.Run("should omit empty basic types", func(t *testing.T) { + var pointer *Foo + + data := map[string]any{ + "array": [0]any{}, + "map": map[string]any{}, + "slice": []any{}, + "string": "", + "bool": false, + "float32": float32(0), + "float64": float64(0), + "int": int(0), + "int8": int8(0), + "int16": int16(0), + "int32": int32(0), + "int64": int64(0), + "uint": uint(0), + "uint8": uint8(0), + "uint16": uint16(0), + "uint32": uint32(0), + "uint64": uint64(0), + "pointer": pointer, + "nil": nil, + } + + require.NotEmpty(t, data) + models.StripEmptyEntries(data) + require.Empty(t, data) + }) + + t.Run("should not omit non-empty basic types", func(t *testing.T) { + data := map[string]any{ + "array": [1]any{1}, + "map": map[any]any{"foo": "bar"}, + "slice": []any{1}, + "string": "foo", + "bool": true, + "float32": float32(1), + "float64": float64(1), + "int": int(1), + "int8": int8(1), + "int16": int16(1), + "int32": int32(1), + "int64": int64(1), + "uint": uint(1), + "uint8": uint8(1), + "uint16": uint16(1), + "uint32": uint32(1), + "uint64": uint64(1), + "pointer": &Foo{}, + } + + require.NotEmpty(t, data) + numKeys := len(data) + models.StripEmptyEntries(data) + require.NotEmpty(t, data) + require.Equal(t, numKeys, len(data)) + }) + + t.Run("should not omit empty struct types", func(t *testing.T) { + data := map[string]any{ + "struct": Foo{Bar: "baz"}, + } + + require.NotEmpty(t, data) + numKeys := len(data) + models.StripEmptyEntries(data) + require.NotEmpty(t, data) + require.Equal(t, numKeys, len(data)) + require.NotEmpty(t, data["struct"]) + require.Equal(t, data["struct"].(Foo).Bar, "baz") + }) + + t.Run("should recursively strip non-empty, nested map[string]any entries", func(t *testing.T) { + data := map[string]any{ + "empty": map[string]any{}, + "nonempty": map[string]any{ + "emptyprop": 0, + "nonemptyprop": 1, + }, + } + + models.StripEmptyEntries(data) + require.Nil(t, data["empty"]) + require.NotNil(t, data["nonempty"]) + require.IsType(t, map[string]any{}, data["nonempty"]) + nested := data["nonempty"].(map[string]any) + require.Equal(t, 1, len(nested)) + require.Nil(t, nested["emptyprop"]) + require.Equal(t, 1, nested["nonemptyprop"]) + }) + + t.Run("should strip non-empty slice entries of type map[string]any", func(t *testing.T) { + data := map[string]any{ + "empty": []any{}, + "nonempty": []any{ + map[string]any{ + "emptyprop": 0, + "nonemptyprop": 1, + }, + }, + } + + fmt.Println(data["nonempty"]) + models.StripEmptyEntries(data) + require.Nil(t, data["empty"]) + require.NotNil(t, data["nonempty"]) + require.IsType(t, []any{}, data["nonempty"]) + slice := data["nonempty"].([]any) + fmt.Println(slice) + require.IsType(t, map[string]any{}, slice[0]) + entry := slice[0].(map[string]any) + require.Nil(t, entry["emptyprop"]) + require.Equal(t, 1, entry["nonemptyprop"]) + }) +} + +func TestOmitEmpty(t *testing.T) { + t.Run("should omit empty basic types", func(t *testing.T) { + data := json.RawMessage(`{ + "string": "", + "number": 0, + "object": {}, + "array": [], + "boolean": false, + "null": null + }`) + + filtered, err := models.OmitEmpty(data) + require.Nil(t, err) + require.Equal(t, `{}`, string(filtered)) + }) + + t.Run("should not omit non-empty basic types", func(t *testing.T) { + data := json.RawMessage(`{ + "string": "foo", + "number": 1, + "object": { "bar": "" }, + "array": [1], + "boolean": true + }`) + + filtered, err := models.OmitEmpty(data) + require.Nil(t, err) + require.Equal(t, `{"array":[1],"boolean":true,"number":1,"object":{},"string":"foo"}`, string(filtered)) + }) + + t.Run("should not omit empty struct/object types, just their empty properties", func(t *testing.T) { + data := json.RawMessage(`{ + "object": { "bar": "" } + }`) + + filtered, err := models.OmitEmpty(data) + require.Nil(t, err) + require.Equal(t, `{"object":{}}`, string(filtered)) + }) + + t.Run("should recursively strip non-empty, nested object entries", func(t *testing.T) { + data := json.RawMessage(`{ + "empty": {}, + "nonempty": { + "emptyprop": 0, + "nonemptyprop": 1 + } + }`) + + filtered, err := models.OmitEmpty(data) + require.Nil(t, err) + require.Equal(t, `{"nonempty":{"nonemptyprop":1}}`, string(filtered)) + }) + + t.Run("should strip non-empty array entries of type object", func(t *testing.T) { + data := json.RawMessage(`{ + "empty": [], + "nonempty": [{ + "emptyprop": 0, + "nonemptyprop": 1 + }] + }`) + + filtered, err := models.OmitEmpty(data) + require.Nil(t, err) + require.Equal(t, `{"nonempty":[{"nonemptyprop":1}]}`, string(filtered)) + }) + + t.Run("should return an error", func(t *testing.T) { + invalidJson := json.RawMessage(`{]}`) + _, err := models.OmitEmpty(invalidJson) + require.Error(t, err) + }) +}