go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/luci_notify/config/validate_test.go (about)

     1  // Copyright 2017 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 config
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"testing"
    21  
    22  	"go.chromium.org/luci/common/testing/assertions"
    23  	"go.chromium.org/luci/config/validation"
    24  
    25  	"go.chromium.org/luci/luci_notify/common"
    26  	"go.chromium.org/luci/luci_notify/testutil"
    27  
    28  	. "github.com/smartystreets/goconvey/convey"
    29  	. "go.chromium.org/luci/common/testing/assertions"
    30  )
    31  
    32  func TestValidation(t *testing.T) {
    33  	t.Parallel()
    34  
    35  	Convey(`Test Environment for validateProjectConfig`, t, func() {
    36  		testValidation := func(env, config, expectFormat string, expectArgs ...any) {
    37  			Convey(env, func() {
    38  				cfg, err := testutil.ParseProjectConfig(config)
    39  				So(err, ShouldBeNil)
    40  				ctx := &validation.Context{Context: context.Background()}
    41  				validateProjectConfig(ctx, cfg)
    42  				err = ctx.Finalize()
    43  				if expectFormat == "" {
    44  					So(err, ShouldBeNil)
    45  					return
    46  				}
    47  				expect := fmt.Sprintf(expectFormat, expectArgs...)
    48  				So(err, assertions.ShouldErrLike, expect)
    49  			})
    50  		}
    51  		testValidation(`empty`, ``, "")
    52  
    53  		testValidation(`builder missing name`, `
    54  			notifiers {
    55  				name: "good-name"
    56  				builders {
    57  					bucket: "test.bucket"
    58  				}
    59  			}`,
    60  			requiredFieldError, "name")
    61  
    62  		testValidation(`builder missing bucket`, `
    63  			notifiers {
    64  				name: "good-name"
    65  				builders {
    66  					name: "i-am-a-builder"
    67  				}
    68  			}`,
    69  			requiredFieldError, "bucket")
    70  
    71  		testValidation(`builder bad repo`, `
    72  			notifiers {
    73  				name: "good-name"
    74  				builders {
    75  					name: "i-am-a-builder"
    76  					bucket: "test.bucket"
    77  					repository: "bad://x.notgooglesource.com/+/hello"
    78  				}
    79  			}`,
    80  			badRepoURLError, "bad://x.notgooglesource.com/+/hello")
    81  
    82  		testValidation(`bad email address`, `
    83  			notifiers {
    84  				name: "good-name"
    85  				notifications {
    86  					on_new_status: SUCCESS
    87  					on_new_status: FAILURE
    88  					on_new_status: INFRA_FAILURE
    89  					email {
    90  						recipients: "@@@@@"
    91  					}
    92  				}
    93  				builders {
    94  					name: "i-am-a-builder"
    95  					bucket: "test.bucket"
    96  				}
    97  			}`,
    98  			badEmailError, "@@@@@")
    99  
   100  		testValidation(`duplicate builders in notifier`, `
   101  			notifiers {
   102  				name: "good-name"
   103  				builders {
   104  					name: "i-am-a-builder"
   105  					bucket: "test.bucket"
   106  				}
   107  				builders {
   108  					name: "i-am-a-builder"
   109  					bucket: "test.bucket"
   110  				}
   111  			}`,
   112  			duplicateBuilderError, "test.bucket/i-am-a-builder")
   113  
   114  		testValidation(`duplicate builders in project`, `
   115  			notifiers {
   116  				name: "good-name"
   117  				builders {
   118  					name: "i-am-a-builder"
   119  					bucket: "test.bucket"
   120  				}
   121  			}
   122  			notifiers {
   123  				name: "good-name-again"
   124  				builders {
   125  					name: "i-am-a-builder"
   126  					bucket: "test.bucket"
   127  				}
   128  			}`,
   129  			duplicateBuilderError, "test.bucket/i-am-a-builder")
   130  
   131  		testValidation(`different bucketname same builders OK`, `
   132  			notifiers {
   133  				name: "good-name"
   134  				builders {
   135  					name: "i-am-a-builder"
   136  					bucket: "test.bucket"
   137  				}
   138  				builders {
   139  					name: "i-am-a-builder"
   140  					bucket: "test.bucket3"
   141  				}
   142  			}
   143  			notifiers {
   144  				name: "good-name-again"
   145  				builders {
   146  					name: "i-am-a-builder"
   147  					bucket: "test.bucket2"
   148  				}
   149  			}`,
   150  			"", "")
   151  
   152  		testValidation(`bad failed_step_regexp in notification`, `
   153  			notifiers {
   154  				name: "invalid"
   155  				notifications: {
   156  					failed_step_regexp: "["
   157  				}
   158  			}`,
   159  			badRegexError, "failed_step_regexp", "error parsing regexp: missing closing ]: `[`")
   160  
   161  		testValidation(`bad failed_step_regexp_exclude in notification`, `
   162  			notifiers {
   163  				name: "invalid"
   164  				notifications: {
   165  					failed_step_regexp_exclude: "x{3,2}"
   166  				}
   167  			}`,
   168  			badRegexError, "failed_step_regexp_exclude", "error parsing regexp: invalid repeat count: `{3,2}`")
   169  
   170  		testValidation(`bad failed_step_regexp in tree_closer`, `
   171  			notifiers {
   172  				name: "invalid"
   173  				tree_closers: {
   174  					tree_status_host: "example.com"
   175  					failed_step_regexp: ")"
   176  				}
   177  			}`,
   178  			badRegexError, "failed_step_regexp", "error parsing regexp: unexpected ): `)`")
   179  
   180  		testValidation(`bad failed_step_regexp_exclude in tree_closer`, `
   181  			notifiers {
   182  				name: "invalid"
   183  				tree_closers: {
   184  					tree_status_host: "example.com"
   185  					failed_step_regexp_exclude: "[z-a]"
   186  				}
   187  			}`,
   188  			badRegexError, "failed_step_regexp_exclude", "error parsing regexp: invalid character class range: `z-a`")
   189  
   190  		testValidation(`missing tree_status_host in tree_closer`, `
   191  			notifiers {
   192  				name: "invalid"
   193  				tree_closers {
   194  					template: "foo"
   195  				}
   196  			}`,
   197  			requiredFieldError, "tree_status_host")
   198  
   199  		testValidation(`duplicate tree_status_host within notifier`, `
   200  			notifiers {
   201  				name: "invalid"
   202  				tree_closers {
   203  					tree_status_host: "tree1.com"
   204  				}
   205  				tree_closers {
   206  					tree_status_host: "tree1.com"
   207  				}
   208  			}`,
   209  			duplicateHostError, "tree1.com")
   210  
   211  		testValidation(`duplicate tree_status_host, different notifiers`, `
   212  			notifiers {
   213  				name: "fine"
   214  				tree_closers {
   215  					tree_status_host: "tree1.com"
   216  				}
   217  			}
   218  			notifiers {
   219  				name: "also fine"
   220  				tree_closers {
   221  					tree_status_host: "tree1.com"
   222  				}
   223  			}`,
   224  			"", "")
   225  	})
   226  
   227  	Convey(`Test Environment for validateSettings`, t, func() {
   228  		testValidation := func(env, config, expectFormat string, expectArgs ...any) {
   229  			Convey(env, func() {
   230  				cfg, err := testutil.ParseSettings(config)
   231  				So(err, ShouldBeNil)
   232  				ctx := &validation.Context{Context: context.Background()}
   233  				ctx.SetFile("settings.cfg")
   234  				validateSettings(ctx, cfg)
   235  				err = ctx.Finalize()
   236  				if expectFormat == "" {
   237  					So(err, ShouldBeNil)
   238  					return
   239  				}
   240  				expect := fmt.Sprintf(expectFormat, expectArgs...)
   241  				So(err, assertions.ShouldErrLike, expect)
   242  			})
   243  		}
   244  		testValidation(`empty`, ``, requiredFieldError, "milo_host")
   245  		testValidation(`bad hostname`, `milo_host: "9mNRn29%^^%#"`, invalidFieldError, "milo_host")
   246  		testValidation(`good`, `milo_host: "luci-milo.example.com"`, "")
   247  	})
   248  
   249  	Convey("email template filename validation", t, func() {
   250  		c := common.SetAppIDForTest(context.Background(), "luci-notify")
   251  		ctx := &validation.Context{Context: c}
   252  		validFileContent := []byte("a\n\nb")
   253  
   254  		Convey("valid", func() {
   255  			validateEmailTemplateFile(ctx, "projects/x", "luci-notify/email-templates/a.template", validFileContent)
   256  			So(ctx.Finalize(), ShouldBeNil)
   257  		})
   258  
   259  		Convey("invalid char", func() {
   260  			validateEmailTemplateFile(ctx, "projects/x", "luci-notify/email-templates/A.template", validFileContent)
   261  			So(ctx.Finalize(), ShouldErrLike, "does not match")
   262  		})
   263  	})
   264  }