package json import ( "testing" "github.com/getsops/sops/v3" "github.com/getsops/sops/v3/config" "github.com/stretchr/testify/assert" ) func TestDecodeJSON(t *testing.T) { in := ` { "glossary":{ "title":"example glossary", "GlossDiv":{ "title":"S", "GlossList":{ "GlossEntry":{ "ID":"SGML", "SortAs":"SGML", "GlossTerm":"Standard Generalized Markup Language", "Acronym":"SGML", "Abbrev":"ISO 8879:1986", "GlossDef":{ "para":"A meta-markup language, used to create markup languages such as DocBook.", "GlossSeeAlso":[ "GML", "XML" ] }, "GlossSee":"markup" } } } } } ` expected := sops.TreeBranch{ sops.TreeItem{ Key: "glossary", Value: sops.TreeBranch{ sops.TreeItem{ Key: "title", Value: "example glossary", }, sops.TreeItem{ Key: "GlossDiv", Value: sops.TreeBranch{ sops.TreeItem{ Key: "title", Value: "S", }, sops.TreeItem{ Key: "GlossList", Value: sops.TreeBranch{ sops.TreeItem{ Key: "GlossEntry", Value: sops.TreeBranch{ sops.TreeItem{ Key: "ID", Value: "SGML", }, sops.TreeItem{ Key: "SortAs", Value: "SGML", }, sops.TreeItem{ Key: "GlossTerm", Value: "Standard Generalized Markup Language", }, sops.TreeItem{ Key: "Acronym", Value: "SGML", }, sops.TreeItem{ Key: "Abbrev", Value: "ISO 8879:1986", }, sops.TreeItem{ Key: "GlossDef", Value: sops.TreeBranch{ sops.TreeItem{ Key: "para", Value: "A meta-markup language, used to create markup languages such as DocBook.", }, sops.TreeItem{ Key: "GlossSeeAlso", Value: []interface{}{ "GML", "XML", }, }, }, }, sops.TreeItem{ Key: "GlossSee", Value: "markup", }, }, }, }, }, }, }, }, }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestDecodeSimpleJSONObject(t *testing.T) { in := `{"foo": "bar", "baz": 2}` expected := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: "bar", }, sops.TreeItem{ Key: "baz", Value: 2.0, }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestDecodeNumber(t *testing.T) { in := `42` _, err := Store{}.treeBranchFromJSON([]byte(in)) assert.NotNil(t, err) assert.Equal(t, "Expected JSON object start, got 42 of type float64 instead", err.Error()) } func TestDecodeArray(t *testing.T) { in := ` [42] ` _, err := Store{}.treeBranchFromJSON([]byte(in)) assert.NotNil(t, err) assert.Equal(t, "Expected JSON object start, got delimiter [ instead", err.Error()) } func TestDecodeEmpty(t *testing.T) { in := `` _, err := Store{}.treeBranchFromJSON([]byte(in)) assert.NotNil(t, err) assert.Equal(t, "EOF", err.Error()) } func TestDecodeNestedJSONObject(t *testing.T) { in := `{"foo": {"foo": "bar"}}` expected := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: "bar", }, }, }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestDecodeJSONWithArray(t *testing.T) { in := `{"foo": {"foo": [1, 2, 3]}, "bar": "baz"}` expected := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{1.0, 2.0, 3.0}, }, }, }, sops.TreeItem{ Key: "bar", Value: "baz", }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestDecodeJSONArrayOfObjects(t *testing.T) { in := `{"foo": [{"bar": "foo"}, {"foo": "bar"}]}` expected := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{ sops.TreeBranch{ sops.TreeItem{ Key: "bar", Value: "foo", }, }, sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: "bar", }, }, }, }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestDecodeJSONArrayOfArrays(t *testing.T) { in := `{"foo": [[["foo", {"bar": "foo"}]]]}` expected := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{ []interface{}{ []interface{}{ "foo", sops.TreeBranch{ sops.TreeItem{ Key: "bar", Value: "foo", }, }, }, }, }, }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestEncodeSimpleJSON(t *testing.T) { branch := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: "bar", }, sops.TreeItem{ Key: "foo", Value: 3.0, }, sops.TreeItem{ Key: "bar", Value: false, }, } out, err := Store{}.jsonFromTreeBranch(branch) assert.Nil(t, err) expected, _ := Store{}.treeBranchFromJSON(out) assert.Equal(t, expected, branch) } func TestEncodeJSONWithEscaping(t *testing.T) { branch := sops.TreeBranch{ sops.TreeItem{ Key: "foo\\bar", Value: "value", }, sops.TreeItem{ Key: "a_key_with\"quotes\"", Value: 4.0, }, sops.TreeItem{ Key: "baz\\\\foo", Value: 2.0, }, } out, err := Store{}.jsonFromTreeBranch(branch) assert.Nil(t, err) expected, _ := Store{}.treeBranchFromJSON(out) assert.Equal(t, expected, branch) } func TestEncodeJSONArrayOfObjects(t *testing.T) { tree := sops.Tree{ Branches: sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: 3, }, sops.TreeItem{ Key: "bar", Value: false, }, }, 2, }, }, }, }, } expected := `{ "foo": [ { "foo": 3, "bar": false }, 2 ] } ` store := Store{ config: config.JSONStoreConfig{ Indent: -1, }, } out, err := store.EmitPlainFile(tree.Branches) assert.Nil(t, err) assert.Equal(t, expected, string(out)) } func TestUnmarshalMetadataFromNonSOPSFile(t *testing.T) { data := []byte(`{"hello": 2}`) store := Store{} _, err := store.LoadEncryptedFile(data) assert.Equal(t, sops.MetadataNotFound, err) } func TestLoadJSONFormattedBinaryFile(t *testing.T) { // This is JSON data, but we want SOPS to interpret it as binary, // e.g. because the --input-type binary flag was provided. data := []byte(`{"hello": 2}`) store := BinaryStore{} branches, err := store.LoadPlainFile(data) assert.Nil(t, err) assert.Equal(t, "data", branches[0][0].Key) } func TestEmitBinaryFile(t *testing.T) { store := BinaryStore{} data, err := store.EmitPlainFile(sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "data", Value: "foo", }, }, }) assert.Nil(t, err) assert.Equal(t, []byte("foo"), data) } func TestEmitBinaryFileWrongBranches(t *testing.T) { store := BinaryStore{} data, err := store.EmitPlainFile(sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "data", Value: "bar", }, }, sops.TreeBranch{ sops.TreeItem{ Key: "data", Value: "bar", }, }, }) assert.Nil(t, data) assert.Contains(t, err.Error(), "there must be exactly one tree branch") data, err = store.EmitPlainFile(sops.TreeBranches{}) assert.Nil(t, data) assert.Contains(t, err.Error(), "there must be exactly one tree branch") } func TestEmitBinaryFileNoData(t *testing.T) { store := BinaryStore{} data, err := store.EmitPlainFile(sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: "bar", }, }, }) assert.Nil(t, data) assert.Contains(t, err.Error(), "no binary data found in tree") } func TestEmitBinaryFileWrongDataType(t *testing.T) { store := BinaryStore{} data, err := store.EmitPlainFile(sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "data", Value: sops.TreeItem{ Key: "foo", Value: "bar", }, }, }, }) assert.Nil(t, data) assert.Contains(t, err.Error(), "'data' key in tree does not have a string value") } func TestEmitValueString(t *testing.T) { bytes, err := (&Store{}).EmitValue("hello") assert.Nil(t, err) assert.Equal(t, []byte("\"hello\""), bytes) } func TestIndentTwoSpaces(t *testing.T) { tree := sops.Tree{ Branches: sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: 3, }, sops.TreeItem{ Key: "bar", Value: false, }, }, 2, }, }, }, }, } expected := `{ "foo": [ { "foo": 3, "bar": false }, 2 ] } ` store := Store{ config: config.JSONStoreConfig{ Indent: 2, }, } out, err := store.EmitPlainFile(tree.Branches) assert.Nil(t, err) assert.Equal(t, expected, string(out)) } func TestIndentDefault(t *testing.T) { tree := sops.Tree{ Branches: sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: 3, }, sops.TreeItem{ Key: "bar", Value: false, }, }, 2, }, }, }, }, } expected := `{ "foo": [ { "foo": 3, "bar": false }, 2 ] } ` store := Store{ config: config.JSONStoreConfig{ Indent: -1, }, } out, err := store.EmitPlainFile(tree.Branches) assert.Nil(t, err) assert.Equal(t, expected, string(out)) } func TestNoIndent(t *testing.T) { tree := sops.Tree{ Branches: sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: 3, }, sops.TreeItem{ Key: "bar", Value: false, }, }, 2, }, }, }, }, } expected := `{ "foo": [ { "foo": 3, "bar": false }, 2 ] } ` store := Store{ config: config.JSONStoreConfig{ Indent: 0, }, } out, err := store.EmitPlainFile(tree.Branches) assert.Nil(t, err) assert.Equal(t, expected, string(out)) } func TestConflictingAttributes(t *testing.T) { // See https://stackoverflow.com/a/23195243 // Duplicate keys in json is technically valid, but discouraged. // Implementations may handle them differently. ECMA-262 says // // > In the case where there are duplicate name Strings within an object, // > lexically preceding values for the same key shall be overwritten. data := ` { "hello": "Sops config file", "hello": "Doubles are ok", "hello": ["repeatedly"], "hello": 3.14 } ` s := new(Store) _, err := s.LoadPlainFile([]byte(data)) assert.Nil(t, err) } func TestComments(t *testing.T) { tree := sops.Tree{ Branches: sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{ sops.Comment{Value: " comment 0"}, sops.TreeBranch{ sops.TreeItem{ Key: sops.Comment{Value: " comment 1"}, Value: nil, }, sops.TreeItem{ Key: "foo", Value: 3, }, sops.TreeItem{ Key: sops.Comment{Value: " comment 2"}, Value: nil, }, sops.TreeItem{ Key: sops.Comment{Value: " comment 3"}, Value: nil, }, sops.TreeItem{ Key: "bar", Value: false, }, sops.TreeItem{ Key: sops.Comment{Value: " comment 4"}, Value: nil, }, sops.TreeItem{ Key: sops.Comment{Value: " comment 5"}, Value: nil, }, }, sops.Comment{Value: " comment 6"}, sops.Comment{Value: " comment 7"}, 2, sops.Comment{Value: " comment 8"}, sops.Comment{Value: " comment 9"}, }, }, }, }, } expected := `{ "foo": [ { "foo": 3, "bar": false }, 2 ] } ` store := Store{ config: config.JSONStoreConfig{ Indent: 2, }, } out, err := store.EmitPlainFile(tree.Branches) assert.Nil(t, err) assert.Equal(t, expected, string(out)) }