github.com/tooploox/oya@v0.0.21-0.20230524103240-1cda1861aad6/pkg/template/scope_test.go (about)

     1  package template_test
     2  
     3  import (
     4  	"encoding/gob"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/tooploox/oya/pkg/template"
    10  	tu "github.com/tooploox/oya/testutil"
    11  )
    12  
    13  func identity(scope template.Scope) template.Scope {
    14  	return scope
    15  }
    16  
    17  func mutation(scope template.Scope) template.Scope {
    18  	scope["bar"] = "baz"
    19  	return scope
    20  }
    21  
    22  func merge(scope template.Scope) template.Scope {
    23  	return scope.Merge(template.Scope{"bar": "baz"})
    24  }
    25  
    26  func deepcopy(dst, src interface{}) error {
    27  	r, w, err := os.Pipe()
    28  	if err != nil {
    29  		return err
    30  	}
    31  	enc := gob.NewEncoder(w)
    32  	err = enc.Encode(src)
    33  	if err != nil {
    34  		return err
    35  	}
    36  	dec := gob.NewDecoder(r)
    37  	return dec.Decode(dst)
    38  }
    39  
    40  func TestScope_UpdateScopeAt(t *testing.T) {
    41  	testCases := []struct {
    42  		desc          string
    43  		scope         template.Scope
    44  		path          string
    45  		force         bool
    46  		f             func(template.Scope) template.Scope
    47  		expectedScope template.Scope
    48  	}{
    49  		{
    50  			desc:          "empty path, return original",
    51  			scope:         template.Scope{},
    52  			path:          "",
    53  			f:             identity,
    54  			expectedScope: template.Scope{},
    55  		},
    56  		{
    57  			desc:  "empty path, make an in-place update",
    58  			scope: template.Scope{},
    59  			path:  "",
    60  			f:     mutation,
    61  			expectedScope: template.Scope{
    62  				"bar": "baz",
    63  			},
    64  		},
    65  		{
    66  			desc:  "empty path, return updated copy",
    67  			scope: template.Scope{},
    68  			path:  "",
    69  			f:     merge,
    70  			expectedScope: template.Scope{
    71  				"bar": "baz",
    72  			},
    73  		},
    74  		{
    75  			desc:  "non-existent path, return original",
    76  			scope: template.Scope{},
    77  			path:  "foo",
    78  			f:     identity,
    79  			expectedScope: template.Scope{
    80  				"foo": template.Scope{},
    81  			},
    82  		},
    83  		{
    84  			desc:  "non-existent path, make an in-place update",
    85  			scope: template.Scope{},
    86  			path:  "foo",
    87  			f:     mutation,
    88  			expectedScope: template.Scope{
    89  				"foo": template.Scope{
    90  					"bar": "baz",
    91  				},
    92  			},
    93  		},
    94  		{
    95  			desc:  "non-existent path, return updated copy",
    96  			scope: template.Scope{},
    97  			path:  "foo",
    98  			f:     merge,
    99  			expectedScope: template.Scope{
   100  				"foo": template.Scope{
   101  					"bar": "baz",
   102  				},
   103  			},
   104  		},
   105  		{
   106  			desc:  "non-existent deep path, return original",
   107  			scope: template.Scope{},
   108  			path:  "foo.xxx.yyy",
   109  			f:     identity,
   110  			expectedScope: template.Scope{
   111  				"foo": template.Scope{
   112  					"xxx": template.Scope{
   113  						"yyy": template.Scope{},
   114  					},
   115  				},
   116  			},
   117  		},
   118  		{
   119  			desc:  "non-existent path, make an in-place update",
   120  			scope: template.Scope{},
   121  			path:  "foo.xxx.yyy",
   122  			f:     mutation,
   123  			expectedScope: template.Scope{
   124  				"foo": template.Scope{
   125  					"xxx": template.Scope{
   126  						"yyy": template.Scope{
   127  							"bar": "baz",
   128  						},
   129  					},
   130  				},
   131  			},
   132  		},
   133  		{
   134  			desc:  "non-existent path, return updated copy",
   135  			scope: template.Scope{},
   136  			path:  "foo.xxx.yyy",
   137  			f:     merge,
   138  			expectedScope: template.Scope{
   139  				"foo": template.Scope{
   140  					"xxx": template.Scope{
   141  						"yyy": template.Scope{
   142  							"bar": "baz",
   143  						},
   144  					},
   145  				},
   146  			},
   147  		},
   148  		{
   149  			desc: "existing non-compatible path",
   150  			scope: template.Scope{
   151  				"foo": "aaa",
   152  			},
   153  			path:  "foo.xxx.yyy",
   154  			f:     merge,
   155  			force: true,
   156  			expectedScope: template.Scope{
   157  				"foo": template.Scope{
   158  					"xxx": template.Scope{
   159  						"yyy": template.Scope{
   160  							"bar": "baz",
   161  						},
   162  					},
   163  				},
   164  			},
   165  		},
   166  	}
   167  
   168  	for _, tc := range testCases {
   169  		var scope template.Scope
   170  		err := deepcopy(&scope, tc.scope) // Because f can mutate it.
   171  		tu.AssertNoErr(t, err, "deepcopy failed")
   172  		err = scope.UpdateScopeAt(tc.path, tc.f, tc.force)
   173  		tu.AssertNoErr(t, err, "UpdateScopeAt failed in test case %q", tc.desc)
   174  		tu.AssertObjectsEqualMsg(t, tc.expectedScope, scope, "In test case %q", tc.desc)
   175  	}
   176  }
   177  
   178  func ExampleScope_Flat() {
   179  	scope := template.Scope{
   180  		"foo": map[interface{}]interface{}{
   181  			"bar": "baz",
   182  			"qux": []interface{}{
   183  				"1", "2", 3,
   184  			},
   185  			"abc": map[string]interface{}{
   186  				"123": true,
   187  			},
   188  		},
   189  	}
   190  	flattened := scope.Flat()
   191  	fmt.Println("foo.bar:", flattened["foo.bar"])
   192  	_, ok := flattened["foo"]
   193  	fmt.Println("foo exists?", ok)
   194  	fmt.Println("foo.qux.0:", flattened["foo.qux.0"])
   195  	fmt.Println("foo.qux.1:", flattened["foo.qux.1"])
   196  	fmt.Println("foo.qux.2:", flattened["foo.qux.2"])
   197  	fmt.Println("foo.abc.123:", flattened["foo.abc.123"])
   198  	// Output:
   199  	// foo.bar: baz
   200  	// foo exists? false
   201  	// foo.qux.0: 1
   202  	// foo.qux.1: 2
   203  	// foo.qux.2: 3
   204  	// foo.abc.123: true
   205  }
   206  
   207  func TestMerge(t *testing.T) {
   208  	testCases := []struct {
   209  		desc               string
   210  		lhs, rhs, expected template.Scope
   211  	}{
   212  		{
   213  			desc:     "empty scopes",
   214  			lhs:      template.Scope{},
   215  			rhs:      template.Scope{},
   216  			expected: template.Scope{},
   217  		},
   218  		{
   219  			desc:     "no overlapping keys",
   220  			lhs:      template.Scope{"foo": "bar"},
   221  			rhs:      template.Scope{"baz": "qux"},
   222  			expected: template.Scope{"foo": "bar", "baz": "qux"},
   223  		},
   224  		{
   225  			desc:     "overlapping keys",
   226  			lhs:      template.Scope{"foo": "xxx"},
   227  			rhs:      template.Scope{"baz": "qux", "foo": "bar"},
   228  			expected: template.Scope{"foo": "bar", "baz": "qux"},
   229  		},
   230  		{
   231  			desc:     "deep merge",
   232  			lhs:      template.Scope{"foo": map[interface{}]interface{}{"bar": "xxxxx", "baz": "orange"}},
   233  			rhs:      template.Scope{"foo": map[interface{}]interface{}{"bar": "apple", "qux": "peach"}},
   234  			expected: template.Scope{"foo": map[interface{}]interface{}{"bar": "apple", "baz": "orange", "qux": "peach"}},
   235  		},
   236  		{
   237  			desc: "very deep merge",
   238  			lhs: template.Scope{"root": map[interface{}]interface{}{
   239  				"foo": map[interface{}]interface{}{
   240  					"bar": "xxxxx",
   241  					"baz": "orange",
   242  				}},
   243  			},
   244  			rhs: template.Scope{"root": map[interface{}]interface{}{
   245  				"foo": map[interface{}]interface{}{
   246  					"bar": "apple",
   247  					"qux": "peach",
   248  				}},
   249  			},
   250  			expected: template.Scope{"root": map[interface{}]interface{}{
   251  				"foo": map[interface{}]interface{}{
   252  					"bar": "apple",
   253  					"baz": "orange",
   254  					"qux": "peach",
   255  				}},
   256  			},
   257  		},
   258  	}
   259  
   260  	for _, tc := range testCases {
   261  		actual := tc.lhs.Merge(tc.rhs)
   262  		tu.AssertObjectsEqual(t, tc.expected, actual)
   263  	}
   264  }
   265  
   266  func TestAssocAt(t *testing.T) {
   267  	testCases := []struct {
   268  		desc          string
   269  		scope         template.Scope
   270  		path          string
   271  		value         interface{}
   272  		force         bool
   273  		isErrExpected bool
   274  		expectedScope template.Scope
   275  	}{
   276  		{
   277  			desc:          "empty path",
   278  			scope:         template.Scope{},
   279  			path:          "",
   280  			value:         "foo",
   281  			isErrExpected: true,
   282  			expectedScope: template.Scope{},
   283  		},
   284  		{
   285  			desc:  "non-existent key",
   286  			scope: template.Scope{},
   287  			path:  "foo",
   288  			value: "bar",
   289  			expectedScope: template.Scope{
   290  				"foo": "bar",
   291  			},
   292  		},
   293  		{
   294  			desc:  "non-existent nested path",
   295  			scope: template.Scope{},
   296  			path:  "foo.bar",
   297  			value: "baz",
   298  			expectedScope: template.Scope{
   299  				"foo": template.Scope{
   300  					"bar": "baz",
   301  				},
   302  			},
   303  		},
   304  		{
   305  			desc: "existing key",
   306  			scope: template.Scope{
   307  				"foo": "xxx",
   308  			},
   309  			path:  "foo",
   310  			value: "bar",
   311  			expectedScope: template.Scope{
   312  				"foo": "bar",
   313  			},
   314  		},
   315  		{
   316  			desc: "existing nested path",
   317  			scope: template.Scope{
   318  				"foo": template.Scope{
   319  					"bar": "xxx",
   320  				},
   321  			},
   322  			path:  "foo.bar",
   323  			value: "baz",
   324  			expectedScope: template.Scope{
   325  				"foo": template.Scope{
   326  					"bar": "baz",
   327  				},
   328  			},
   329  		},
   330  		{
   331  			desc: "existing incompatible value",
   332  			scope: template.Scope{
   333  				"foo": "xxx",
   334  			},
   335  			path:          "foo.bar",
   336  			value:         "baz",
   337  			force:         false,
   338  			isErrExpected: true,
   339  		},
   340  		{
   341  			desc: "existing incompatible value, force",
   342  			scope: template.Scope{
   343  				"foo": "xxx",
   344  			},
   345  			path:  "foo.bar",
   346  			value: "baz",
   347  			force: true,
   348  			expectedScope: template.Scope{
   349  				"foo": template.Scope{
   350  					"bar": "baz",
   351  				},
   352  			},
   353  		},
   354  	}
   355  
   356  	for _, tc := range testCases {
   357  		err := tc.scope.AssocAt(tc.path, tc.value, tc.force)
   358  		if tc.isErrExpected {
   359  			tu.AssertErr(t, err, "AssocAt unexpectedly succeeded in test case %q", tc.desc)
   360  
   361  		} else {
   362  			tu.AssertNoErr(t, err, "AssocAt failed in test case %q", tc.desc)
   363  			tu.AssertObjectsEqualMsg(t, tc.expectedScope, tc.scope, "In test case %q", tc.desc)
   364  		}
   365  	}
   366  
   367  }