patcher_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. package yamlpatch_test
  2. import (
  3. "os"
  4. "path/filepath"
  5. "testing"
  6. "github.com/crowdsecurity/crowdsec/pkg/yamlpatch"
  7. "github.com/stretchr/testify/require"
  8. )
  9. // similar to the one in cstest, but with test number too. We cannot import
  10. // cstest here because of circular dependency.
  11. func requireErrorContains(t *testing.T, err error, expectedErr string) {
  12. t.Helper()
  13. if expectedErr != "" {
  14. require.ErrorContains(t, err, expectedErr)
  15. return
  16. }
  17. require.NoError(t, err)
  18. }
  19. func TestMergedPatchContent(t *testing.T) {
  20. t.Parallel()
  21. tests := []struct {
  22. name string
  23. base string
  24. patch string
  25. expected string
  26. expectedErr string
  27. }{
  28. {
  29. "invalid yaml in base",
  30. "notayaml",
  31. "",
  32. "",
  33. "config.yaml: yaml: unmarshal errors:",
  34. },
  35. {
  36. "invalid yaml in base (detailed message)",
  37. "notayaml",
  38. "",
  39. "",
  40. "cannot unmarshal !!str `notayaml`",
  41. },
  42. {
  43. "invalid yaml in patch",
  44. "",
  45. "notayaml",
  46. "",
  47. "config.yaml.local: yaml: unmarshal errors:",
  48. },
  49. {
  50. "invalid yaml in patch (detailed message)",
  51. "",
  52. "notayaml",
  53. "",
  54. "cannot unmarshal !!str `notayaml`",
  55. },
  56. {
  57. "basic merge",
  58. "{'first':{'one':1,'two':2},'second':{'three':3}}",
  59. "{'first':{'one':10,'dos':2}}",
  60. "{'first':{'one':10,'dos':2,'two':2},'second':{'three':3}}",
  61. "",
  62. },
  63. // bools and zero values; here the "mergo" package had issues
  64. // so we used something simpler.
  65. {
  66. "bool merge - off if false",
  67. "bool: on",
  68. "bool: off",
  69. "bool: false",
  70. "",
  71. },
  72. {
  73. "bool merge - on is true",
  74. "bool: off",
  75. "bool: on",
  76. "bool: true",
  77. "",
  78. },
  79. {
  80. "string is not a bool - on to off",
  81. "{'bool': 'on'}",
  82. "{'bool': 'off'}",
  83. "{'bool': 'off'}",
  84. "",
  85. },
  86. {
  87. "string is not a bool - off to on",
  88. "{'bool': 'off'}",
  89. "{'bool': 'on'}",
  90. "{'bool': 'on'}",
  91. "",
  92. },
  93. {
  94. "bool merge - true to false",
  95. "{'bool': true}",
  96. "{'bool': false}",
  97. "{'bool': false}",
  98. "",
  99. },
  100. {
  101. "bool merge - false to true",
  102. "{'bool': false}",
  103. "{'bool': true}",
  104. "{'bool': true}",
  105. "",
  106. },
  107. {
  108. "string merge - value to value",
  109. "{'string': 'value'}",
  110. "{'string': ''}",
  111. "{'string': ''}",
  112. "",
  113. },
  114. {
  115. "sequence merge - value to empty",
  116. "{'sequence': [1, 2]}",
  117. "{'sequence': []}",
  118. "{'sequence': []}",
  119. "",
  120. },
  121. {
  122. "map merge - value to value",
  123. "{'map': {'one': 1, 'two': 2}}",
  124. "{'map': {}}",
  125. "{'map': {'one': 1, 'two': 2}}",
  126. "",
  127. },
  128. // mismatched types
  129. {
  130. "can't merge a sequence into a mapping",
  131. "map: {'key': 'value'}",
  132. "map: ['value1', 'value2']",
  133. "",
  134. "can't merge a sequence into a mapping",
  135. },
  136. {
  137. "can't merge a scalar into a mapping",
  138. "map: {'key': 'value'}",
  139. "map: 3",
  140. "",
  141. "can't merge a scalar into a mapping",
  142. },
  143. {
  144. "can't merge a mapping into a sequence",
  145. "sequence: ['value1', 'value2']",
  146. "sequence: {'key': 'value'}",
  147. "",
  148. "can't merge a mapping into a sequence",
  149. },
  150. {
  151. "can't merge a scalar into a sequence",
  152. "sequence: ['value1', 'value2']",
  153. "sequence: 3",
  154. "",
  155. "can't merge a scalar into a sequence",
  156. },
  157. {
  158. "can't merge a sequence into a scalar",
  159. "scalar: true",
  160. "scalar: ['value1', 'value2']",
  161. "",
  162. "can't merge a sequence into a scalar",
  163. },
  164. {
  165. "can't merge a mapping into a scalar",
  166. "scalar: true",
  167. "scalar: {'key': 'value'}",
  168. "",
  169. "can't merge a mapping into a scalar",
  170. },
  171. }
  172. for _, tc := range tests {
  173. tc := tc
  174. t.Run(tc.name, func(t *testing.T) {
  175. t.Parallel()
  176. dirPath, err := os.MkdirTemp("", "yamlpatch")
  177. require.NoError(t, err)
  178. defer os.RemoveAll(dirPath)
  179. configPath := filepath.Join(dirPath, "config.yaml")
  180. patchPath := filepath.Join(dirPath, "config.yaml.local")
  181. err = os.WriteFile(configPath, []byte(tc.base), 0o600)
  182. require.NoError(t, err)
  183. err = os.WriteFile(patchPath, []byte(tc.patch), 0o600)
  184. require.NoError(t, err)
  185. patcher := yamlpatch.NewPatcher(configPath, ".local")
  186. patchedBytes, err := patcher.MergedPatchContent()
  187. requireErrorContains(t, err, tc.expectedErr)
  188. require.YAMLEq(t, tc.expected, string(patchedBytes))
  189. })
  190. }
  191. }
  192. func TestPrependedPatchContent(t *testing.T) {
  193. t.Parallel()
  194. tests := []struct {
  195. name string
  196. base string
  197. patch string
  198. expected string
  199. expectedErr string
  200. }{
  201. // we test with scalars here, because YAMLeq does not work
  202. // with multi-document files, so we need char-to-char comparison
  203. // which is noisy with sequences and (unordered) mappings
  204. {
  205. "newlines are always appended, if missing, by yaml.Marshal()",
  206. "foo: bar",
  207. "",
  208. "foo: bar\n",
  209. "",
  210. },
  211. {
  212. "prepend empty document",
  213. "foo: bar\n",
  214. "",
  215. "foo: bar\n",
  216. "",
  217. },
  218. {
  219. "prepend a document to another",
  220. "foo: bar",
  221. "baz: qux",
  222. "baz: qux\n---\nfoo: bar\n",
  223. "",
  224. },
  225. {
  226. "prepend document with same key",
  227. "foo: true",
  228. "foo: false",
  229. "foo: false\n---\nfoo: true\n",
  230. "",
  231. },
  232. {
  233. "prepend multiple documents",
  234. "one: 1\n---\ntwo: 2\n---\none: 3",
  235. "four: 4\n---\none: 1.1",
  236. "four: 4\n---\none: 1.1\n---\none: 1\n---\ntwo: 2\n---\none: 3\n",
  237. "",
  238. },
  239. {
  240. "invalid yaml in base",
  241. "blablabla",
  242. "",
  243. "",
  244. "config.yaml: yaml: unmarshal errors:",
  245. },
  246. {
  247. "invalid yaml in base (detailed message)",
  248. "blablabla",
  249. "",
  250. "",
  251. "cannot unmarshal !!str `blablabla`",
  252. },
  253. {
  254. "invalid yaml in patch",
  255. "",
  256. "blablabla",
  257. "",
  258. "config.yaml.local: yaml: unmarshal errors:",
  259. },
  260. {
  261. "invalid yaml in patch (detailed message)",
  262. "",
  263. "blablabla",
  264. "",
  265. "cannot unmarshal !!str `blablabla`",
  266. },
  267. }
  268. for _, tc := range tests {
  269. tc := tc
  270. t.Run(tc.name, func(t *testing.T) {
  271. t.Parallel()
  272. dirPath, err := os.MkdirTemp("", "yamlpatch")
  273. require.NoError(t, err)
  274. defer os.RemoveAll(dirPath)
  275. configPath := filepath.Join(dirPath, "config.yaml")
  276. patchPath := filepath.Join(dirPath, "config.yaml.local")
  277. err = os.WriteFile(configPath, []byte(tc.base), 0o600)
  278. require.NoError(t, err)
  279. err = os.WriteFile(patchPath, []byte(tc.patch), 0o600)
  280. require.NoError(t, err)
  281. patcher := yamlpatch.NewPatcher(configPath, ".local")
  282. patchedBytes, err := patcher.PrependedPatchContent()
  283. requireErrorContains(t, err, tc.expectedErr)
  284. // YAMLeq does not handle multiple documents
  285. require.Equal(t, tc.expected, string(patchedBytes))
  286. })
  287. }
  288. }