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  }