crowdsec/pkg/yamlpatch/patcher_test.go
mmetc 98f2ac5e7c
fix #1385: .yaml.local (#1497)
Added support for .yaml.local files to override values in .yaml
2022-05-18 10:08:37 +02:00

314 lines
6.3 KiB
Go

package yamlpatch_test
import (
"os"
"path/filepath"
"testing"
"github.com/crowdsecurity/crowdsec/pkg/yamlpatch"
"github.com/stretchr/testify/require"
)
// similar to the one in cstest, but with test number too. We cannot import
// cstest here because of circular dependency.
func requireErrorContains(t *testing.T, err error, expectedErr string) {
t.Helper()
if expectedErr != "" {
require.ErrorContains(t, err, expectedErr)
return
}
require.NoError(t, err)
}
func TestMergedPatchContent(t *testing.T) {
t.Parallel()
tests := []struct {
name string
base string
patch string
expected string
expectedErr string
}{
{
"invalid yaml in base",
"notayaml",
"",
"",
"config.yaml: yaml: unmarshal errors:",
},
{
"invalid yaml in base (detailed message)",
"notayaml",
"",
"",
"cannot unmarshal !!str `notayaml`",
},
{
"invalid yaml in patch",
"",
"notayaml",
"",
"config.yaml.local: yaml: unmarshal errors:",
},
{
"invalid yaml in patch (detailed message)",
"",
"notayaml",
"",
"cannot unmarshal !!str `notayaml`",
},
{
"basic merge",
"{'first':{'one':1,'two':2},'second':{'three':3}}",
"{'first':{'one':10,'dos':2}}",
"{'first':{'one':10,'dos':2,'two':2},'second':{'three':3}}",
"",
},
// bools and zero values; here the "mergo" package had issues
// so we used something simpler.
{
"bool merge - off if false",
"bool: on",
"bool: off",
"bool: false",
"",
},
{
"bool merge - on is true",
"bool: off",
"bool: on",
"bool: true",
"",
},
{
"string is not a bool - on to off",
"{'bool': 'on'}",
"{'bool': 'off'}",
"{'bool': 'off'}",
"",
},
{
"string is not a bool - off to on",
"{'bool': 'off'}",
"{'bool': 'on'}",
"{'bool': 'on'}",
"",
},
{
"bool merge - true to false",
"{'bool': true}",
"{'bool': false}",
"{'bool': false}",
"",
},
{
"bool merge - false to true",
"{'bool': false}",
"{'bool': true}",
"{'bool': true}",
"",
},
{
"string merge - value to value",
"{'string': 'value'}",
"{'string': ''}",
"{'string': ''}",
"",
},
{
"sequence merge - value to empty",
"{'sequence': [1, 2]}",
"{'sequence': []}",
"{'sequence': []}",
"",
},
{
"map merge - value to value",
"{'map': {'one': 1, 'two': 2}}",
"{'map': {}}",
"{'map': {'one': 1, 'two': 2}}",
"",
},
// mismatched types
{
"can't merge a sequence into a mapping",
"map: {'key': 'value'}",
"map: ['value1', 'value2']",
"",
"can't merge a sequence into a mapping",
},
{
"can't merge a scalar into a mapping",
"map: {'key': 'value'}",
"map: 3",
"",
"can't merge a scalar into a mapping",
},
{
"can't merge a mapping into a sequence",
"sequence: ['value1', 'value2']",
"sequence: {'key': 'value'}",
"",
"can't merge a mapping into a sequence",
},
{
"can't merge a scalar into a sequence",
"sequence: ['value1', 'value2']",
"sequence: 3",
"",
"can't merge a scalar into a sequence",
},
{
"can't merge a sequence into a scalar",
"scalar: true",
"scalar: ['value1', 'value2']",
"",
"can't merge a sequence into a scalar",
},
{
"can't merge a mapping into a scalar",
"scalar: true",
"scalar: {'key': 'value'}",
"",
"can't merge a mapping into a scalar",
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
dirPath, err := os.MkdirTemp("", "yamlpatch")
require.NoError(t, err)
defer os.RemoveAll(dirPath)
configPath := filepath.Join(dirPath, "config.yaml")
patchPath := filepath.Join(dirPath, "config.yaml.local")
err = os.WriteFile(configPath, []byte(tc.base), 0o600)
require.NoError(t, err)
err = os.WriteFile(patchPath, []byte(tc.patch), 0o600)
require.NoError(t, err)
patcher := yamlpatch.NewPatcher(configPath, ".local")
patchedBytes, err := patcher.MergedPatchContent()
requireErrorContains(t, err, tc.expectedErr)
require.YAMLEq(t, tc.expected, string(patchedBytes))
})
}
}
func TestPrependedPatchContent(t *testing.T) {
t.Parallel()
tests := []struct {
name string
base string
patch string
expected string
expectedErr string
}{
// we test with scalars here, because YAMLeq does not work
// with multi-document files, so we need char-to-char comparison
// which is noisy with sequences and (unordered) mappings
{
"newlines are always appended, if missing, by yaml.Marshal()",
"foo: bar",
"",
"foo: bar\n",
"",
},
{
"prepend empty document",
"foo: bar\n",
"",
"foo: bar\n",
"",
},
{
"prepend a document to another",
"foo: bar",
"baz: qux",
"baz: qux\n---\nfoo: bar\n",
"",
},
{
"prepend document with same key",
"foo: true",
"foo: false",
"foo: false\n---\nfoo: true\n",
"",
},
{
"prepend multiple documents",
"one: 1\n---\ntwo: 2\n---\none: 3",
"four: 4\n---\none: 1.1",
"four: 4\n---\none: 1.1\n---\none: 1\n---\ntwo: 2\n---\none: 3\n",
"",
},
{
"invalid yaml in base",
"blablabla",
"",
"",
"config.yaml: yaml: unmarshal errors:",
},
{
"invalid yaml in base (detailed message)",
"blablabla",
"",
"",
"cannot unmarshal !!str `blablabla`",
},
{
"invalid yaml in patch",
"",
"blablabla",
"",
"config.yaml.local: yaml: unmarshal errors:",
},
{
"invalid yaml in patch (detailed message)",
"",
"blablabla",
"",
"cannot unmarshal !!str `blablabla`",
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
dirPath, err := os.MkdirTemp("", "yamlpatch")
require.NoError(t, err)
defer os.RemoveAll(dirPath)
configPath := filepath.Join(dirPath, "config.yaml")
patchPath := filepath.Join(dirPath, "config.yaml.local")
err = os.WriteFile(configPath, []byte(tc.base), 0o600)
require.NoError(t, err)
err = os.WriteFile(patchPath, []byte(tc.patch), 0o600)
require.NoError(t, err)
patcher := yamlpatch.NewPatcher(configPath, ".local")
patchedBytes, err := patcher.PrependedPatchContent()
requireErrorContains(t, err, tc.expectedErr)
// YAMLeq does not handle multiple documents
require.Equal(t, tc.expected, string(patchedBytes))
})
}
}