123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- 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))
- })
- }
- }
|