go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/text/templateproto/template_test.go (about) 1 // Copyright 2016 The LUCI 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 templateproto 16 17 import ( 18 "testing" 19 20 "github.com/golang/protobuf/proto" 21 . "github.com/smartystreets/goconvey/convey" 22 . "go.chromium.org/luci/common/testing/assertions" 23 ) 24 25 func parse(template string) *File_Template { 26 ret := &File_Template{} 27 So(proto.UnmarshalText(template, ret), ShouldBeNil) 28 return ret 29 } 30 31 func TestTemplateNormalize(t *testing.T) { 32 t.Parallel() 33 34 badCases := []struct { 35 err, data string 36 }{ 37 {"body is empty", ""}, 38 39 {"param \"\": invalid name", `body: "foofball" param: <key: "" value: <>>`}, 40 {"param \"$#foo\": malformed name", `body: "foofball" param: <key: "$#foo" value:<>>`}, 41 {"param \"${}\": malformed name", `body: "foofball" param: <key: "${}" value:<>>`}, 42 {"param \"blah${cool}nerd\": malformed name", `body: "foofball" param: <key: "blah${cool}nerd" value:<>>`}, 43 {"param \"${ok}\": not present in body", `body: "foofball" param: <key: "${ok}" value:<>>`}, 44 {"param \"${foof}\": schema: is nil", `body: "${foof}ball" param: <key: "${foof}" value:<>>`}, 45 {"param \"${foof}\": schema: has no type", `body: "${foof}ball" param: <key: "${foof}" value: <schema: <>>>`}, 46 47 {`param "${foof}": default value: type is "str", expected "int"`, `body: "${foof}ball" param: <key: "${foof}" value: <schema: <int:<>> default: <str: "">>>`}, 48 {"param \"${foof}\": default value: not nullable", `body: "${foof}ball" param: <key: "${foof}" value: <schema: <int:<>> default: <null: <>>>>`}, 49 {"param \"${foof}\": default value: invalid character 'q'", `body: "${foof}ball" param: <key: "${foof}" value: <schema: <object:<>> default: <object: "querp">>>`}, 50 51 {`param "${f}": schema: set requires entries`, `body: "${f}" param: <key: "${f}" value: <schema: <enum:<>>>>`}, 52 {`param "${f}": schema: blank token`, `body: "${f}" param: <key: "${f}" value: <schema: <enum:<entry: <>>>>>`}, 53 {`param "${f}": schema: duplicate token "tok"`, `body: "{\"k\": ${f}}" param: <key: "${f}" value: <schema: <enum:<entry: <token: "tok"> entry: <token: "tok">>>>>`}, 54 55 {"parsing rendered body: invalid character 'w'", `body: "wumpus"`}, 56 } 57 58 Convey("File_Template.Normalize", t, func() { 59 Convey("bad", func() { 60 61 for _, tc := range badCases { 62 Convey(tc.err, func() { 63 t := parse(tc.data) 64 So(t.Normalize(), ShouldErrLike, tc.err) 65 }) 66 } 67 68 Convey("length", func() { 69 Convey("string", func() { 70 t := parse(` 71 body: "{\"key\": ${foof}}" 72 param: < 73 key: "${foof}" 74 value: < 75 schema: <str:<max_length: 1>> 76 > 77 > 78 `) 79 So(t.Normalize(), ShouldBeNil) 80 _, err := t.RenderL(LiteralMap{"${foof}": "hi there"}) 81 So(err, ShouldErrLike, "param \"${foof}\": value is too large") 82 }) 83 84 Convey("bytes", func() { 85 t := parse(` 86 body: "{\"key\": ${foof}}" 87 param: < 88 key: "${foof}" 89 value: < 90 schema: <bytes:<max_length: 1>> 91 > 92 > 93 `) 94 So(t.Normalize(), ShouldBeNil) 95 _, err := t.RenderL(LiteralMap{"${foof}": []byte("hi there")}) 96 So(err, ShouldErrLike, "param \"${foof}\": value is too large") 97 }) 98 99 Convey("object", func() { 100 t := parse(` 101 body: "{\"key\": ${foof}}" 102 param: < 103 key: "${foof}" 104 value: < 105 schema: <object:<max_length: 4>> 106 > 107 > 108 `) 109 So(t.Normalize(), ShouldBeNil) 110 _, err := t.RenderL(LiteralMap{"${foof}": map[string]any{"hi": 1}}) 111 So(err, ShouldErrLike, "param \"${foof}\": value is too large") 112 }) 113 114 Convey("enum", func() { 115 t := parse(` 116 body: "{\"key\": ${foof}}" 117 param: < 118 key: "${foof}" 119 value: < 120 schema: <enum:< 121 entry: <token: "foo"> 122 >> 123 > 124 > 125 `) 126 So(t.Normalize(), ShouldBeNil) 127 _, err := t.RenderL(LiteralMap{"${foof}": "bar"}) 128 So(err, ShouldErrLike, "param \"${foof}\": value does not match enum: \"bar\"") 129 }) 130 }) 131 132 Convey("parse", func() { 133 Convey("not obj value", func() { 134 m, err := (LiteralMap{"$key": &Value_Object{"querp"}}).Convert() 135 So(err, ShouldBeNil) 136 spec := &Specifier{TemplateName: "thing", Params: m} 137 So(spec.Normalize(), ShouldErrLike, "param \"$key\": invalid character 'q'") 138 }) 139 140 Convey("not ary value", func() { 141 m, err := (LiteralMap{"$key": &Value_Array{"querp"}}).Convert() 142 So(err, ShouldBeNil) 143 spec := &Specifier{TemplateName: "thing", Params: m} 144 So(spec.Normalize(), ShouldErrLike, "param \"$key\": invalid character 'q'") 145 }) 146 }) 147 148 Convey("required param", func() { 149 t := parse(` 150 body: "{\"key\": ${value}}" 151 param: < 152 key: "${value}" 153 value: < 154 schema: <int: <>> 155 > 156 > 157 `) 158 So(t.Normalize(), ShouldBeNil) 159 _, err := t.Render(nil) 160 So(err, ShouldErrLike, "param \"${value}\": missing") 161 }) 162 163 Convey("extra param", func() { 164 t := parse(` 165 body: "{\"key\": ${value}}" 166 param: < 167 key: "${value}" 168 value: < 169 schema: <int: <>> 170 > 171 > 172 `) 173 So(t.Normalize(), ShouldBeNil) 174 _, err := t.RenderL(LiteralMap{"foo": nil, "${value}": 1}) 175 So(err, ShouldErrLike, "unknown parameters: [\"foo\"]") 176 }) 177 178 Convey("bad type", func() { 179 t := parse(` 180 body: "{\"key\": ${value}}" 181 param: < 182 key: "${value}" 183 value: < 184 schema: <int: <>> 185 > 186 > 187 `) 188 So(t.Normalize(), ShouldBeNil) 189 _, err := t.RenderL(LiteralMap{"${value}": nil}) 190 So(err, ShouldErrLike, "param \"${value}\": not nullable") 191 }) 192 193 Convey("prevents JSONi", func() { 194 Convey("object", func() { 195 m, err := (LiteralMap{ 196 "${value}": &Value_Object{`{}, "otherKey": {}`}}).Convert() 197 So(err, ShouldBeNil) 198 spec := &Specifier{TemplateName: "thing", Params: m} 199 So(spec.Normalize(), ShouldErrLike, "param \"${value}\": got extra junk") 200 201 spec.Params["${value}"].Value.(*Value_Object).Object = `{"extra": "space"} ` 202 So(spec.Normalize(), ShouldBeNil) 203 So(spec.Params["${value}"].GetObject(), ShouldEqual, `{"extra":"space"}`) 204 }) 205 206 Convey("array", func() { 207 va := &Value_Array{`[], "otherKey": []`} 208 So(va.Normalize(), ShouldErrLike, "got extra junk") 209 }) 210 }) 211 }) 212 213 Convey("good", func() { 214 Convey("default", func() { 215 t := parse(` 216 body: "{\"key\": ${value}}" 217 param: < 218 key: "${value}" 219 value: < 220 schema: <int: <>> 221 default: <int: 20> 222 > 223 > 224 `) 225 So(t.Normalize(), ShouldBeNil) 226 doc, err := t.Render(nil) 227 So(err, ShouldBeNil) 228 So(doc, ShouldEqual, `{"key": 20}`) 229 }) 230 231 Convey("big int", func() { 232 t := parse(` 233 body: "{\"key\": ${value}}" 234 param: < 235 key: "${value}" 236 value: < 237 schema: <int: <>> 238 default: <int: 9223372036854775807> 239 > 240 > 241 `) 242 So(t.Normalize(), ShouldBeNil) 243 doc, err := t.Render(nil) 244 So(err, ShouldBeNil) 245 So(doc, ShouldEqual, `{"key": "9223372036854775807"}`) 246 }) 247 248 Convey("big uint", func() { 249 t := parse(` 250 body: "{\"key\": ${value}}" 251 param: < 252 key: "${value}" 253 value: < 254 schema: <uint: <>> 255 default: <uint: 18446744073709551615> 256 > 257 > 258 `) 259 So(t.Normalize(), ShouldBeNil) 260 doc, err := t.Render(nil) 261 So(err, ShouldBeNil) 262 So(doc, ShouldEqual, `{"key": "18446744073709551615"}`) 263 }) 264 265 Convey("param", func() { 266 t := parse(` 267 body: "{\"key\": ${value}}" 268 param: < 269 key: "${value}" 270 value: < 271 schema: <uint: <>> 272 > 273 > 274 `) 275 So(t.Normalize(), ShouldBeNil) 276 doc, err := t.RenderL(LiteralMap{"${value}": uint(19)}) 277 So(err, ShouldBeNil) 278 So(doc, ShouldEqual, `{"key": 19}`) 279 }) 280 281 Convey("float", func() { 282 t := parse(` 283 body: "{\"key\": ${value}}" 284 param: < 285 key: "${value}" 286 value: < 287 schema: <float: <>> 288 > 289 > 290 `) 291 So(t.Normalize(), ShouldBeNil) 292 doc, err := t.RenderL(LiteralMap{"${value}": 19.1}) 293 So(err, ShouldBeNil) 294 So(doc, ShouldEqual, `{"key": 19.1}`) 295 }) 296 297 Convey("boolean", func() { 298 t := parse(` 299 body: "{\"key\": ${value}}" 300 param: < 301 key: "${value}" 302 value: < 303 schema: <bool: <>> 304 > 305 > 306 `) 307 So(t.Normalize(), ShouldBeNil) 308 doc, err := t.RenderL(LiteralMap{"${value}": true}) 309 So(err, ShouldBeNil) 310 So(doc, ShouldEqual, `{"key": true}`) 311 }) 312 313 Convey("array", func() { 314 t := parse(` 315 body: "{\"key\": ${value}}" 316 param: < 317 key: "${value}" 318 value: < 319 schema: <array: <>> 320 > 321 > 322 `) 323 So(t.Normalize(), ShouldBeNil) 324 doc, err := t.RenderL(LiteralMap{"${value}": []any{"hi", 20}}) 325 So(err, ShouldBeNil) 326 So(doc, ShouldEqual, `{"key": ["hi",20]}`) 327 }) 328 329 Convey("null", func() { 330 t := parse(` 331 body: "{\"key\": ${value}}" 332 param: < 333 key: "${value}" 334 value: < 335 schema: <int: <>> 336 nullable: true 337 > 338 > 339 `) 340 So(t.Normalize(), ShouldBeNil) 341 doc, err := t.RenderL(LiteralMap{"${value}": nil}) 342 So(err, ShouldBeNil) 343 So(doc, ShouldEqual, `{"key": null}`) 344 }) 345 }) 346 }) 347 }