| // Copyright 2017 The LUCI Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package config |
| |
| import ( |
| "context" |
| "fmt" |
| "testing" |
| |
| "go.chromium.org/luci/common/testing/ftt" |
| "go.chromium.org/luci/common/testing/truth/assert" |
| "go.chromium.org/luci/common/testing/truth/should" |
| "go.chromium.org/luci/config/validation" |
| |
| "go.chromium.org/luci/luci_notify/common" |
| "go.chromium.org/luci/luci_notify/testutil" |
| ) |
| |
| func TestValidation(t *testing.T) { |
| t.Parallel() |
| |
| ftt.Run(`Test Environment for validateProjectConfig`, t, func(t *ftt.Test) { |
| testValidation := func(env, config, expectFormat string, expectArgs ...any) { |
| t.Run(env, func(t *ftt.Test) { |
| cfg, err := testutil.ParseProjectConfig(config) |
| assert.Loosely(t, err, should.BeNil) |
| ctx := &validation.Context{Context: context.Background()} |
| validateProjectConfig(ctx, cfg) |
| err = ctx.Finalize() |
| if expectFormat == "" { |
| assert.Loosely(t, err, should.BeNil) |
| return |
| } |
| expect := fmt.Sprintf(expectFormat, expectArgs...) |
| assert.Loosely(t, err, should.ErrLike(expect)) |
| }) |
| } |
| testValidation(`empty`, ``, "") |
| |
| testValidation(`builder missing name`, ` |
| notifiers { |
| name: "good-name" |
| builders { |
| bucket: "test.bucket" |
| } |
| }`, |
| requiredFieldError, "name") |
| |
| testValidation(`builder missing bucket`, ` |
| notifiers { |
| name: "good-name" |
| builders { |
| name: "i-am-a-builder" |
| } |
| }`, |
| requiredFieldError, "bucket") |
| |
| testValidation(`builder bad repo`, ` |
| notifiers { |
| name: "good-name" |
| builders { |
| name: "i-am-a-builder" |
| bucket: "test.bucket" |
| repository: "bad://x.notgooglesource.com/+/hello" |
| } |
| }`, |
| badRepoURLError, "bad://x.notgooglesource.com/+/hello") |
| |
| testValidation(`bad email address`, ` |
| notifiers { |
| name: "good-name" |
| notifications { |
| on_new_status: SUCCESS |
| on_new_status: FAILURE |
| on_new_status: INFRA_FAILURE |
| email { |
| recipients: "@@@@@" |
| } |
| } |
| builders { |
| name: "i-am-a-builder" |
| bucket: "test.bucket" |
| } |
| }`, |
| badEmailError, "@@@@@") |
| |
| testValidation(`duplicate builders in notifier`, ` |
| notifiers { |
| name: "good-name" |
| builders { |
| name: "i-am-a-builder" |
| bucket: "test.bucket" |
| } |
| builders { |
| name: "i-am-a-builder" |
| bucket: "test.bucket" |
| } |
| }`, |
| duplicateBuilderError, "test.bucket/i-am-a-builder") |
| |
| testValidation(`duplicate builders in project`, ` |
| notifiers { |
| name: "good-name" |
| builders { |
| name: "i-am-a-builder" |
| bucket: "test.bucket" |
| } |
| } |
| notifiers { |
| name: "good-name-again" |
| builders { |
| name: "i-am-a-builder" |
| bucket: "test.bucket" |
| } |
| }`, |
| duplicateBuilderError, "test.bucket/i-am-a-builder") |
| |
| testValidation(`different bucketname same builders OK`, ` |
| notifiers { |
| name: "good-name" |
| builders { |
| name: "i-am-a-builder" |
| bucket: "test.bucket" |
| } |
| builders { |
| name: "i-am-a-builder" |
| bucket: "test.bucket3" |
| } |
| } |
| notifiers { |
| name: "good-name-again" |
| builders { |
| name: "i-am-a-builder" |
| bucket: "test.bucket2" |
| } |
| }`, |
| "", "") |
| |
| testValidation(`bad failed_step_regexp in notification`, ` |
| notifiers { |
| name: "invalid" |
| notifications: { |
| failed_step_regexp: "[" |
| } |
| }`, |
| badRegexError, "failed_step_regexp", "error parsing regexp: missing closing ]: `[`") |
| |
| testValidation(`bad failed_step_regexp_exclude in notification`, ` |
| notifiers { |
| name: "invalid" |
| notifications: { |
| failed_step_regexp_exclude: "x{3,2}" |
| } |
| }`, |
| badRegexError, "failed_step_regexp_exclude", "error parsing regexp: invalid repeat count: `{3,2}`") |
| |
| testValidation(`bad failed_step_regexp in tree_closer`, ` |
| notifiers { |
| name: "invalid" |
| tree_closers: { |
| tree_name: "example" |
| failed_step_regexp: ")" |
| } |
| }`, |
| badRegexError, "failed_step_regexp", "error parsing regexp: unexpected ): `)`") |
| |
| testValidation(`bad failed_step_regexp_exclude in tree_closer`, ` |
| notifiers { |
| name: "invalid" |
| tree_closers: { |
| tree_name: "example" |
| failed_step_regexp_exclude: "[z-a]" |
| } |
| }`, |
| badRegexError, "failed_step_regexp_exclude", "error parsing regexp: invalid character class range: `z-a`") |
| |
| testValidation(`missing tree_name in tree_closer`, ` |
| notifiers { |
| name: "invalid" |
| tree_closers { |
| template: "foo" |
| } |
| }`, |
| requiredFieldError, "tree_name") |
| |
| testValidation(`bad tree_name in tree_closer`, ` |
| notifiers { |
| name: "invalid" |
| tree_closers { |
| tree_name: "bad.tree.name" |
| template: "foo" |
| } |
| }`, |
| invalidFieldError, "tree_name") |
| |
| testValidation(`bad tree_status_host in tree_closer`, ` |
| notifiers { |
| name: "invalid" |
| tree_closers { |
| tree_status_host: "bad.tree.status.host" |
| template: "foo" |
| } |
| }`, |
| invalidFieldError, "tree_status_host") |
| |
| testValidation(`both tree_name and tree_status_host in tree_closer`, ` |
| notifiers { |
| name: "invalid" |
| tree_closers { |
| tree_status_host: "myapp-status.appspot.com" |
| template: "foo" |
| tree_name: "myapp" |
| } |
| }`, |
| onlyTreeNameOrHostError) |
| |
| testValidation(`duplicate tree_name within notifier`, ` |
| notifiers { |
| name: "invalid" |
| tree_closers { |
| tree_name: "tree1" |
| } |
| tree_closers { |
| tree_name: "tree1" |
| } |
| }`, |
| duplicateTreeNameError, "tree1") |
| |
| testValidation(`duplicate tree_name within notifier (mixed tree_name and tree_status_host)`, ` |
| notifiers { |
| name: "invalid" |
| tree_closers { |
| tree_name: "tree1" |
| } |
| tree_closers { |
| tree_status_host: "tree1-status.appspot.com" |
| } |
| }`, |
| duplicateTreeNameError, "tree1") |
| |
| testValidation(`duplicate tree_name, different notifiers`, ` |
| notifiers { |
| name: "fine" |
| tree_closers { |
| tree_name: "tree1" |
| } |
| } |
| notifiers { |
| name: "also fine" |
| tree_closers { |
| tree_name: "tree1" |
| } |
| }`, |
| "", "") |
| }) |
| |
| ftt.Run(`Test Environment for validateSettings`, t, func(t *ftt.Test) { |
| testValidation := func(env, config, expectFormat string, expectArgs ...any) { |
| t.Run(env, func(t *ftt.Test) { |
| cfg, err := testutil.ParseSettings(config) |
| assert.Loosely(t, err, should.BeNil) |
| ctx := &validation.Context{Context: context.Background()} |
| ctx.SetFile("settings.cfg") |
| validateSettings(ctx, cfg) |
| err = ctx.Finalize() |
| if expectFormat == "" { |
| assert.Loosely(t, err, should.BeNil) |
| return |
| } |
| expect := fmt.Sprintf(expectFormat, expectArgs...) |
| assert.Loosely(t, err, should.ErrLike(expect)) |
| }) |
| } |
| testValidation(`bad hostname`, `luci_tree_status_host: "9mNRn29%^^%#"`, invalidFieldError, "luci_tree_status_host") |
| testValidation(`good`, `luci_tree_status_host: "luci-tree-status.example.com"`, "") |
| }) |
| |
| ftt.Run("email template filename validation", t, func(t *ftt.Test) { |
| c := common.SetAppIDForTest(context.Background(), "luci-notify") |
| ctx := &validation.Context{Context: c} |
| validFileContent := []byte("a\n\nb") |
| |
| t.Run("valid", func(t *ftt.Test) { |
| validateEmailTemplateFile(ctx, "projects/x", "luci-notify/email-templates/a.template", validFileContent) |
| assert.Loosely(t, ctx.Finalize(), should.BeNil) |
| }) |
| |
| t.Run("invalid char", func(t *ftt.Test) { |
| validateEmailTemplateFile(ctx, "projects/x", "luci-notify/email-templates/A.template", validFileContent) |
| assert.Loosely(t, ctx.Finalize(), should.ErrLike("does not match")) |
| }) |
| }) |
| } |