Files
nats-server/server/config_check_test.go
Ivan Kozlovic 63138509f7 Tune some code/test for Windows
Running test suite on a Windows VM, I notice several failures.
Updated the compute of the RTT to be at least 1ns. I think that
this is just an issue with the VM I am running, but that change
will have no impact for normal situations (since setting the rtt
to the very minimum duration (1ns) instead of 0) and will prevent
some tests from failing.

Because of those same timer granularity issues, I had to add some
delays between some actions in order for time.Sub()/Since() to
actually report something more than 0.

Signed-off-by: Ivan Kozlovic <ivan@synadia.com>
2019-11-21 14:32:46 -07:00

1385 lines
32 KiB
Go

// Copyright 2018 The NATS 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 server
import (
"errors"
"fmt"
"os"
"strings"
"testing"
)
func TestConfigCheck(t *testing.T) {
tests := []struct {
// name is the name of the test.
name string
// config is content of the configuration file.
config string
// warningErr is an error that does not prevent server from starting.
warningErr error
// errorLine is the location of the error.
errorLine int
// errorPos is the position of the error.
errorPos int
// warning errors also include a reason optionally.
reason string
// newDefaultErr is a configuration error that includes source of error.
err error
}{
{
name: "when unknown field is used at top level",
config: `
monitor = "127.0.0.1:4442"
`,
err: errors.New(`unknown field "monitor"`),
errorLine: 2,
errorPos: 17,
},
{
name: "when default permissions are used at top level",
config: `
"default_permissions" {
publish = ["_SANDBOX.>"]
subscribe = ["_SANDBOX.>"]
}
`,
err: errors.New(`unknown field "default_permissions"`),
errorLine: 2,
errorPos: 18,
},
{
name: "when authorization config is empty",
config: `
authorization = {
}
`,
err: nil,
},
{
name: "when authorization config has unknown fields",
config: `
authorization = {
foo = "bar"
}
`,
err: errors.New(`unknown field "foo"`),
errorLine: 3,
errorPos: 5,
},
{
name: "when authorization config has unknown fields",
config: `
port = 4222
authorization = {
user = "hello"
foo = "bar"
password = "world"
}
`,
err: errors.New(`unknown field "foo"`),
errorLine: 6,
errorPos: 5,
},
{
name: "when user authorization config has unknown fields",
config: `
authorization = {
users = [
{
user = "foo"
pass = "bar"
token = "quux"
}
]
}
`,
err: errors.New(`unknown field "token"`),
errorLine: 7,
errorPos: 9,
},
{
name: "when user authorization permissions config has unknown fields",
config: `
authorization {
permissions {
subscribe = {}
inboxes = {}
publish = {}
}
}
`,
err: errors.New(`Unknown field "inboxes" parsing permissions`),
errorLine: 5,
errorPos: 7,
},
{
name: "when user authorization permissions config has unknown fields within allow or deny",
config: `
authorization {
permissions {
subscribe = {
allow = ["hello", "world"]
deny = ["foo", "bar"]
denied = "_INBOX.>"
}
publish = {}
}
}
`,
err: errors.New(`Unknown field name "denied" parsing subject permissions, only 'allow' or 'deny' are permitted`),
errorLine: 7,
errorPos: 9,
},
{
name: "when user authorization permissions config has unknown fields within allow or deny",
config: `
authorization {
permissions {
publish = {
allow = ["hello", "world"]
deny = ["foo", "bar"]
allowed = "_INBOX.>"
}
subscribe = {}
}
}
`,
err: errors.New(`Unknown field name "allowed" parsing subject permissions, only 'allow' or 'deny' are permitted`),
errorLine: 7,
errorPos: 9,
},
{
name: "when user authorization permissions config has unknown fields using arrays",
config: `
authorization {
default_permissions {
subscribe = ["a"]
publish = ["b"]
inboxes = ["c"]
}
users = [
{
user = "foo"
pass = "bar"
}
]
}
`,
err: errors.New(`Unknown field "inboxes" parsing permissions`),
errorLine: 7,
errorPos: 6,
},
{
name: "when user authorization permissions config has unknown fields using strings",
config: `
authorization {
default_permissions {
subscribe = "a"
requests = "b"
publish = "c"
}
users = [
{
user = "foo"
pass = "bar"
}
]
}
`,
err: errors.New(`Unknown field "requests" parsing permissions`),
errorLine: 6,
errorPos: 6,
},
{
name: "when user authorization permissions config is empty",
config: `
authorization = {
users = [
{
user = "foo", pass = "bar", permissions = {
}
}
]
}
`,
err: nil,
},
{
name: "when unknown permissions are included in user config",
config: `
authorization = {
users = [
{
user = "foo", pass = "bar", permissions {
inboxes = true
}
}
]
}
`,
err: errors.New(`Unknown field "inboxes" parsing permissions`),
errorLine: 6,
errorPos: 11,
},
{
name: "when clustering config is empty",
config: `
cluster = {
}
`,
err: nil,
},
{
name: "when unknown option is in clustering config",
config: `
# NATS Server Configuration
port = 4222
cluster = {
port = 6222
foo = "bar"
authorization {
user = "hello"
pass = "world"
}
}
`,
err: errors.New(`unknown field "foo"`),
errorLine: 9,
errorPos: 5,
},
{
name: "when unknown option is in clustering authorization config",
config: `
cluster = {
authorization {
foo = "bar"
}
}
`,
err: errors.New(`unknown field "foo"`),
errorLine: 4,
errorPos: 7,
},
{
name: "when unknown option is in tls config",
config: `
tls = {
hello = "world"
}
`,
err: errors.New(`error parsing tls config, unknown field ["hello"]`),
errorLine: 3,
errorPos: 5,
},
{
name: "when unknown option is in cluster tls config",
config: `
cluster {
tls = {
foo = "bar"
}
}
`,
err: errors.New(`error parsing tls config, unknown field ["foo"]`),
errorLine: 4,
errorPos: 7,
},
{
name: "when using cipher suites in the TLS config",
config: `
tls = {
cipher_suites: [
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
]
preferences = []
}
`,
err: errors.New(`error parsing tls config, unknown field ["preferences"]`),
errorLine: 7,
errorPos: 7,
},
{
name: "when using curve preferences in the TLS config",
config: `
tls = {
curve_preferences: [
"CurveP256",
"CurveP384",
"CurveP521"
]
suites = []
}
`,
err: errors.New(`error parsing tls config, unknown field ["suites"]`),
errorLine: 8,
errorPos: 7,
},
{
name: "when using curve preferences in the TLS config",
config: `
tls = {
curve_preferences: [
"CurveP5210000"
]
}
`,
err: errors.New(`unrecognized curve preference CurveP5210000`),
errorLine: 4,
errorPos: 5,
},
{
name: "when unknown option is in cluster config with defined routes",
config: `
cluster {
port = 6222
routes = [
nats://127.0.0.1:6222
]
peers = []
}
`,
err: errors.New(`unknown field "peers"`),
errorLine: 7,
errorPos: 5,
},
{
name: "when used as variable in authorization block it should not be considered as unknown field",
config: `
# listen: 127.0.0.1:-1
listen: 127.0.0.1:4222
authorization {
# Superuser can do anything.
super_user = {
publish = ">"
subscribe = ">"
}
# Can do requests on foo or bar, and subscribe to anything
# that is a response to an _INBOX.
#
# Notice that authorization filters can be singletons or arrays.
req_pub_user = {
publish = ["req.foo", "req.bar"]
subscribe = "_INBOX.>"
}
# Setup a default user that can subscribe to anything, but has
# no publish capabilities.
default_user = {
subscribe = "PUBLIC.>"
}
unused = "hello"
# Default permissions if none presented. e.g. susan below.
default_permissions: $default_user
# Users listed with persmissions.
users = [
{user: alice, password: foo, permissions: $super_user}
{user: bob, password: bar, permissions: $req_pub_user}
{user: susan, password: baz}
]
}
`,
err: errors.New(`unknown field "unused"`),
errorLine: 27,
errorPos: 5,
},
{
name: "when used as variable in top level config it should not be considered as unknown field",
config: `
monitoring_port = 8222
http_port = $monitoring_port
port = 4222
`,
err: nil,
},
{
name: "when used as variable in cluster config it should not be considered as unknown field",
config: `
cluster {
clustering_port = 6222
port = $clustering_port
}
`,
err: nil,
},
{
name: "when setting permissions within cluster authorization block",
config: `
cluster {
authorization {
permissions = {
publish = { allow = ["foo", "bar"] }
}
}
permissions = {
publish = { deny = ["foo", "bar"] }
}
}
`,
warningErr: errors.New(`invalid use of field "authorization"`),
errorLine: 3,
errorPos: 5,
reason: `setting "permissions" within cluster authorization block is deprecated`,
},
{
name: "when write deadline is used with deprecated usage",
config: `
write_deadline = 100
`,
warningErr: errors.New(`invalid use of field "write_deadline"`),
errorLine: 2,
errorPos: 17,
reason: `write_deadline should be converted to a duration`,
},
/////////////////////
// ACCOUNTS //
/////////////////////
{
name: "when accounts block is correctly configured",
config: `
http_port = 8222
accounts {
#
# synadia > nats.io, cncf
#
synadia {
# SAADJL5XAEM6BDYSWDTGVILJVY54CQXZM5ZLG4FRUAKB62HWRTPNSGXOHA
nkey = "AC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL"
users [
{
# SUAEL6RU3BSDAFKOHNTEOK5Q6FTM5FTAMWVIKBET6FHPO4JRII3CYELVNM
nkey = "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
}
]
exports = [
{ service: "synadia.requests", accounts: [nats, cncf] }
]
}
#
# nats < synadia
#
nats {
# SUAJTM55JH4BNYDA22DMDZJSRBRKVDGSLYK2HDIOCM3LPWCDXIDV5Q4CIE
nkey = "ADRZ42QBM7SXQDXXTSVWT2WLLFYOQGAFC4TO6WOAXHEKQHIXR4HFYJDS"
users [
{
# SUADZTYQAKTY5NQM7XRB5XR3C24M6ROGZLBZ6P5HJJSSOFUGC5YXOOECOM
nkey = "UD6AYQSOIN2IN5OGC6VQZCR4H3UFMIOXSW6NNS6N53CLJA4PB56CEJJI"
}
]
imports = [
# This account has to send requests to 'nats.requests' subject
{ service: { account: "synadia", subject: "synadia.requests" }, to: "nats.requests" }
]
}
#
# cncf < synadia
#
cncf {
# SAAFHDZX7SGZ2SWHPS22JRPPK5WX44NPLNXQHR5C5RIF6QRI3U65VFY6C4
nkey = "AD4YRVUJF2KASKPGRMNXTYKIYSCB3IHHB4Y2ME6B2PDIV5QJ23C2ZRIT"
users [
{
# SUAKINP3Z2BPUXWOFSW2FZC7TFJCMMU7DHKP2C62IJQUDASOCDSTDTRMJQ
nkey = "UB57IEMPG4KOTPFV5A66QKE2HZ3XBXFHVRCCVMJEWKECMVN2HSH3VTSJ"
}
]
imports = [
# This account has to send requests to 'synadia.requests' subject
{ service: { account: "synadia", subject: "synadia.requests" } }
]
}
}
`,
err: nil,
},
{
name: "when nkey is invalid within accounts block",
config: `
accounts {
#
# synadia > nats.io, cncf
#
synadia {
# SAADJL5XAEM6BDYSWDTGVILJVY54CQXZM5ZLG4FRUAKB62HWRTPNSGXOHA
nkey = "AC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL"
users [
{
# SUAEL6RU3BSDAFKOHNTEOK5Q6FTM5FTAMWVIKBET6FHPO4JRII3CYELVNM
nkey = "SCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
}
]
exports = [
{ service: "synadia.requests", accounts: [nats, cncf] }
]
}
#
# nats < synadia
#
nats {
# SUAJTM55JH4BNYDA22DMDZJSRBRKVDGSLYK2HDIOCM3LPWCDXIDV5Q4CIE
nkey = "ADRZ42QBM7SXQDXXTSVWT2WLLFYOQGAFC4TO6WOAXHEKQHIXR4HFYJDS"
users [
{
# SUADZTYQAKTY5NQM7XRB5XR3C24M6ROGZLBZ6P5HJJSSOFUGC5YXOOECOM
nkey = "UD6AYQSOIN2IN5OGC6VQZCR4H3UFMIOXSW6NNS6N53CLJA4PB56CEJJI"
}
]
imports = [
# This account has to send requests to 'nats.requests' subject
{ service: { account: "synadia", subject: "synadia.requests" }, to: "nats.requests" }
]
}
#
# cncf < synadia
#
cncf {
# SAAFHDZX7SGZ2SWHPS22JRPPK5WX44NPLNXQHR5C5RIF6QRI3U65VFY6C4
nkey = "AD4YRVUJF2KASKPGRMNXTYKIYSCB3IHHB4Y2ME6B2PDIV5QJ23C2ZRIT"
users [
{
# SUAKINP3Z2BPUXWOFSW2FZC7TFJCMMU7DHKP2C62IJQUDASOCDSTDTRMJQ
nkey = "UB57IEMPG4KOTPFV5A66QKE2HZ3XBXFHVRCCVMJEWKECMVN2HSH3VTSJ"
}
]
imports = [
# This account has to send requests to 'synadia.requests' subject
{ service: { account: "synadia", subject: "synadia.requests" } }
]
}
}
`,
err: errors.New(`Not a valid public nkey for a user`),
errorLine: 14,
errorPos: 11,
},
{
name: "when accounts block has unknown fields",
config: `
http_port = 8222
accounts {
foo = "bar"
}`,
err: errors.New(`Expected map entries for accounts`),
errorLine: 5,
errorPos: 19,
},
{
name: "when accounts has a referenced config variable within same block",
config: `
accounts {
PERMISSIONS = {
publish = {
allow = ["foo","bar"]
deny = ["quux"]
}
}
synadia {
nkey = "AC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL"
users [
{
nkey = "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
permissions = $PERMISSIONS
}
]
exports = [
{ stream: "synadia.>" }
]
}
}`,
err: nil,
},
{
name: "when accounts has an unreferenced config variables within same block",
config: `
accounts {
PERMISSIONS = {
publish = {
allow = ["foo","bar"]
deny = ["quux"]
}
}
synadia {
nkey = "AC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL"
users [
{
nkey = "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"
}
]
exports = [
{ stream: "synadia.>" }
]
}
}`,
err: errors.New(`unknown field "publish"`),
errorLine: 4,
errorPos: 5,
},
{
name: "when accounts block defines a global account",
config: `
http_port = 8222
accounts {
$G = {
}
}
`,
err: errors.New(`"$G" is a Reserved Account`),
errorLine: 5,
errorPos: 19,
},
{
name: "when accounts block uses an invalid public key",
config: `
accounts {
synadia = {
nkey = "invalid"
}
}
`,
err: errors.New(`Not a valid public nkey for an account: "invalid"`),
errorLine: 4,
errorPos: 21,
},
{
name: "when accounts list includes reserved account",
config: `
port = 4222
accounts = [foo, bar, "$G"]
http_port = 8222
`,
err: errors.New(`"$G" is a Reserved Account`),
errorLine: 4,
errorPos: 26,
},
{
name: "when accounts list includes a dupe entry",
config: `
port = 4222
accounts = [foo, bar, bar]
http_port = 8222
`,
err: errors.New(`Duplicate Account Entry: bar`),
errorLine: 4,
errorPos: 25,
},
{
name: "when accounts block includes a dupe user",
config: `
port = 4222
accounts = {
nats {
users = [
{ user: "foo", pass: "bar" },
{ user: "hello", pass: "world" },
{ user: "foo", pass: "bar" }
]
}
}
http_port = 8222
`,
err: errors.New(`Duplicate user "foo" detected`),
errorLine: 6,
errorPos: 21,
},
{
name: "when accounts block imports are not a list",
config: `
port = 4222
accounts = {
nats {
imports = true
}
}
http_port = 8222
`,
err: errors.New(`Imports should be an array, got bool`),
errorLine: 6,
errorPos: 21,
},
{
name: "when accounts block exports are not a list",
config: `
port = 4222
accounts = {
nats {
exports = true
}
}
http_port = 8222
`,
err: errors.New(`Exports should be an array, got bool`),
errorLine: 6,
errorPos: 21,
},
{
name: "when accounts block imports items are not a map",
config: `
port = 4222
accounts = {
nats {
imports = [
false
]
}
}
http_port = 8222
`,
err: errors.New(`Import Items should be a map with type entry, got bool`),
errorLine: 7,
errorPos: 23,
},
{
name: "when accounts block export items are not a map",
config: `
port = 4222
accounts = {
nats {
exports = [
false
]
}
}
http_port = 8222
`,
err: errors.New(`Export Items should be a map with type entry, got bool`),
errorLine: 7,
errorPos: 23,
},
{
name: "when accounts exports has a stream name that is not a string",
config: `
port = 4222
accounts = {
nats {
exports = [
{
stream: false
}
]
}
}
http_port = 8222
`,
err: errors.New(`Expected stream name to be string, got bool`),
errorLine: 8,
errorPos: 25,
},
{
name: "when accounts exports has a service name that is not a string",
config: `
accounts = {
nats {
exports = [
{
service: false
}
]
}
}
`,
err: errors.New(`Expected service name to be string, got bool`),
errorLine: 6,
errorPos: 25,
},
{
name: "when accounts imports stream without name",
config: `
port = 4222
accounts = {
nats {
imports = [
{ stream: { }}
]
}
}
http_port = 8222
`,
err: errors.New(`Expect an account name and a subject`),
errorLine: 7,
errorPos: 25,
},
{
name: "when accounts imports service without name",
config: `
port = 4222
accounts = {
nats {
imports = [
{ service: { }}
]
}
}
http_port = 8222
`,
err: errors.New(`Expect an account name and a subject`),
errorLine: 7,
errorPos: 25,
},
{
name: "when user authorization config has both token and users",
config: `
authorization = {
token = "s3cr3t"
users = [
{
user = "foo"
pass = "bar"
}
]
}
`,
err: errors.New(`Can not have a token and a users array`),
errorLine: 2,
errorPos: 3,
},
{
name: "when user authorization config has both token and user",
config: `
authorization = {
user = "foo"
pass = "bar"
users = [
{
user = "foo"
pass = "bar"
}
]
}
`,
err: errors.New(`Can not have a single user/pass and a users array`),
errorLine: 2,
errorPos: 3,
},
{
name: "when user authorization config has users not as a list",
config: `
authorization = {
users = false
}
`,
err: errors.New(`Expected users field to be an array, got false`),
errorLine: 3,
errorPos: 5,
},
{
name: "when user authorization config has users not as a map",
config: `
authorization = {
users = [false]
}
`,
err: errors.New(`Expected user entry to be a map/struct, got false`),
errorLine: 3,
errorPos: 14,
},
{
name: "when user authorization config has permissions not as a map",
config: `
authorization = {
users = [{user: hello, pass: world}]
permissions = false
}
`,
err: errors.New(`Expected permissions to be a map/struct, got false`),
errorLine: 4,
errorPos: 19,
},
{
name: "when user authorization permissions config has invalid fields within allow",
config: `
authorization {
permissions {
publish = {
allow = [false, "hello", "world"]
deny = ["foo", "bar"]
}
subscribe = {}
}
}
`,
err: errors.New(`Subject in permissions array cannot be cast to string`),
errorLine: 5,
errorPos: 18,
},
{
name: "when user authorization permissions config has invalid fields within deny",
config: `
authorization {
permissions {
publish = {
allow = ["hello", "world"]
deny = [true, "foo", "bar"]
}
subscribe = {}
}
}
`,
err: errors.New(`Subject in permissions array cannot be cast to string`),
errorLine: 6,
errorPos: 17,
},
{
name: "when user authorization permissions config has invalid type",
config: `
authorization {
permissions {
publish = {
allow = false
}
subscribe = {}
}
}
`,
err: errors.New(`Expected subject permissions to be a subject, or array of subjects, got bool`),
errorLine: 5,
errorPos: 9,
},
{
name: "when user authorization permissions subject is invalid",
config: `
authorization {
permissions {
publish = {
allow = ["foo..bar"]
}
subscribe = {}
}
}
`,
err: errors.New(`subject "foo..bar" is not a valid subject`),
errorLine: 5,
errorPos: 9,
},
{
name: "when cluster config listen is invalid",
config: `
cluster {
listen = "0.0.0.0:XXXX"
}
`,
err: errors.New(`could not parse port "XXXX"`),
errorLine: 3,
errorPos: 5,
},
{
name: "when cluster config includes multiple users",
config: `
cluster {
authorization {
users = []
}
}
`,
err: errors.New(`Cluster authorization does not allow multiple users`),
errorLine: 3,
errorPos: 5,
},
{
name: "when cluster routes are invalid",
config: `
cluster {
routes = [
"0.0.0.0:XXXX"
# "0.0.0.0:YYYY"
# "0.0.0.0:ZZZZ"
]
}
`,
err: errors.New(`error parsing route url ["0.0.0.0:XXXX"]`),
errorLine: 4,
errorPos: 22,
},
{
name: "when setting invalid TLS config within cluster block",
config: `
cluster {
tls {
}
}
`,
err: nil,
errorLine: 0,
errorPos: 0,
},
{
name: "invalid lame_duck_duration type",
config: `
lame_duck_duration: abc
`,
err: errors.New(`error parsing lame_duck_duration: time: invalid duration abc`),
errorLine: 2,
errorPos: 3,
},
{
name: "when only setting TLS timeout for a leafnode remote",
config: `
leafnodes {
remotes = [
{
url: "tls://connect.ngs.global:7422"
tls {
timeout: 0.01
}
}
]
}`,
err: nil,
errorLine: 0,
errorPos: 0,
},
{
name: "when setting latency tracking without a system account",
config: `
accounts {
sys { users = [ {user: sys, pass: "" } ] }
nats.io: {
users = [ { user : bar, pass: "" } ]
exports = [
{ service: "nats.add"
response: singleton
latency: {
sampling: 100%
subject: "latency.tracking.add"
}
}
]
}
}
`,
err: errors.New(`Error adding service latency sampling for "nats.add": system account not setup`),
errorLine: 2,
errorPos: 17,
},
{
name: "when setting latency tracking with a system account",
config: `
system_account: sys
accounts {
sys { users = [ {user: sys, pass: "" } ] }
nats.io: {
users = [ { user : bar, pass: "" } ]
exports = [
{ service: "nats.add"
response: singleton
latency: {
sampling: 100%
subject: "latency.tracking.add"
}
}
]
}
}
`,
err: nil,
errorLine: 0,
errorPos: 0,
},
{
name: "when setting latency tracking with an invalid publish subject",
config: `
system_account = sys
accounts {
sys { users = [ {user: sys, pass: "" } ] }
nats.io: {
users = [ { user : bar, pass: "" } ]
exports = [
{ service: "nats.add"
response: singleton
latency: "*"
}
]
}
}
`,
err: errors.New(`Error adding service latency sampling for "nats.add" on subject "*": invalid publish subject`),
errorLine: 3,
errorPos: 17,
},
{
name: "when setting latency tracking on a stream",
config: `
system_account = sys
accounts {
sys { users = [ {user: sys, pass: "" } ] }
nats.io: {
users = [ { user : bar, pass: "" } ]
exports = [
{ stream: "nats.add"
latency: "foo"
}
]
}
}
`,
err: errors.New(`Detected latency directive on non-service`),
errorLine: 11,
errorPos: 25,
},
{
name: "when using duplicate service import subject",
config: `
accounts {
A: {
users = [ {user: user1, pass: ""} ]
exports = [
{service: "remote1"}
{service: "remote2"}
]
}
B: {
users = [ {user: user2, pass: ""} ]
imports = [
{service: {account: "A", subject: "remote1"}, to: "local"}
{service: {account: "A", subject: "remote2"}, to: "local"}
]
}
}
`,
err: errors.New(`Duplicate service import subject "local", previously used in import for account "A", subject "remote1"`),
errorLine: 14,
errorPos: 71,
},
{
name: "mixing single and multi users in leafnode authorization",
config: `
leafnodes {
authorization {
user: user1
password: pwd
users = [{user: user2, password: pwd}]
}
}
`,
err: errors.New("can not have a single user/pass and a users array"),
errorLine: 3,
errorPos: 20,
},
{
name: "duplicate usernames in leafnode authorization",
config: `
leafnodes {
authorization {
users = [
{user: user, password: pwd}
{user: user, password: pwd}
]
}
}
`,
err: errors.New(`duplicate user "user" detected in leafnode authorization`),
errorLine: 3,
errorPos: 20,
},
}
checkConfig := func(config string) error {
opts := &Options{
CheckConfig: true,
}
return opts.ProcessConfigFile(config)
}
checkErr := func(t *testing.T, err, expectedErr error) {
t.Helper()
switch {
case err == nil && expectedErr == nil:
// OK
case err != nil && expectedErr == nil:
t.Errorf("Unexpected error after processing config: %s", err)
case err == nil && expectedErr != nil:
t.Errorf("Expected %q error after processing invalid config but got nothing", expectedErr)
}
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
conf := createConfFile(t, []byte(test.config))
defer os.Remove(conf)
err := checkConfig(conf)
var expectedErr error
// Check for either warnings or errors.
if test.err != nil {
expectedErr = test.err
} else if test.warningErr != nil {
expectedErr = test.warningErr
}
if err != nil && expectedErr != nil {
msg := fmt.Sprintf("%s:%d:%d: %s", conf, test.errorLine, test.errorPos, expectedErr.Error())
if test.reason != "" {
msg += ": " + test.reason
}
msg += "\n"
if err.Error() != msg {
t.Errorf("Expected:\n%q\ngot:\n%q", msg, err.Error())
}
}
checkErr(t, err, expectedErr)
})
}
}
func TestConfigCheckIncludes(t *testing.T) {
// Check happy path first.
opts := &Options{
CheckConfig: true,
}
err := opts.ProcessConfigFile("./configs/include_conf_check_a.conf")
if err != nil {
t.Errorf("Unexpected error processing include files with configuration check enabled: %v", err)
}
opts = &Options{
CheckConfig: true,
}
err = opts.ProcessConfigFile("./configs/include_bad_conf_check_a.conf")
if err == nil {
t.Errorf("Expected error processing include files with configuration check enabled: %v", err)
}
expectedErr := `include_bad_conf_check_b.conf:10:19: unknown field "monitoring_port"` + "\n"
if err != nil && !strings.HasSuffix(err.Error(), expectedErr) {
t.Errorf("Expected: \n%q, got\n: %q", expectedErr, err.Error())
}
}
func TestConfigCheckMultipleErrors(t *testing.T) {
opts := &Options{
CheckConfig: true,
}
err := opts.ProcessConfigFile("./configs/multiple_errors.conf")
if err == nil {
t.Errorf("Expected error processing config files with multiple errors check enabled: %v", err)
}
cerr, ok := err.(*processConfigErr)
if !ok {
t.Fatalf("Expected a configuration process error")
}
got := len(cerr.Warnings())
expected := 1
if got != expected {
t.Errorf("Expected a %d warning, got: %d", expected, got)
}
got = len(cerr.Errors())
expected = 7
if got != 7 {
t.Errorf("Expected a %d errors, got: %d", expected, got)
}
errMsg := err.Error()
errs := []string{
`./configs/multiple_errors.conf:12:1: invalid use of field "write_deadline": write_deadline should be converted to a duration`,
`./configs/multiple_errors.conf:2:1: Cannot have a user/pass and token`,
`./configs/multiple_errors.conf:10:1: unknown field "monitoring"`,
`./configs/multiple_errors.conf:67:3: Cluster authorization does not allow multiple users`,
`./configs/multiple_errors.conf:21:5: Not a valid public nkey for an account: "OC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL"`,
`./configs/multiple_errors.conf:26:9: Not a valid public nkey for a user`,
`./configs/multiple_errors.conf:36:5: Not a valid public nkey for an account: "ODRZ42QBM7SXQDXXTSVWT2WLLFYOQGAFC4TO6WOAXHEKQHIXR4HFYJDS"`,
`./configs/multiple_errors.conf:41:9: Not a valid public nkey for a user`,
}
for _, msg := range errs {
found := strings.Contains(errMsg, msg)
if !found {
t.Errorf("Expected to find error %q", msg)
}
}
}