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  }