cuelang.org/go@v0.13.0/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  
    24  	"cuelang.org/go/cue"
    25  	"cuelang.org/go/cue/cuecontext"
    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  
    76  	for _, tc := range testCases {
    77  		t.Run(tc.name, func(t *testing.T) {
    78  			ctx := cuecontext.New()
    79  			codec := New(ctx, nil)
    80  
    81  			v, err := codec.ExtractType(tc.value)
    82  			if err != nil {
    83  				t.Fatal(err)
    84  			}
    85  
    86  			if tc.constraints != "" {
    87  				v1 := ctx.CompileString(tc.constraints, cue.Filename(tc.name))
    88  				if err := v1.Err(); err != nil {
    89  					t.Fatal(err)
    90  				}
    91  				v = v.Unify(v1)
    92  			}
    93  
    94  			err = codec.Validate(v, tc.value)
    95  			checkErr(t, err, tc.err)
    96  
    97  			// Smoke test that it seems to work OK with deprecated *cue.Runtime argument
    98  			r := &cue.Runtime{}
    99  			codec = New(r, nil)
   100  			if _, err := codec.ExtractType(tc.value); err != nil {
   101  				t.Fatal(err)
   102  			}
   103  		})
   104  	}
   105  }
   106  
   107  func TestComplete(t *testing.T) {
   108  	type sump struct {
   109  		A *int `cue:"C-B"`
   110  		B *int `cue:"C-A"`
   111  		C *int `cue:"A+B"`
   112  	}
   113  	one := 1
   114  	two := 2
   115  	fail := "some error"
   116  	_ = fail
   117  	_ = one
   118  	testCases := []struct {
   119  		name        string
   120  		value       interface{}
   121  		result      interface{}
   122  		constraints string
   123  		err         string
   124  	}{{
   125  		name:   "*Sum",
   126  		value:  &Sum{A: 1, B: 4, C: 5},
   127  		result: &Sum{A: 1, B: 4, C: 5},
   128  	}, {
   129  		name:   "*Sum",
   130  		value:  &Sum{A: 1, B: 4},
   131  		result: &Sum{A: 1, B: 4, C: 5},
   132  	}, {
   133  		name:   "*sump",
   134  		value:  &sump{A: &one, B: &one},
   135  		result: &sump{A: &one, B: &one, C: &two},
   136  	}, {
   137  		name:   "*Sum: backwards",
   138  		value:  &Sum{B: 4, C: 8},
   139  		result: &Sum{A: 4, B: 4, C: 8},
   140  	}, {
   141  		name:   "*Sum: sum too low",
   142  		value:  &Sum{A: 1, B: 3},
   143  		result: &Sum{A: 1, B: 3}, // Value should not be updated
   144  		err:    fail,
   145  	}, {
   146  		name:   "*Sum: sum underspecified",
   147  		value:  &Sum{A: 1},
   148  		result: &Sum{A: 1}, // Value should not be updated
   149  		err:    fail,
   150  	}, {
   151  		name:   "Sum: cannot modify",
   152  		value:  Sum{A: 3, B: 4, C: 7},
   153  		result: Sum{A: 3, B: 4, C: 7},
   154  		err:    fail,
   155  	}, {
   156  		name:   "*Sum: cannot update nil value",
   157  		value:  (*Sum)(nil),
   158  		result: (*Sum)(nil),
   159  		err:    fail,
   160  	}, {
   161  		name:   "cannot modify slice",
   162  		value:  []string{"a", "b", "c"},
   163  		result: []string{"a", "b", "c"},
   164  		err:    fail,
   165  	}}
   166  	for _, tc := range testCases {
   167  		t.Run(tc.name, func(t *testing.T) {
   168  			ctx := cuecontext.New()
   169  			codec := New(ctx, nil)
   170  
   171  			v, err := codec.ExtractType(tc.value)
   172  			if err != nil {
   173  				t.Fatal(err)
   174  			}
   175  
   176  			if tc.constraints != "" {
   177  				c := ctx.CompileString(tc.constraints, cue.Filename(tc.name))
   178  				if err := c.Err(); err != nil {
   179  					t.Fatal(err)
   180  				}
   181  				v = v.Unify(c)
   182  			}
   183  
   184  			err = codec.Complete(v, tc.value)
   185  			checkErr(t, err, tc.err)
   186  			if diff := cmp.Diff(tc.value, tc.result); diff != "" {
   187  				t.Error(diff)
   188  			}
   189  		})
   190  	}
   191  }
   192  
   193  func TestEncode(t *testing.T) {
   194  	testCases := []struct {
   195  		in   string
   196  		dst  interface{}
   197  		want interface{}
   198  	}{{
   199  		in:   "4",
   200  		dst:  new(int),
   201  		want: 4,
   202  	}}
   203  	ctx := cuecontext.New()
   204  	c := New(ctx, nil)
   205  
   206  	for _, tc := range testCases {
   207  		t.Run("", func(t *testing.T) {
   208  			in := ctx.CompileString(tc.in, cue.Filename("test"))
   209  			if err := in.Err(); err != nil {
   210  				t.Fatal(err)
   211  			}
   212  
   213  			err := c.Encode(in, tc.dst)
   214  			if err != nil {
   215  				t.Fatal(err)
   216  			}
   217  
   218  			got := reflect.ValueOf(tc.dst).Elem().Interface()
   219  			if diff := cmp.Diff(got, tc.want); diff != "" {
   220  				t.Error(diff)
   221  			}
   222  		})
   223  	}
   224  }
   225  
   226  func TestDecode(t *testing.T) {
   227  	testCases := []struct {
   228  		in   interface{}
   229  		want string
   230  	}{{
   231  		in:   "str",
   232  		want: `"str"`,
   233  	}, {
   234  		in: func() interface{} {
   235  			type T struct {
   236  				B int
   237  			}
   238  			type S struct {
   239  				A string
   240  				T
   241  			}
   242  			return S{}
   243  		}(),
   244  		want: `{
   245  	A: ""
   246  	B: 0
   247  }`,
   248  	}, {
   249  		in: func() interface{} {
   250  			type T struct {
   251  				B int
   252  			}
   253  			type S struct {
   254  				A string
   255  				T `json:"t"`
   256  			}
   257  			return S{}
   258  		}(),
   259  		want: `{
   260  	A: ""
   261  	t: {
   262  		B: 0
   263  	}
   264  }`,
   265  	}}
   266  	c := New(cuecontext.New(), nil)
   267  
   268  	for _, tc := range testCases {
   269  		t.Run("", func(t *testing.T) {
   270  			v, err := c.Decode(tc.in)
   271  			if err != nil {
   272  				t.Fatal(err)
   273  			}
   274  
   275  			got := fmt.Sprint(v)
   276  			if got != tc.want {
   277  				t.Errorf("got %v; want %v", got, tc.want)
   278  			}
   279  		})
   280  	}
   281  }
   282  
   283  // For debugging purposes, do not remove.
   284  func TestX(t *testing.T) {
   285  	t.Skip()
   286  
   287  	fail := "some error"
   288  	// Not a typical constraint, but it is possible.
   289  	var (
   290  		name        = "string list incompatible lengths"
   291  		value       = []string{"a", "b", "c"}
   292  		constraints = `4*[string]`
   293  		wantErr     = fail
   294  	)
   295  
   296  	ctx := cuecontext.New()
   297  	codec := New(ctx, nil)
   298  
   299  	v, err := codec.ExtractType(value)
   300  	if err != nil {
   301  		t.Fatal(err)
   302  	}
   303  
   304  	if constraints != "" {
   305  		c := ctx.CompileString(constraints, cue.Filename(name))
   306  		if err := c.Err(); err != nil {
   307  			t.Fatal(err)
   308  		}
   309  		v = v.Unify(c)
   310  	}
   311  
   312  	err = codec.Validate(v, value)
   313  	checkErr(t, err, wantErr)
   314  }