cuelang.org/go@v0.10.1/cuego/cuego_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 cuego 16 17 import ( 18 "reflect" 19 "testing" 20 ) 21 22 type Sum struct { 23 A int `cue:"C-B" json:",omitempty"` 24 B int `cue:"C-A" json:",omitempty"` 25 C int `cue:"A+B & >=5" json:",omitempty"` 26 } 27 28 func checkErr(t *testing.T, got error, want string) { 29 t.Helper() 30 if (got == nil) != (want == "") { 31 t.Errorf("error: got %v; want %v", got, want) 32 } 33 } 34 func TestValidate(t *testing.T) { 35 fail := "some error" 36 testCases := []struct { 37 name string 38 value interface{} 39 constraints string 40 err string 41 }{{ 42 name: "Sum", 43 value: Sum{A: 1, B: 4, C: 5}, 44 }, { 45 name: "*Sum", 46 value: &Sum{A: 1, B: 4, C: 5}, 47 }, { 48 name: "*Sum: incorrect sum", 49 value: &Sum{A: 1, B: 4, C: 6}, 50 err: fail, 51 }, { 52 name: "*Sum: field C is too low", 53 value: &Sum{A: 1, B: 3, C: 4}, 54 err: fail, 55 }, { 56 name: "*Sum: nil value", 57 value: (*Sum)(nil), 58 // }, { 59 // // TODO: figure out whether this constraint should constrain it 60 // // to a struct or not. 61 // name: "*Sum: nil disallowed by constraint", 62 // value: (*Sum)(nil), 63 // constraints: "!=null", 64 // err: fail, 65 }, { 66 // Not a typical constraint, but it is possible. 67 name: "string list", 68 value: []string{"a", "b", "c"}, 69 constraints: `[_, "b", ...]`, 70 }, { 71 // Not a typical constraint, but it is possible. 72 name: "string list incompatible lengths", 73 value: []string{"a", "b", "c"}, 74 constraints: `4*[string]`, 75 err: fail, 76 }} 77 78 for _, tc := range testCases { 79 t.Run(tc.name, func(t *testing.T) { 80 c := &Context{} 81 if tc.constraints != "" { 82 err := c.Constrain(tc.value, tc.constraints) 83 if err != nil { 84 t.Fatal(err) 85 } 86 } 87 err := c.Validate(tc.value) 88 checkErr(t, err, tc.err) 89 }) 90 } 91 } 92 93 func TestUpdate(t *testing.T) { 94 type updated struct { 95 A []*int `cue:"[...int|*1]"` // arbitrary length slice with values 1 96 B []int `cue:"3*[int|*1]"` // slice of length 3, defaults to [1,1,1] 97 98 // TODO: better errors if the user forgets to quote. 99 M map[string]int `cue:",opt"` 100 } 101 type sump struct { 102 A *int `cue:"C-B"` 103 B *int `cue:"C-A"` 104 C *int `cue:"A+B"` 105 } 106 one := 1 107 two := 2 108 fail := "some error" 109 _ = fail 110 _ = one 111 testCases := []struct { 112 name string 113 value interface{} 114 result interface{} 115 constraints string 116 err string 117 }{{ 118 name: "*Sum", 119 value: &Sum{A: 1, B: 4, C: 5}, 120 result: &Sum{A: 1, B: 4, C: 5}, 121 }, { 122 name: "*Sum", 123 value: &Sum{A: 1, B: 4}, 124 result: &Sum{A: 1, B: 4, C: 5}, 125 }, { 126 name: "*sump", 127 value: &sump{A: &one, B: &one}, 128 result: &sump{A: &one, B: &one, C: &two}, 129 }, { 130 name: "*Sum: backwards", 131 value: &Sum{B: 4, C: 8}, 132 result: &Sum{A: 4, B: 4, C: 8}, 133 }, { 134 name: "*Sum: sum too low", 135 value: &Sum{A: 1, B: 3}, 136 result: &Sum{A: 1, B: 3}, // Value should not be updated 137 err: fail, 138 }, { 139 name: "*Sum: sum underspecified", 140 value: &Sum{A: 1}, 141 result: &Sum{A: 1}, // Value should not be updated 142 err: fail, 143 }, { 144 name: "Sum: cannot modify", 145 value: Sum{A: 3, B: 4, C: 7}, 146 result: Sum{A: 3, B: 4, C: 7}, 147 err: fail, 148 }, { 149 name: "*Sum: cannot update nil value", 150 value: (*Sum)(nil), 151 result: (*Sum)(nil), 152 err: fail, 153 }, { 154 name: "cannot modify slice", 155 value: []string{"a", "b", "c"}, 156 result: []string{"a", "b", "c"}, 157 err: fail, 158 }, { 159 name: "composite values update", 160 // allocate a slice with uninitialized values and let Update fill 161 // out default values. 162 value: &updated{A: make([]*int, 3)}, 163 result: &updated{ 164 A: []*int{&one, &one, &one}, 165 B: []int{1, 1, 1}, 166 M: map[string]int(nil), 167 }, 168 }, { 169 name: "composite values update with unsatisfied map constraints", 170 value: &updated{}, 171 result: &updated{}, 172 173 constraints: ` { M: {foo: bar, bar: foo} } `, 174 err: fail, // incomplete values 175 }, { 176 name: "composite values update with map constraints", 177 value: &updated{M: map[string]int{"foo": 1}}, 178 constraints: ` { M: {foo: bar, bar: foo} } `, 179 result: &updated{ 180 // TODO: would be better if this is nil, but will not matter for 181 // JSON output: if omitempty is false, an empty list will be 182 // printed regardless, and if it is true, it will be omitted 183 // regardless. 184 A: []*int{}, 185 B: []int{1, 1, 1}, 186 M: map[string]int{"bar": 1, "foo": 1}, 187 }, 188 }} 189 for _, tc := range testCases { 190 t.Run(tc.name, func(t *testing.T) { 191 c := &Context{} 192 if tc.constraints != "" { 193 err := c.Constrain(tc.value, tc.constraints) 194 if err != nil { 195 t.Fatal(err) 196 } 197 } 198 err := c.Complete(tc.value) 199 checkErr(t, err, tc.err) 200 if !reflect.DeepEqual(tc.value, tc.result) { 201 t.Errorf("value:\n got: %#v;\nwant: %#v", tc.value, tc.result) 202 } 203 }) 204 } 205 }