blob: 19b94d7f2d28ac64988a4be00fcb542db6963efd [file] [log] [blame]
// 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"))
})
})
}