github.com/solo-io/cue@v0.4.7/encoding/gocode/gocodec/codec_test.go (about)

     1  // Copyright 2019 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gocodec
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/kr/pretty"
    24  
    25  	"github.com/solo-io/cue/cue"
    26  )
    27  
    28  type Sum struct {
    29  	A int `cue:"C-B" json:",omitempty"`
    30  	B int `cue:"C-A" json:",omitempty"`
    31  	C int `cue:"A+B & >=5" json:",omitempty"`
    32  }
    33  
    34  func checkErr(t *testing.T, got error, want string) {
    35  	t.Helper()
    36  	if (got == nil) != (want == "") {
    37  		t.Errorf("error: got %v; want %v", got, want)
    38  	}
    39  }
    40  func TestValidate(t *testing.T) {
    41  	fail := "some error"
    42  	testCases := []struct {
    43  		name        string
    44  		value       interface{}
    45  		constraints string
    46  		err         string
    47  	}{{
    48  		name:        "*Sum: nil disallowed by constraint",
    49  		value:       (*Sum)(nil),
    50  		constraints: "!=null",
    51  		err:         fail,
    52  	}, {
    53  		name:  "Sum",
    54  		value: Sum{A: 1, B: 4, C: 5},
    55  	}, {
    56  		name:  "*Sum",
    57  		value: &Sum{A: 1, B: 4, C: 5},
    58  	}, {
    59  		name:  "*Sum: incorrect sum",
    60  		value: &Sum{A: 1, B: 4, C: 6},
    61  		err:   fail,
    62  	}, {
    63  		name:  "*Sum: field C is too low",
    64  		value: &Sum{A: 1, B: 3, C: 4},
    65  		err:   fail,
    66  	}, {
    67  		name:  "*Sum: nil value",
    68  		value: (*Sum)(nil),
    69  	}, {
    70  		// Not a typical constraint, but it is possible.
    71  		name:        "string list",
    72  		value:       []string{"a", "b", "c"},
    73  		constraints: `[_, "b", ...]`,
    74  	}, {
    75  		// Not a typical constraint, but it is possible.
    76  		name:        "string list incompatible lengths",
    77  		value:       []string{"a", "b", "c"},
    78  		constraints: `4*[string]`,
    79  		err:         fail,
    80  	}}
    81  
    82  	for _, tc := range testCases {
    83  		t.Run(tc.name, func(t *testing.T) {
    84  			r := &cue.Runtime{}
    85  			codec := New(r, nil)
    86  
    87  			v, err := codec.ExtractType(tc.value)
    88  			if err != nil {
    89  				t.Fatal(err)
    90  			}
    91  
    92  			if tc.constraints != "" {
    93  				inst, err := r.Compile(tc.name, tc.constraints)
    94  				if err != nil {
    95  					t.Fatal(err)
    96  				}
    97  				v = v.Unify(inst.Value())
    98  			}
    99  
   100  			err = codec.Validate(v, tc.value)
   101  			checkErr(t, err, tc.err)
   102  		})
   103  	}
   104  }
   105  
   106  func TestComplete(t *testing.T) {
   107  	type updated struct {
   108  		A []*int `cue:"[...int|*1]"` // arbitrary length slice with values 1
   109  		B []int  `cue:"3*[int|*1]"`  // slice of length 3, defaults to [1,1,1]
   110  
   111  		// TODO: better errors if the user forgets to quote.
   112  		M map[string]int `cue:",opt"`
   113  	}
   114  	type sump struct {
   115  		A *int `cue:"C-B"`
   116  		B *int `cue:"C-A"`
   117  		C *int `cue:"A+B"`
   118  	}
   119  	one := 1
   120  	two := 2
   121  	fail := "some error"
   122  	_ = fail
   123  	_ = one
   124  	testCases := []struct {
   125  		name        string
   126  		value       interface{}
   127  		result      interface{}
   128  		constraints string
   129  		err         string
   130  	}{{
   131  		name:   "*Sum",
   132  		value:  &Sum{A: 1, B: 4, C: 5},
   133  		result: &Sum{A: 1, B: 4, C: 5},
   134  	}, {
   135  		name:   "*Sum",
   136  		value:  &Sum{A: 1, B: 4},
   137  		result: &Sum{A: 1, B: 4, C: 5},
   138  	}, {
   139  		name:   "*sump",
   140  		value:  &sump{A: &one, B: &one},
   141  		result: &sump{A: &one, B: &one, C: &two},
   142  	}, {
   143  		name:   "*Sum: backwards",
   144  		value:  &Sum{B: 4, C: 8},
   145  		result: &Sum{A: 4, B: 4, C: 8},
   146  	}, {
   147  		name:   "*Sum: sum too low",
   148  		value:  &Sum{A: 1, B: 3},
   149  		result: &Sum{A: 1, B: 3}, // Value should not be updated
   150  		err:    fail,
   151  	}, {
   152  		name:   "*Sum: sum underspecified",
   153  		value:  &Sum{A: 1},
   154  		result: &Sum{A: 1}, // Value should not be updated
   155  		err:    fail,
   156  	}, {
   157  		name:   "Sum: cannot modify",
   158  		value:  Sum{A: 3, B: 4, C: 7},
   159  		result: Sum{A: 3, B: 4, C: 7},
   160  		err:    fail,
   161  	}, {
   162  		name:   "*Sum: cannot update nil value",
   163  		value:  (*Sum)(nil),
   164  		result: (*Sum)(nil),
   165  		err:    fail,
   166  	}, {
   167  		name:   "cannot modify slice",
   168  		value:  []string{"a", "b", "c"},
   169  		result: []string{"a", "b", "c"},
   170  		err:    fail,
   171  	}, {
   172  		name: "composite values update",
   173  		// allocate a slice with uninitialized values and let Update fill
   174  		// out default values.
   175  		value: &updated{A: make([]*int, 3)},
   176  		result: &updated{
   177  			A: []*int{&one, &one, &one},
   178  			B: []int{1, 1, 1},
   179  			M: map[string]int(nil),
   180  		},
   181  	}, {
   182  		name:   "composite values update with unsatisfied map constraints",
   183  		value:  &updated{},
   184  		result: &updated{},
   185  
   186  		constraints: ` { M: {foo: bar, bar: foo} } `,
   187  		err:         fail, // incomplete values
   188  	}, {
   189  		name:        "composite values update with map constraints",
   190  		value:       &updated{M: map[string]int{"foo": 1}},
   191  		constraints: ` { M: {foo: bar, bar: foo} } `,
   192  		result: &updated{
   193  			// TODO: would be better if this is nil, but will not matter for
   194  			// JSON output: if omitempty is false, an empty list will be
   195  			// printed regardless, and if it is true, it will be omitted
   196  			// regardless.
   197  			A: []*int{},
   198  			B: []int{1, 1, 1},
   199  			M: map[string]int{"bar": 1, "foo": 1},
   200  		},
   201  	}}
   202  	for _, tc := range testCases {
   203  		t.Run(tc.name, func(t *testing.T) {
   204  			r := &cue.Runtime{}
   205  			codec := New(r, nil)
   206  
   207  			v, err := codec.ExtractType(tc.value)
   208  			if err != nil {
   209  				t.Fatal(err)
   210  			}
   211  
   212  			if tc.constraints != "" {
   213  				inst, err := r.Compile(tc.name, tc.constraints)
   214  				if err != nil {
   215  					t.Fatal(err)
   216  				}
   217  				v = v.Unify(inst.Value())
   218  			}
   219  
   220  			err = codec.Complete(v, tc.value)
   221  			checkErr(t, err, tc.err)
   222  			if !reflect.DeepEqual(tc.value, tc.result) {
   223  				t.Error(pretty.Diff(tc.value, tc.result))
   224  			}
   225  		})
   226  	}
   227  }
   228  
   229  func TestEncode(t *testing.T) {
   230  	testCases := []struct {
   231  		in   string
   232  		dst  interface{}
   233  		want interface{}
   234  	}{{
   235  		in:   "4",
   236  		dst:  new(int),
   237  		want: 4,
   238  	}}
   239  	r := &cue.Runtime{}
   240  	c := New(r, nil)
   241  
   242  	for _, tc := range testCases {
   243  		t.Run("", func(t *testing.T) {
   244  			inst, err := r.Compile("test", tc.in)
   245  			if err != nil {
   246  				t.Fatal(err)
   247  			}
   248  
   249  			err = c.Encode(inst.Value(), tc.dst)
   250  			if err != nil {
   251  				t.Fatal(err)
   252  			}
   253  
   254  			got := reflect.ValueOf(tc.dst).Elem().Interface()
   255  			if !cmp.Equal(got, tc.want) {
   256  				t.Error(cmp.Diff(got, tc.want))
   257  			}
   258  		})
   259  	}
   260  }
   261  
   262  func TestDecode(t *testing.T) {
   263  	testCases := []struct {
   264  		in   interface{}
   265  		want string
   266  	}{{
   267  		in:   "str",
   268  		want: `"str"`,
   269  	}, {
   270  		in: func() interface{} {
   271  			type T struct {
   272  				B int
   273  			}
   274  			type S struct {
   275  				A string
   276  				T
   277  			}
   278  			return S{}
   279  		}(),
   280  		want: `{
   281  	A: ""
   282  	B: 0
   283  }`,
   284  	}, {
   285  		in: func() interface{} {
   286  			type T struct {
   287  				B int
   288  			}
   289  			type S struct {
   290  				A string
   291  				T `json:"t"`
   292  			}
   293  			return S{}
   294  		}(),
   295  		want: `{
   296  	A: ""
   297  	t: {
   298  		B: 0
   299  	}
   300  }`,
   301  	}}
   302  	c := New(&cue.Runtime{}, nil)
   303  
   304  	for _, tc := range testCases {
   305  		t.Run("", func(t *testing.T) {
   306  			v, err := c.Decode(tc.in)
   307  			if err != nil {
   308  				t.Fatal(err)
   309  			}
   310  
   311  			got := fmt.Sprint(v)
   312  			if got != tc.want {
   313  				t.Errorf("got %v; want %v", got, tc.want)
   314  			}
   315  		})
   316  	}
   317  }
   318  
   319  // For debugging purposes, do not remove.
   320  func TestX(t *testing.T) {
   321  	t.Skip()
   322  
   323  	fail := "some error"
   324  	// Not a typical constraint, but it is possible.
   325  	var (
   326  		name        = "string list incompatible lengths"
   327  		value       = []string{"a", "b", "c"}
   328  		constraints = `4*[string]`
   329  		wantErr     = fail
   330  	)
   331  
   332  	r := &cue.Runtime{}
   333  	codec := New(r, nil)
   334  
   335  	v, err := codec.ExtractType(value)
   336  	if err != nil {
   337  		t.Fatal(err)
   338  	}
   339  
   340  	if constraints != "" {
   341  		inst, err := r.Compile(name, constraints)
   342  		if err != nil {
   343  			t.Fatal(err)
   344  		}
   345  		w := inst.Value()
   346  		v = v.Unify(w)
   347  	}
   348  
   349  	err = codec.Validate(v, value)
   350  	checkErr(t, err, wantErr)
   351  }