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 }