mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
This PR introduces native support for MQTT clients. It requires use
of accounts with JetStream enabled. Since as of now clustering is
not available, MQTT will be limited to single instance.
Only QoS 0 and 1 are supported at the moment. MQTT clients can
exchange messages with NATS clients and vice-versa.
Since JetStream is required, accounts with JetStream enabled must
exist in order for an MQTT client to connect to the NATS Server.
The administrator can limit the users that can use MQTT with the
allowed_connection_types option in the user section. For instance:
```
accounts {
mqtt {
users [
{user: all, password: pwd, allowed_connection_types: ["STANDARD", "WEBSOCKET", "MQTT"]}
{user: mqtt_only, password: pwd, allowed_connection_types: "MQTT"}
]
jetstream: enabled
}
}
```
The "mqtt_only" can only be used for MQTT connections, which the user
"all" accepts standard, websocket and MQTT clients.
Here is what a configuration to enable MQTT looks like:
```
mqtt {
# Specify a host and port to listen for websocket connections
#
# listen: "host:port"
# It can also be configured with individual parameters,
# namely host and port.
#
# host: "hostname"
port: 1883
# TLS configuration section
#
# tls {
# cert_file: "/path/to/cert.pem"
# key_file: "/path/to/key.pem"
# ca_file: "/path/to/ca.pem"
#
# # Time allowed for the TLS handshake to complete
# timeout: 2.0
#
# # Takes the user name from the certificate
# #
# # verify_an_map: true
#}
# Authentication override. Here are possible options.
#
# authorization {
# # Simple username/password
# #
# user: "some_user_name"
# password: "some_password"
#
# # Token. The server will check the MQTT's password in the connect
# # protocol against this token.
# #
# # token: "some_token"
#
# # Time allowed for the client to send the MQTT connect protocol
# # after the TCP connection is established.
# #
# timeout: 2.0
#}
# If an MQTT client connects and does not provide a username/password and
# this option is set, the server will use this client (and therefore account).
#
# no_auth_user: "some_user_name"
# This is the time after which the server will redeliver a QoS 1 message
# sent to a subscription that has not acknowledged (PUBACK) the message.
# The default is 30 seconds.
#
# ack_wait: "1m"
# This limits the number of QoS1 messages sent to a session without receiving
# acknowledgement (PUBACK) from that session. MQTT specification defines
# a packet identifier as an unsigned int 16, which means that the maximum
# value is 65535. The default value is 1024.
#
# max_ack_pending: 100
}
```
Signed-off-by: Ivan Kozlovic <ivan@synadia.com>
1582 lines
36 KiB
Go
1582 lines
36 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: "verify_cert_and_check_known_urls not support for clients",
|
|
config: `
|
|
tls = {
|
|
cert_file: "configs/certs/server.pem"
|
|
key_file: "configs/certs/key.pem"
|
|
verify_cert_and_check_known_urls: true
|
|
}
|
|
`,
|
|
err: errors.New("verify_cert_and_check_known_urls not supported in this context"),
|
|
errorLine: 5,
|
|
errorPos: 10,
|
|
},
|
|
{
|
|
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`),
|
|
errorLine: 2,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "lame_duck_duration too small",
|
|
config: `
|
|
lame_duck_duration: "5s"
|
|
`,
|
|
err: errors.New(`invalid lame_duck_duration of 5s, minimum is 30 seconds`),
|
|
errorLine: 2,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "invalid lame_duck_grace_period type",
|
|
config: `
|
|
lame_duck_grace_period: abc
|
|
`,
|
|
err: errors.New(`error parsing lame_duck_grace_period: time: invalid duration`),
|
|
errorLine: 2,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "lame_duck_grace_period should be positive",
|
|
config: `
|
|
lame_duck_grace_period: "-5s"
|
|
`,
|
|
err: errors.New(`invalid lame_duck_grace_period, needs to be positive`),
|
|
errorLine: 2,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when only setting TLS timeout for a leafnode remote",
|
|
config: `
|
|
leafnodes {
|
|
remotes = [
|
|
{
|
|
url: "tls://nats:7422"
|
|
tls {
|
|
timeout: 0.01
|
|
}
|
|
}
|
|
]
|
|
}`,
|
|
err: nil,
|
|
errorLine: 0,
|
|
errorPos: 0,
|
|
},
|
|
{
|
|
name: "verify_cert_and_check_known_urls do not work for leaf nodes",
|
|
config: `
|
|
leafnodes {
|
|
remotes = [
|
|
{
|
|
url: "tls://nats:7422"
|
|
tls {
|
|
timeout: 0.01
|
|
verify_cert_and_check_known_urls: true
|
|
}
|
|
}
|
|
]
|
|
}`,
|
|
//Unexpected error after processing config: /var/folders/9h/6g_c9l6n6bb8gp331d_9y0_w0000gn/T/057996446:8:5:
|
|
err: errors.New("verify_cert_and_check_known_urls not supported in this context"),
|
|
errorLine: 8,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when leafnode remotes use wrong type",
|
|
config: `
|
|
leafnodes {
|
|
remotes: {
|
|
url: "tls://nats:7422"
|
|
}
|
|
}`,
|
|
err: errors.New(`Expected remotes field to be an array, got map[string]interface {}`),
|
|
errorLine: 3,
|
|
errorPos: 5,
|
|
},
|
|
{
|
|
name: "when leafnode remotes url uses wrong type",
|
|
config: `
|
|
leafnodes {
|
|
remotes: [
|
|
{ urls: 1234 }
|
|
]
|
|
}`,
|
|
err: errors.New(`Expected remote leafnode url to be an array or string, got 1234`),
|
|
errorLine: 4,
|
|
errorPos: 18,
|
|
},
|
|
{
|
|
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: 21,
|
|
},
|
|
{
|
|
name: "mqtt bad type",
|
|
config: `
|
|
mqtt [
|
|
"wrong"
|
|
]
|
|
`,
|
|
err: errors.New(`Expected mqtt to be a map, got []interface {}`),
|
|
errorLine: 2,
|
|
errorPos: 17,
|
|
},
|
|
{
|
|
name: "mqtt bad listen",
|
|
config: `
|
|
mqtt {
|
|
listen: "xxxxxxxx"
|
|
}
|
|
`,
|
|
err: errors.New(`could not parse address string "xxxxxxxx"`),
|
|
errorLine: 3,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "mqtt bad host",
|
|
config: `
|
|
mqtt {
|
|
host: 1234
|
|
}
|
|
`,
|
|
err: errors.New(`interface conversion: interface {} is int64, not string`),
|
|
errorLine: 3,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "mqtt bad port",
|
|
config: `
|
|
mqtt {
|
|
port: "abc"
|
|
}
|
|
`,
|
|
err: errors.New(`interface conversion: interface {} is string, not int64`),
|
|
errorLine: 3,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "mqtt bad TLS",
|
|
config: `
|
|
mqtt {
|
|
port: -1
|
|
tls {
|
|
cert_file: "./configs/certs/server.pem"
|
|
}
|
|
}
|
|
`,
|
|
err: errors.New(`missing 'key_file' in TLS configuration`),
|
|
errorLine: 4,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "connection types wrong type",
|
|
config: `
|
|
authorization {
|
|
users [
|
|
{user: a, password: pwd, allowed_connection_types: 123}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`error parsing allowed connection types: unsupported type int64`),
|
|
errorLine: 4,
|
|
errorPos: 53,
|
|
},
|
|
{
|
|
name: "connection types content wrong type",
|
|
config: `
|
|
authorization {
|
|
users [
|
|
{user: a, password: pwd, allowed_connection_types: [
|
|
123
|
|
WEBSOCKET
|
|
]}
|
|
]
|
|
}
|
|
`,
|
|
err: errors.New(`error parsing allowed connection types: unsupported type in array int64`),
|
|
errorLine: 5,
|
|
errorPos: 32,
|
|
},
|
|
{
|
|
name: "connection types type unknown",
|
|
config: `
|
|
authorization {
|
|
users [
|
|
{user: a, password: pwd, allowed_connection_types: [ "UNKNOWN" ]}
|
|
]
|
|
}
|
|
`,
|
|
err: fmt.Errorf("invalid connection types [%q]", "UNKNOWN"),
|
|
errorLine: 4,
|
|
errorPos: 53,
|
|
},
|
|
{
|
|
name: "websocket auth unknown var",
|
|
config: `
|
|
websocket {
|
|
authorization {
|
|
unknown: "field"
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("unknown field %q", "unknown"),
|
|
errorLine: 4,
|
|
errorPos: 25,
|
|
},
|
|
{
|
|
name: "websocket bad tls",
|
|
config: `
|
|
websocket {
|
|
tls {
|
|
cert_file: "configs/certs/server.pem"
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("missing 'key_file' in TLS configuration"),
|
|
errorLine: 3,
|
|
errorPos: 21,
|
|
},
|
|
{
|
|
name: "verify_cert_and_check_known_urls not support for websockets",
|
|
config: `
|
|
websocket {
|
|
tls {
|
|
cert_file: "configs/certs/server.pem"
|
|
key_file: "configs/certs/key.pem"
|
|
verify_cert_and_check_known_urls: true
|
|
}
|
|
}
|
|
`,
|
|
err: fmt.Errorf("verify_cert_and_check_known_urls not supported in this context"),
|
|
errorLine: 6,
|
|
errorPos: 10,
|
|
},
|
|
}
|
|
|
|
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
|
|
}
|
|
if !strings.Contains(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)
|
|
}
|
|
}
|
|
}
|