github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/easy/yamlx/yamlx_test.go (about) 1 package yamlx 2 3 import ( 4 "os" 5 "path/filepath" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 "github.com/tidwall/gjson" 11 12 "github.com/jxskiss/gopkg/v2/perf/json" 13 ) 14 15 func TestMarshal(t *testing.T) { 16 data := map[string]any{ 17 "a": 1, 18 "b": "2", 19 "c": 3.14, 20 } 21 buf, err := Marshal(data) 22 require.Nil(t, err) 23 assert.True(t, len(buf) > 0) 24 } 25 26 func TestUnmarshal(t *testing.T) { 27 t.Run("without extended feature", func(t *testing.T) { 28 yamlData, err := os.ReadFile("./testdata/normal.yaml") 29 require.Nil(t, err) 30 31 var out map[string]any 32 err = Unmarshal(yamlData, &out) 33 require.Nil(t, err) 34 assert.Len(t, out, 2) 35 assert.Len(t, out["definitions"], 1) 36 37 jsonData, err := json.MarshalToString(out) 38 require.Nil(t, err) 39 assert.Equal(t, "production", gjson.Get(jsonData, "pipelines.branches.main.1.step.deployment").String()) 40 assert.Equal(t, "manual", gjson.Get(jsonData, "pipelines.branches.main.1.step.trigger").String()) 41 }) 42 43 t.Run("env", func(t *testing.T) { 44 os.Setenv("MY_ENV_1", "value1") 45 os.Setenv("MY_ENV_3", "value3") 46 47 yamlData, err := os.ReadFile("./testdata/env.yaml") 48 require.Nil(t, err) 49 50 var out any 51 err = Unmarshal(yamlData, &out, EnableEnv()) 52 require.Nil(t, err) 53 assert.Len(t, out, 5) 54 55 jsonData, err := json.MarshalToString(out) 56 require.Nil(t, err) 57 assert.Equal(t, "value1", gjson.Get(jsonData, "0").String()) 58 assert.Equal(t, "value1", gjson.Get(jsonData, "1").String()) 59 assert.Equal(t, "value3", gjson.Get(jsonData, "2").String()) 60 assert.Equal(t, "value1", gjson.Get(jsonData, "3.key1").String()) 61 assert.Equal(t, "value1", gjson.Get(jsonData, "3.key2").String()) 62 assert.Equal(t, "value3", gjson.Get(jsonData, "3.key3").String()) 63 assert.True(t, gjson.Get(jsonData, "4.key4").Exists()) 64 assert.Equal(t, "", gjson.Get(jsonData, "4.key4").String()) 65 }) 66 67 t.Run("include / success", func(t *testing.T) { 68 yamlData, err := os.ReadFile("./testdata/include1.yaml") 69 require.Nil(t, err) 70 71 var out map[string]any 72 err = Unmarshal(yamlData, &out, EnableInclude()) 73 require.Nil(t, err) 74 assert.Len(t, out, 3) 75 76 jsonData, err := json.MarshalToString(out) 77 require.Nil(t, err) 78 assert.Equal(t, "value1", gjson.Get(jsonData, "key1.subkey1").String()) 79 assert.Equal(t, "production", gjson.Get(jsonData, "key1.subkey3.pipelines.branches.main.1.step.deployment").String()) 80 assert.Equal(t, "production", gjson.Get(jsonData, "key1.subkey4.pipelines.branches.main.1.step.deployment").String()) 81 assert.EqualValues(t, 12345, gjson.Get(jsonData, "key2.array_1.1").Int()) 82 assert.EqualValues(t, 12345, gjson.Get(jsonData, "key3.sub1.sub2.0.sub3_k1").Int()) 83 assert.Equal(t, "production", gjson.Get(jsonData, "key3.sub1.sub2.0.sub3_k2.pipelines.branches.main.1.step.deployment").String()) 84 }) 85 86 t.Run("include / circular", func(t *testing.T) { 87 yamlData, err := os.ReadFile("./testdata/include_circular_1.yaml") 88 require.Nil(t, err) 89 90 var out map[string]any 91 err = Unmarshal(yamlData, &out, EnableInclude()) 92 require.NotNil(t, err) 93 assert.Contains(t, err.Error(), "circular include detected: ") 94 assert.Contains(t, err.Error(), filepath.Join("testdata", "include_circular_3.yaml")) 95 }) 96 97 t.Run("reference / success", func(t *testing.T) { 98 yamlData, err := os.ReadFile("./testdata/ref.yaml") 99 require.Nil(t, err) 100 101 var out map[string]any 102 err = Unmarshal(yamlData, &out) 103 require.Nil(t, err) 104 assert.Len(t, out, 9) 105 106 jsonData, err := json.MarshalToString(out) 107 require.Nil(t, err) 108 assert.Equal(t, "bar", gjson.Get(jsonData, "obj1.foo").String()) 109 assert.Equal(t, "bar", gjson.Get(jsonData, "test_ref1").String()) 110 assert.EqualValues(t, 3, gjson.Get(jsonData, "test_ref2").Int()) 111 assert.EqualValues(t, 3, gjson.Get(jsonData, "test_ref3").Int()) 112 assert.Equal(t, "bar", gjson.Get(jsonData, "test_ref4.key1.key2").String()) 113 assert.EqualValues(t, 3, gjson.Get(jsonData, "test_ref4.key1.key3").Int()) 114 assert.Equal(t, "bar", gjson.Get(jsonData, "friends.0.last").String()) 115 assert.EqualValues(t, 3, gjson.Get(jsonData, "friends.1.age").Int()) 116 assert.Equal(t, []any{"Dale", "Roger", "Jane"}, gjson.Get(jsonData, "test_ref5").Value()) 117 assert.Equal(t, 118 map[string]any{ 119 "key2": "bar", 120 "key3": float64(3), 121 "key4": []any{"Dale", "Roger", "Jane"}, 122 }, 123 gjson.Get(jsonData, "test_ref6.key1").Value()) 124 }) 125 126 t.Run("reference / not found", func(t *testing.T) { 127 yamlData, err := os.ReadFile("./testdata/ref_not_found.yaml") 128 require.Nil(t, err) 129 130 var out map[string]any 131 err = Unmarshal(yamlData, &out) 132 require.NotNil(t, err) 133 assert.Contains(t, err.Error(), "cannot find referenced data: friends.0.phone_number") 134 }) 135 136 t.Run("reference / circular", func(t *testing.T) { 137 yamlData, err := os.ReadFile("./testdata/ref_circular.yaml") 138 require.Nil(t, err) 139 140 var out map[string]any 141 err = Unmarshal(yamlData, &out) 142 require.NotNil(t, err) 143 assert.Contains(t, err.Error(), "circular reference detected: friends.#.age") 144 }) 145 146 t.Run("reference / relative", func(t *testing.T) { 147 yamlData, err := os.ReadFile("./testdata/ref_relative.yaml") 148 require.Nil(t, err) 149 150 var out map[string]any 151 err = Unmarshal(yamlData, &out) 152 require.Nil(t, err) 153 assert.Len(t, out, 2) 154 155 jsonData, err := json.MarshalToString(out) 156 require.Nil(t, err) 157 assert.Equal(t, "Deploy", gjson.Get(jsonData, "definitions.steps.1.step.name1").String()) 158 assert.Equal(t, "level2", gjson.Get(jsonData, "definitions.steps.1.step.name2").String()) 159 assert.Equal(t, "Build and test", gjson.Get(jsonData, "definitions.steps.1.step.name3").String()) 160 assert.Equal(t, "./deploy.sh target/my-app.jar", gjson.Get(jsonData, "definitions.steps.1.step.script.0").String()) 161 assert.Equal(t, "./deploy.sh target/my-app.jar", gjson.Get(jsonData, "definitions.steps.1.step.script.1").String()) 162 assert.Equal(t, "./deploy.sh target/my-app.jar", gjson.Get(jsonData, "definitions.steps.1.step.script.2").String()) 163 assert.Equal(t, "mvn package", gjson.Get(jsonData, "definitions.steps.1.step.script.3").String()) 164 assert.Equal(t, `{"deployment":"test","name":"Deploy","name1":"Deploy","name2":"level2","name3":"Build and test","script":["./deploy.sh target/my-app.jar","./deploy.sh target/my-app.jar","./deploy.sh target/my-app.jar","mvn package"]}`, gjson.Get(jsonData, "definitions.steps.1.tostr").Str) 165 }) 166 167 t.Run("variable / success", func(t *testing.T) { 168 yamlData, err := os.ReadFile("./testdata/variable.yaml") 169 require.Nil(t, err) 170 171 var out map[string]any 172 err = Unmarshal(yamlData, &out) 173 require.Nil(t, err) 174 assert.Len(t, out, 3) 175 176 jsonData, err := json.MarshalToString(out) 177 require.Nil(t, err) 178 assert.Equal(t, []any{"mvn package"}, gjson.Get(jsonData, "vars.k1").Value()) 179 assert.Equal(t, 180 map[string]any{ 181 "key1": "value1", 182 }, 183 gjson.Get(jsonData, "vars.k2.k3").Value()) 184 assert.Equal(t, "test", gjson.Get(jsonData, "vars.k4.0").Value()) 185 assert.Equal(t, 186 map[string]any{ 187 "name": "Deploy", 188 "deployment": "production", 189 "script": []any{ 190 "./deploy.sh target/my-app.jar", 191 }, 192 "trigger": "manual", 193 }, 194 gjson.Get(jsonData, "vars.k5.k6.k7").Value()) 195 }) 196 197 t.Run("variable / circular_1", func(t *testing.T) { 198 yamlData, err := os.ReadFile("./testdata/variable_circular_1.yaml") 199 require.Nil(t, err) 200 201 var out any 202 err = Unmarshal(yamlData, &out) 203 require.NotNil(t, err) 204 assert.Contains(t, err.Error(), "circular variable reference detected: test_var") 205 }) 206 207 t.Run("variable / circular_2", func(t *testing.T) { 208 yamlData, err := os.ReadFile("./testdata/variable_circular_2.yaml") 209 require.Nil(t, err) 210 211 var out any 212 err = Unmarshal(yamlData, &out) 213 require.NotNil(t, err) 214 assert.Contains(t, err.Error(), "circular variable reference detected: test_var") 215 }) 216 217 t.Run("function / success", func(t *testing.T) { 218 yamlData := ` 219 nowUnix: "@@fn nowUnix" 220 nowRFC3339: "@@fn nowRFC3339" 221 nowFormat: '@@fn nowFormat("2006-01-02")' 222 uuid: '@@fn uuid' 223 key2: 224 randStr: '@@fn randStr(5)' 225 array: 226 - '@@ref nowUnix' 227 - '@@ref key2.randStr' 228 ` 229 var out map[string]any 230 err := Unmarshal([]byte(yamlData), &out) 231 require.Nil(t, err) 232 233 jsonData, err := json.MarshalToString(out) 234 require.Nil(t, err) 235 236 assert.NotZero(t, gjson.Get(jsonData, "nowUnix").Int()) 237 assert.NotZero(t, gjson.Get(jsonData, "nowRFC3339").String()) 238 assert.NotZero(t, gjson.Get(jsonData, "nowFormat").String()) 239 assert.NotZero(t, gjson.Get(jsonData, "uuid").String()) 240 assert.NotZero(t, gjson.Get(jsonData, "key2.randStr").String()) 241 assert.Equal(t, 242 gjson.Get(jsonData, "nowUnix").Int(), 243 gjson.Get(jsonData, "key2.array.0").Int()) 244 assert.Equal(t, 245 gjson.Get(jsonData, "key2.randStr").String(), 246 gjson.Get(jsonData, "key2.array.1").String()) 247 }) 248 249 t.Run("function / custom", func(t *testing.T) { 250 fn1 := func() int64 { 251 return 123 252 } 253 fn2 := func() (string, error) { 254 return "abc", nil 255 } 256 fn3 := func(i int, s string) (string, error) { 257 slice := []string{"x", "y", "z"} 258 return s + slice[i], nil 259 } 260 yamlData := ` 261 k1: "@@fn fn1" 262 k2: "@@fn fn2()" 263 k3: '@@fn fn3(1, "123")' 264 k4: '@@fn fn3(2, "123")' 265 ` 266 var out map[string]any 267 err := Unmarshal([]byte(yamlData), &out, 268 WithFuncMap(map[string]any{ 269 "fn1": fn1, "fn2": fn2, "fn3": fn3, 270 })) 271 require.Nil(t, err) 272 assert.Len(t, out, 4) 273 274 assert.Equal(t, 123, out["k1"]) 275 assert.Equal(t, "abc", out["k2"]) 276 assert.Equal(t, "123y", out["k3"]) 277 assert.Equal(t, "123z", out["k4"]) 278 }) 279 280 t.Run("variable to function result", func(t *testing.T) { 281 os.Setenv("ENTITY_ID", "12345") 282 yamlData, err := os.ReadFile("./testdata/var_to_fn.yaml") 283 require.Nil(t, err) 284 285 var out map[string]any 286 err = Unmarshal(yamlData, &out, EnableEnv()) 287 require.Nil(t, err) 288 assert.Len(t, out, 8) 289 assert.Len(t, out["key2"], 2) 290 assert.Len(t, out["var1"], 2) 291 assert.NotZero(t, out["cid"]) 292 assert.Equal(t, out["cid"], out["var2"]) 293 assert.Equal(t, "12345", out["key3"]) 294 assert.Equal(t, "12345", out["env1"]) 295 }) 296 297 t.Run("escape", func(t *testing.T) { 298 yamlData := ` 299 nowUnix: "\\@@fn nowUnix" 300 type: "\\@@type" 301 key2: 302 - '\@@ref ref_1' 303 - key1: "\\@@incl abc.yaml " 304 key2: '\@@var test_var' 305 key3: '\\@@var test_var' 306 key4: '\\@var test_var' 307 key5: "\\@var test_var" 308 ` 309 var out map[string]any 310 err := Unmarshal([]byte(yamlData), &out) 311 require.Nil(t, err) 312 313 assert.Equal(t, "@@fn nowUnix", out["nowUnix"]) 314 assert.Equal(t, "@@type", out["type"]) 315 assert.Equal(t, 316 []any{ 317 "@@ref ref_1", 318 map[string]any{ 319 "key1": "@@incl abc.yaml ", 320 "key2": `@@var test_var`, 321 "key3": `\@@var test_var`, 322 "key4": `\\@var test_var`, 323 "key5": `\@var test_var`, 324 }, 325 }, 326 out["key2"]) 327 }) 328 329 t.Run("json", func(t *testing.T) { 330 jsonData := `{ 331 "aa": "@@incl testdata/include1.yaml", 332 "bb": [ 333 {"incl_json": "@@incl testdata/include2.json"}, 334 "@@incl testdata/include2.json" 335 ] 336 }` 337 var out map[string]any 338 err := Unmarshal([]byte(jsonData), &out, EnableInclude()) 339 require.Nil(t, err) 340 341 assert.Len(t, out["aa"], 3) 342 assert.Len(t, out["bb"], 2) 343 assert.IsType(t, map[string]any{}, out["bb"].([]any)[0]) 344 assert.IsType(t, []any{}, out["bb"].([]any)[1]) 345 assert.Len(t, out["bb"].([]any)[0].(map[string]any)["incl_json"].([]any), 3) 346 assert.Equal(t, out["bb"].([]any)[0].(map[string]any)["incl_json"], out["bb"].([]any)[1]) 347 }) 348 } 349 350 func TestLoad(t *testing.T) { 351 var err error 352 var out any 353 354 err = Load("./testdata/normal.yaml", &out) 355 require.Nil(t, err) 356 357 err = Load("./testdata/include1.yaml", &out, EnableInclude()) 358 require.Nil(t, err) 359 360 err = Load("./testdata/ref.yaml", &out) 361 require.Nil(t, err) 362 363 err = Load("./testdata/variable.yaml", &out) 364 require.Nil(t, err) 365 } 366 367 func Test_unescapeStrValue(t *testing.T) { 368 testCases := []struct { 369 input string 370 want string 371 }{ 372 { 373 input: "\\@@var test_var", 374 want: "@@var test_var", 375 }, 376 { 377 input: `\@@incl file.yaml `, 378 want: "@@incl file.yaml ", 379 }, 380 { 381 input: `\@@@type`, 382 want: `@@@type`, 383 }, 384 { 385 input: `\\\\@@type`, 386 want: `\\\@@type`, 387 }, 388 { 389 input: `\\\@@@`, 390 want: `\\@@@`, 391 }, 392 } 393 for _, tc := range testCases { 394 got := unescapeStrValue(tc.input) 395 assert.Equalf(t, tc.want, got, "input= %q", tc.input) 396 } 397 }