2026-04-06 15:48:37 +05:30
// Copyright IBM Corp. 2020, 2026
2022-09-12 09:37:13 -04:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-05-21 12:12:08 -04:00
package config
import (
2025-12-22 11:30:10 +05:30
"crypto/rand"
2023-10-31 10:32:46 -04:00
"encoding/base64"
2021-05-04 08:37:56 -05:00
"fmt"
2024-11-01 13:27:40 -04:00
"net"
2022-12-01 13:15:53 -05:00
"net/http"
2021-05-04 08:37:56 -05:00
"os"
2020-05-21 12:12:08 -04:00
"testing"
2021-08-04 17:57:36 -04:00
"time"
2020-05-21 12:12:08 -04:00
2026-10-12 10:33:41 -07:00
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/event"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/ratelimit"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/util"
2022-03-16 17:04:07 -04:00
configutil "github.com/hashicorp/go-secure-stdlib/configutil/v2"
2021-07-14 17:50:15 -07:00
"github.com/hashicorp/go-secure-stdlib/listenerutil"
2021-12-07 13:01:04 +00:00
"github.com/hashicorp/go-secure-stdlib/parseutil"
2020-05-21 12:12:08 -04:00
"github.com/stretchr/testify/assert"
2021-05-04 08:37:56 -05:00
"github.com/stretchr/testify/require"
2020-05-21 12:12:08 -04:00
)
func TestDevController ( t * testing . T ) {
actual , err := DevController ()
if err != nil {
t . Fatal ( err )
}
2021-02-11 20:10:48 -05:00
truePointer := new ( bool )
* truePointer = true
2022-12-01 13:15:53 -05:00
apiHeaders := map [ int ] http . Header {
0 : {
"Content-Security-Policy" : { "default-src 'none'" },
"X-Content-Type-Options" : { "nosniff" },
"Strict-Transport-Security" : { "max-age=31536000; includeSubDomains" },
"Cache-Control" : { "no-store" },
},
}
uiHeaders := map [ int ] http . Header {
0 : {
2023-05-29 13:30:16 -07:00
"Content-Security-Policy" : { defaultCsp },
2022-12-01 13:15:53 -05:00
"X-Content-Type-Options" : { "nosniff" },
"Strict-Transport-Security" : { "max-age=31536000; includeSubDomains" },
"Cache-Control" : { "no-store" },
},
}
2020-05-21 12:12:08 -04:00
exp := & Config {
2021-07-08 15:50:25 -04:00
Eventing : event . DefaultEventerConfig (),
2020-05-21 12:12:08 -04:00
SharedConfig : & configutil . SharedConfig {
DisableMlock : true ,
2021-07-14 17:50:15 -07:00
Listeners : [] * listenerutil . ListenerConfig {
2020-05-21 12:12:08 -04:00
{
2022-12-01 13:15:53 -05:00
Type : "tcp" ,
Purpose : [] string { "api" },
TLSDisable : true ,
CorsEnabled : truePointer ,
CorsAllowedOrigins : [] string { "*" },
CustomApiResponseHeaders : apiHeaders ,
CustomUiResponseHeaders : uiHeaders ,
2020-05-21 12:12:08 -04:00
},
{
2022-12-01 13:15:53 -05:00
Type : "tcp" ,
Purpose : [] string { "cluster" },
CustomApiResponseHeaders : apiHeaders ,
CustomUiResponseHeaders : uiHeaders ,
2020-05-21 12:12:08 -04:00
},
2022-03-28 17:16:39 +01:00
{
2022-12-01 13:15:53 -05:00
Type : "tcp" ,
Purpose : [] string { "ops" },
TLSDisable : true ,
CustomApiResponseHeaders : apiHeaders ,
CustomUiResponseHeaders : uiHeaders ,
2022-03-28 17:16:39 +01:00
},
2020-05-21 12:12:08 -04:00
},
Seals : [] * configutil . KMS {
{
Type : "aead" ,
2020-08-14 10:12:31 -04:00
Purpose : [] string { "root" },
2020-05-21 12:12:08 -04:00
Config : map [ string ] string {
"aead_type" : "aes-gcm" ,
2020-08-19 16:47:18 -04:00
"key_id" : "global_root" ,
2020-05-21 12:12:08 -04:00
},
},
{
Type : "aead" ,
Purpose : [] string { "worker-auth" },
Config : map [ string ] string {
"aead_type" : "aes-gcm" ,
2020-08-19 16:47:18 -04:00
"key_id" : "global_worker-auth" ,
2020-05-21 12:12:08 -04:00
},
},
2023-04-02 17:45:43 -04:00
{
Type : "aead" ,
Purpose : [] string { "bsr" },
Config : map [ string ] string {
"aead_type" : "aes-gcm" ,
"key_id" : "global_bsr" ,
},
},
2020-08-20 13:34:03 -04:00
{
Type : "aead" ,
Purpose : [] string { "recovery" },
Config : map [ string ] string {
"aead_type" : "aes-gcm" ,
"key_id" : "global_recovery" ,
},
},
2020-05-21 12:12:08 -04:00
},
},
2020-08-12 00:40:39 -04:00
Controller : & Controller {
2023-12-04 17:25:16 +00:00
Name : "dev-controller" ,
Description : "A default controller created in dev mode" ,
ApiRateLimits : make ( ratelimit . Configs , 0 ),
ApiRateLimiterMaxQuotas : ratelimit . DefaultLimiterMaxQuotas (),
2020-08-12 00:40:39 -04:00
},
2020-05-21 12:12:08 -04:00
DevController : true ,
}
2023-04-14 20:01:14 -04:00
exp . Eventing . ErrorEventsDisabled = true
exp . Eventing . SysEventsEnabled = false
exp . Eventing . ObservationsEnabled = false
2020-05-21 12:12:08 -04:00
exp . Listeners [ 0 ]. RawConfig = actual . Listeners [ 0 ]. RawConfig
exp . Listeners [ 1 ]. RawConfig = actual . Listeners [ 1 ]. RawConfig
2022-03-28 17:16:39 +01:00
exp . Listeners [ 2 ]. RawConfig = actual . Listeners [ 2 ]. RawConfig
2020-05-21 12:12:08 -04:00
exp . Seals [ 0 ]. Config [ "key" ] = actual . Seals [ 0 ]. Config [ "key" ]
exp . Seals [ 1 ]. Config [ "key" ] = actual . Seals [ 1 ]. Config [ "key" ]
2020-08-20 13:34:03 -04:00
exp . Seals [ 2 ]. Config [ "key" ] = actual . Seals [ 2 ]. Config [ "key" ]
2023-04-02 17:45:43 -04:00
exp . Seals [ 3 ]. Config [ "key" ] = actual . Seals [ 3 ]. Config [ "key" ]
2020-09-23 01:23:10 -04:00
exp . DevControllerKey = actual . Seals [ 0 ]. Config [ "key" ]
exp . DevWorkerAuthKey = actual . Seals [ 1 ]. Config [ "key" ]
2023-04-02 17:45:43 -04:00
exp . DevBsrKey = actual . Seals [ 2 ]. Config [ "key" ]
exp . DevRecoveryKey = actual . Seals [ 3 ]. Config [ "key" ]
2020-05-21 12:12:08 -04:00
assert . Equal ( t , exp , actual )
2021-04-14 15:26:21 -05:00
// Do some CORS-specific testing
{
// CORS disabled
conf := `
listener "tcp" {
purpose = "api"
cors_enabled = false
}
`
actual , err = Parse ( conf )
assert . NoError ( t , err )
l0 := actual . Listeners [ 0 ]
assert . False ( t , * l0 . CorsEnabled )
assert . Empty ( t , l0 . CorsAllowedHeaders )
// Enabled with a wildcard
conf = `
listener "tcp" {
purpose = "api"
cors_enabled = true
cors_allowed_origins = ["*"]
}
`
actual , err = Parse ( conf )
assert . NoError ( t , err )
l0 = actual . Listeners [ 0 ]
assert . True ( t , * l0 . CorsEnabled )
assert . Equal ( t , [] string { "*" }, l0 . CorsAllowedOrigins )
assert . Nil ( t , l0 . CorsDisableDefaultAllowedOriginValues )
// Disabled, default behavior
conf = `
listener "tcp" {
purpose = "api"
}
`
actual , err = Parse ( conf )
assert . NoError ( t , err )
l0 = actual . Listeners [ 0 ]
assert . True ( t , * l0 . CorsEnabled )
2021-05-21 12:33:02 -05:00
assert . Equal ( t , [] string { "*" }, l0 . CorsAllowedOrigins )
2021-04-14 15:26:21 -05:00
assert . Nil ( t , l0 . CorsDisableDefaultAllowedOriginValues )
// Disabled, default behavior
conf = `
listener "tcp" {
purpose = "api"
cors_disable_default_allowed_origin_values = true
}
`
actual , err = Parse ( conf )
assert . NoError ( t , err )
l0 = actual . Listeners [ 0 ]
assert . Nil ( t , l0 . CorsEnabled )
assert . Empty ( t , l0 . CorsAllowedOrigins )
assert . True ( t , * l0 . CorsDisableDefaultAllowedOriginValues )
}
2021-10-14 11:56:08 -04:00
// Test plugins block
{
// CORS disabled
conf := `
plugins {
execution_dir = "/tmp/foobar"
}
`
actual , err = Parse ( conf )
assert . NoError ( t , err )
assert . Equal ( t , actual . Plugins . ExecutionDir , "/tmp/foobar" )
}
2020-05-21 12:12:08 -04:00
}
2020-06-24 13:08:13 -07:00
2020-07-20 13:52:16 -04:00
func TestDevWorker ( t * testing . T ) {
2023-04-14 20:01:14 -04:00
actual , err := DevWorker ( WithSysEventsEnabled ( true ), WithObservationsEnabled ( true ), TestWithErrorEventsEnabled ( t , true ))
2020-07-20 13:52:16 -04:00
if err != nil {
t . Fatal ( err )
}
exp := & Config {
2021-07-08 15:50:25 -04:00
Eventing : event . DefaultEventerConfig (),
2020-07-20 13:52:16 -04:00
SharedConfig : & configutil . SharedConfig {
DisableMlock : true ,
2021-07-14 17:50:15 -07:00
Listeners : [] * listenerutil . ListenerConfig {
2020-07-20 13:52:16 -04:00
{
2020-10-10 13:42:04 -04:00
Type : "tcp" ,
Purpose : [] string { "proxy" },
2022-12-01 13:15:53 -05:00
CustomApiResponseHeaders : map [ int ] http . Header {
0 : {
"Content-Security-Policy" : { "default-src 'none'" },
"X-Content-Type-Options" : { "nosniff" },
"Strict-Transport-Security" : { "max-age=31536000; includeSubDomains" },
"Cache-Control" : { "no-store" },
},
},
CustomUiResponseHeaders : map [ int ] http . Header {
0 : {
2023-05-29 13:30:16 -07:00
"Content-Security-Policy" : { defaultCsp },
2022-12-01 13:15:53 -05:00
"X-Content-Type-Options" : { "nosniff" },
"Strict-Transport-Security" : { "max-age=31536000; includeSubDomains" },
"Cache-Control" : { "no-store" },
},
},
2020-07-20 13:52:16 -04:00
},
},
2022-08-05 12:12:07 -04:00
Seals : [] * configutil . KMS {
{
Type : "aead" ,
Purpose : [] string { "worker-auth-storage" },
Config : map [ string ] string {
"aead_type" : "aes-gcm" ,
"key_id" : "worker-auth-storage" ,
},
},
},
2020-07-20 13:52:16 -04:00
},
Worker : & Worker {
2022-06-13 15:06:03 -07:00
Name : "w_1234567890" ,
Description : "A default worker created in dev mode" ,
InitialUpstreams : [] string { "127.0.0.1" },
2022-10-25 18:29:38 -05:00
InitialUpstreamsRaw : [] any { "127.0.0.1" },
2021-01-21 16:27:20 -05:00
Tags : map [ string ][] string {
"type" : { "dev" , "local" },
},
2020-07-20 13:52:16 -04:00
},
}
exp . Listeners [ 0 ]. RawConfig = actual . Listeners [ 0 ]. RawConfig
2022-08-05 12:12:07 -04:00
exp . Seals [ 0 ]. Config [ "key" ] = actual . Seals [ 0 ]. Config [ "key" ]
2021-01-21 16:27:20 -05:00
exp . Worker . TagsRaw = actual . Worker . TagsRaw
assert . Equal ( t , exp , actual )
// Redo it with key=value syntax for tags
devWorkerKeyValueConfig := `
listener "tcp" {
purpose = "proxy"
}
worker {
2022-05-16 15:14:12 -07:00
name = "w_1234567890"
2021-01-21 16:27:20 -05:00
description = "A default worker created in dev mode"
2022-06-13 15:06:03 -07:00
initial_upstreams = ["127.0.0.1"]
2021-01-21 16:27:20 -05:00
tags = ["type=dev", "type=local"]
}
`
2020-07-20 13:52:16 -04:00
2021-01-21 16:27:20 -05:00
actual , err = Parse ( devConfig + devWorkerKeyValueConfig )
assert . NoError ( t , err )
exp . Listeners [ 0 ]. RawConfig = actual . Listeners [ 0 ]. RawConfig
2022-08-05 12:12:07 -04:00
exp . Seals = nil
2021-01-21 16:27:20 -05:00
exp . Worker . TagsRaw = actual . Worker . TagsRaw
2020-07-20 13:52:16 -04:00
assert . Equal ( t , exp , actual )
2021-01-21 16:27:20 -05:00
2021-03-29 16:00:40 -04:00
// Handle when there is a singular value not indicated as a slice
devWorkerKeyValueConfig = `
listener "tcp" {
purpose = "proxy"
}
worker {
2022-05-16 15:14:12 -07:00
name = "w_1234567890"
2021-03-29 16:00:40 -04:00
description = "A default worker created in dev mode"
2022-06-13 15:06:03 -07:00
initial_upstreams = ["127.0.0.1"]
2021-03-29 16:00:40 -04:00
tags {
type = "local"
}
}
`
actual , err = Parse ( devConfig + devWorkerKeyValueConfig )
assert . NoError ( t , err )
exp . Listeners [ 0 ]. RawConfig = actual . Listeners [ 0 ]. RawConfig
exp . Worker . TagsRaw = actual . Worker . TagsRaw
prevTags := exp . Worker . Tags
exp . Worker . Tags = map [ string ][] string { "type" : { "local" }}
assert . Equal ( t , exp , actual )
exp . Worker . Tags = prevTags
2021-01-21 16:27:20 -05:00
// Redo it with non-lower-cased keys
devWorkerKeyValueConfig = `
listener "tcp" {
purpose = "proxy"
}
worker {
2022-05-16 15:14:12 -07:00
name = "w_1234567890"
2021-01-21 16:27:20 -05:00
description = "A default worker created in dev mode"
2022-06-13 15:06:03 -07:00
initial_upstreams = ["127.0.0.1"]
2021-01-21 16:27:20 -05:00
tags = ["tyPe=dev", "type=local"]
}
`
_ , err = Parse ( devConfig + devWorkerKeyValueConfig )
assert . Error ( t , err )
// Redo it with non-lower-cased values
devWorkerKeyValueConfig = `
listener "tcp" {
purpose = "proxy"
}
worker {
2022-05-16 15:14:12 -07:00
name = "w_1234567890"
2021-01-21 16:27:20 -05:00
description = "A default worker created in dev mode"
2022-06-13 15:06:03 -07:00
initial_upstreams = ["127.0.0.1"]
2021-01-21 16:27:20 -05:00
tags = ["type=dev", "type=loCal"]
}
`
_ , err = Parse ( devConfig + devWorkerKeyValueConfig )
assert . Error ( t , err )
// Redo with non-printable characters to validate the strutil function
devWorkerKeyValueConfig = `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "dev-work\u0000er"
description = "A default worker created in dev mode"
2022-06-13 15:06:03 -07:00
initial_upstreams = ["127.0.0.1"]
2021-01-21 16:27:20 -05:00
tags = ["type=dev", "type=local"]
}
`
_ , err = Parse ( devConfig + devWorkerKeyValueConfig )
assert . Error ( t , err )
2022-09-02 14:32:41 -04:00
// Check activation token parsing
devWorkerActivationTokenConfig := `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "dev-worker"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
controller_generated_activation_token = "foobar"
}
`
actual , err = Parse ( devConfig + devWorkerActivationTokenConfig )
require . NoError ( t , err )
assert . Equal ( t , "foobar" , actual . Worker . ControllerGeneratedActivationToken )
2020-07-20 13:52:16 -04:00
}
2021-05-04 08:37:56 -05:00
2022-06-03 13:25:58 -07:00
func TestDevCombined ( t * testing . T ) {
actual , err := DevCombined ()
if err != nil {
t . Fatal ( err )
}
truePointer := new ( bool )
* truePointer = true
2022-12-01 13:15:53 -05:00
apiHeaders := map [ int ] http . Header {
0 : {
"Content-Security-Policy" : { "default-src 'none'" },
"X-Content-Type-Options" : { "nosniff" },
"Strict-Transport-Security" : { "max-age=31536000; includeSubDomains" },
"Cache-Control" : { "no-store" },
},
}
uiHeaders := map [ int ] http . Header {
0 : {
2023-05-29 13:30:16 -07:00
"Content-Security-Policy" : { defaultCsp },
2022-12-01 13:15:53 -05:00
"X-Content-Type-Options" : { "nosniff" },
"Strict-Transport-Security" : { "max-age=31536000; includeSubDomains" },
"Cache-Control" : { "no-store" },
},
}
2022-06-03 13:25:58 -07:00
exp := & Config {
Eventing : event . DefaultEventerConfig (),
SharedConfig : & configutil . SharedConfig {
DisableMlock : true ,
Listeners : [] * listenerutil . ListenerConfig {
{
2022-12-01 13:15:53 -05:00
Type : "tcp" ,
Purpose : [] string { "api" },
TLSDisable : true ,
CorsEnabled : truePointer ,
CorsAllowedOrigins : [] string { "*" },
CustomApiResponseHeaders : apiHeaders ,
CustomUiResponseHeaders : uiHeaders ,
2022-06-03 13:25:58 -07:00
},
{
2022-12-01 13:15:53 -05:00
Type : "tcp" ,
Purpose : [] string { "cluster" },
CustomApiResponseHeaders : apiHeaders ,
CustomUiResponseHeaders : uiHeaders ,
2022-06-03 13:25:58 -07:00
},
{
2022-12-01 13:15:53 -05:00
Type : "tcp" ,
Purpose : [] string { "ops" },
TLSDisable : true ,
CustomApiResponseHeaders : apiHeaders ,
CustomUiResponseHeaders : uiHeaders ,
2022-06-03 13:25:58 -07:00
},
{
2022-12-01 13:15:53 -05:00
Type : "tcp" ,
Purpose : [] string { "proxy" },
CustomApiResponseHeaders : apiHeaders ,
CustomUiResponseHeaders : uiHeaders ,
2022-06-03 13:25:58 -07:00
},
},
Seals : [] * configutil . KMS {
{
Type : "aead" ,
Purpose : [] string { "root" },
Config : map [ string ] string {
"aead_type" : "aes-gcm" ,
"key_id" : "global_root" ,
},
},
{
Type : "aead" ,
Purpose : [] string { "worker-auth" },
Config : map [ string ] string {
"aead_type" : "aes-gcm" ,
"key_id" : "global_worker-auth" ,
},
},
2023-04-02 17:45:43 -04:00
{
Type : "aead" ,
Purpose : [] string { "bsr" },
Config : map [ string ] string {
"aead_type" : "aes-gcm" ,
"key_id" : "global_bsr" ,
},
},
2022-06-03 13:25:58 -07:00
{
Type : "aead" ,
2022-08-05 12:12:07 -04:00
Purpose : [] string { "recovery" },
2022-06-03 13:25:58 -07:00
Config : map [ string ] string {
"aead_type" : "aes-gcm" ,
2022-08-05 12:12:07 -04:00
"key_id" : "global_recovery" ,
2022-06-03 13:25:58 -07:00
},
},
{
Type : "aead" ,
2022-08-05 12:12:07 -04:00
Purpose : [] string { "worker-auth-storage" },
2022-06-03 13:25:58 -07:00
Config : map [ string ] string {
"aead_type" : "aes-gcm" ,
2022-08-05 12:12:07 -04:00
"key_id" : "worker-auth-storage" ,
2022-06-03 13:25:58 -07:00
},
},
},
},
Controller : & Controller {
2023-12-04 17:25:16 +00:00
Name : "dev-controller" ,
Description : "A default controller created in dev mode" ,
ApiRateLimits : make ( ratelimit . Configs , 0 ),
ApiRateLimiterMaxQuotas : ratelimit . DefaultLimiterMaxQuotas (),
2022-06-03 13:25:58 -07:00
},
DevController : true ,
Worker : & Worker {
2022-06-13 15:06:03 -07:00
Name : "w_1234567890" ,
Description : "A default worker created in dev mode" ,
InitialUpstreams : [] string { "127.0.0.1" },
2022-10-25 18:29:38 -05:00
InitialUpstreamsRaw : [] any { "127.0.0.1" },
2022-06-03 13:25:58 -07:00
Tags : map [ string ][] string {
"type" : { "dev" , "local" },
},
},
}
exp . Listeners [ 0 ]. RawConfig = actual . Listeners [ 0 ]. RawConfig
exp . Listeners [ 1 ]. RawConfig = actual . Listeners [ 1 ]. RawConfig
exp . Listeners [ 2 ]. RawConfig = actual . Listeners [ 2 ]. RawConfig
exp . Listeners [ 3 ]. RawConfig = actual . Listeners [ 3 ]. RawConfig
exp . Seals [ 0 ]. Config [ "key" ] = actual . Seals [ 0 ]. Config [ "key" ]
exp . Seals [ 1 ]. Config [ "key" ] = actual . Seals [ 1 ]. Config [ "key" ]
exp . Seals [ 2 ]. Config [ "key" ] = actual . Seals [ 2 ]. Config [ "key" ]
exp . Seals [ 3 ]. Config [ "key" ] = actual . Seals [ 3 ]. Config [ "key" ]
2023-04-02 17:45:43 -04:00
exp . Seals [ 4 ]. Config [ "key" ] = actual . Seals [ 4 ]. Config [ "key" ]
2022-06-03 13:25:58 -07:00
exp . DevControllerKey = actual . Seals [ 0 ]. Config [ "key" ]
exp . DevWorkerAuthKey = actual . Seals [ 1 ]. Config [ "key" ]
2023-04-02 17:45:43 -04:00
exp . DevBsrKey = actual . Seals [ 2 ]. Config [ "key" ]
exp . DevRecoveryKey = actual . Seals [ 3 ]. Config [ "key" ]
exp . DevWorkerAuthStorageKey = actual . Seals [ 4 ]. Config [ "key" ]
2022-06-03 13:25:58 -07:00
exp . Worker . TagsRaw = actual . Worker . TagsRaw
assert . Equal ( t , exp , actual )
}
2023-03-28 09:39:28 -07:00
func TestDevWorkerCredentialStoragePath ( t * testing . T ) {
2022-06-03 13:25:58 -07:00
t . Parallel ()
tests := [] struct {
2022-06-09 16:27:17 -04:00
name string
devWorkerProvidedConfiguration string
storagePath string
2022-06-03 13:25:58 -07:00
}{
{
name : "Relative Storage Directory" ,
2022-06-09 16:27:17 -04:00
devWorkerProvidedConfiguration : `
2022-06-03 13:25:58 -07:00
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
2022-06-13 15:06:03 -07:00
initial_upstreams = ["127.0.0.1"]
2022-06-03 13:25:58 -07:00
tags {
type = ["dev", "local"]
}
2022-06-03 17:32:33 -04:00
auth_storage_path = ".."
2022-06-03 13:25:58 -07:00
}
` ,
storagePath : ".." ,
},
{
name : "Nonexistent Storage Directory" ,
2022-06-09 16:27:17 -04:00
devWorkerProvidedConfiguration : `
2022-06-03 13:25:58 -07:00
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
2022-06-13 15:06:03 -07:00
initial_upstreams = ["127.0.0.1"]
2022-06-03 13:25:58 -07:00
tags {
type = ["dev", "local"]
}
2022-06-03 17:32:33 -04:00
auth_storage_path = "nonexistent_dir/here"
2022-06-03 13:25:58 -07:00
}
` ,
storagePath : "nonexistent_dir/here" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2022-06-09 16:27:17 -04:00
parsed , err := Parse ( devConfig + tt . devWorkerProvidedConfiguration )
2022-06-03 13:25:58 -07:00
require . NoError ( t , err )
require . Equal ( t , tt . storagePath , parsed . Worker . AuthStoragePath )
})
}
}
2023-12-15 15:26:35 -08:00
func TestDevWorkerRecordingStorageMinimumAvailableCapacity ( t * testing . T ) {
t . Parallel ()
td := t . TempDir ()
tests := [] struct {
name string
devWorkerProvidedConfiguration string
storagePath string
storageCapacity string
expectedDiskSpace uint64
expectedErrMsg string
}{
{
name : "empty storage with empty capacity" ,
devWorkerProvidedConfiguration : `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags {
type = ["dev", "local"]
}
}
` ,
expectedDiskSpace : 0 ,
storagePath : "" ,
},
{
name : "empty storage path with set capacity" ,
devWorkerProvidedConfiguration : `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags {
type = ["dev", "local"]
}
recording_storage_minimum_available_capacity = "4kib"
}
` ,
expectedErrMsg : "recording_storage_path cannot be empty when providing recording_storage_minimum_available_capacity" ,
},
{
name : "storage path with empty capacity defaults to 500mib" ,
devWorkerProvidedConfiguration : fmt . Sprintf ( `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags {
type = ["dev", "local"]
}
recording_storage_path = "%v"
}
` , td ),
storagePath : td ,
expectedDiskSpace : 524288000 ,
},
{
name : "storage path with capacity string" ,
devWorkerProvidedConfiguration : fmt . Sprintf ( `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags {
type = ["dev", "local"]
}
recording_storage_path = "%v"
recording_storage_minimum_available_capacity = "4kib"
}
` , td ),
storagePath : td ,
expectedDiskSpace : 4096 ,
},
{
name : "storage path with raw byte value" ,
devWorkerProvidedConfiguration : fmt . Sprintf ( `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags {
type = ["dev", "local"]
}
recording_storage_path = "%v"
recording_storage_minimum_available_capacity = "4096"
}
` , td ),
storagePath : td ,
expectedDiskSpace : 4096 ,
},
{
name : "storage path with invalid capacity input" ,
devWorkerProvidedConfiguration : fmt . Sprintf ( `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags {
type = ["dev", "local"]
}
recording_storage_path = "%v"
recording_storage_minimum_available_capacity = "gib"
}
` , td ),
storagePath : td ,
expectedErrMsg : "could not parse capacity from input" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
parsed , err := Parse ( devConfig + tt . devWorkerProvidedConfiguration )
if tt . expectedErrMsg != "" {
require . Error ( t , err )
assert . ErrorContains ( t , err , tt . expectedErrMsg )
return
}
require . NoError ( t , err )
assert . Equal ( t , tt . storagePath , parsed . Worker . RecordingStoragePath )
assert . Equal ( t , tt . expectedDiskSpace , parsed . Worker . RecordingStorageMinimumAvailableDiskSpace )
})
}
}
2023-03-28 09:39:28 -07:00
func TestDevWorkerRecordingStoragePath ( t * testing . T ) {
t . Parallel ()
td := t . TempDir ()
tests := [] struct {
name string
devWorkerProvidedConfiguration string
storagePath string
}{
{
name : "Relative Storage Directory" ,
devWorkerProvidedConfiguration : `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags {
type = ["dev", "local"]
}
recording_storage_path = ".."
}
` ,
storagePath : ".." ,
},
{
name : "temp dir" ,
devWorkerProvidedConfiguration : fmt . Sprintf ( `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags {
type = ["dev", "local"]
}
recording_storage_path = "%v"
}
` , td ),
storagePath : td ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
parsed , err := Parse ( devConfig + tt . devWorkerProvidedConfiguration )
require . NoError ( t , err )
require . Equal ( t , tt . storagePath , parsed . Worker . RecordingStoragePath )
})
}
}
2025-05-13 16:55:28 +01:00
// TestDevControllerIpv6 validates that all listeners use an IPv6 address when
// the WithIPv6Enabled(true) option is passed into DevController. Other dev
// controller configurations are validated in TestDevController.
2024-11-01 13:27:40 -04:00
func TestDevControllerIpv6 ( t * testing . T ) {
2025-05-13 16:55:28 +01:00
require := require . New ( t )
2024-11-01 13:27:40 -04:00
actual , err := DevController ( WithIPv6Enabled ( true ))
require . NoError ( err )
2025-05-13 16:55:28 +01:00
// Expected an error here because PublicClusterAddr is not set.
2024-11-01 13:27:40 -04:00
_ , _ , err = net . SplitHostPort ( actual . Controller . PublicClusterAddr )
require . Error ( err )
2025-05-13 16:55:28 +01:00
// Same here.
2024-11-01 13:27:40 -04:00
publicAddr , port , err := util . SplitHostPort ( actual . Controller . PublicClusterAddr )
2025-05-13 16:55:28 +01:00
require . ErrorIs ( err , util . ErrMissingPort )
require . Empty ( port )
require . Empty ( publicAddr )
2024-11-01 13:27:40 -04:00
require . NotEmpty ( actual . Listeners )
for _ , l := range actual . Listeners {
addr , _ , err := util . SplitHostPort ( l . Address )
2025-05-13 16:55:28 +01:00
require . ErrorIs ( err , util . ErrMissingPort )
require . NotEmpty ( t , addr )
2024-11-01 13:27:40 -04:00
ip := net . ParseIP ( addr )
2025-05-13 16:55:28 +01:00
require . NotNil ( ip , "failed to parse listener address for %v" , l . Purpose )
require . NotNil ( ip . To16 (), "failed to convert address to IPv6 for %v, found %v" , l . Purpose , addr )
2024-11-01 13:27:40 -04:00
}
}
2025-05-13 16:55:28 +01:00
// TestDevWorkerIpv6 validates that all listeners use an IPv6 address when the
// WithIPv6Enabled(true) option is passed into DevWorker. Other dev worker
// configurations are validated in TestDevWorker.
2024-11-01 13:27:40 -04:00
func TestDevWorkerIpv6 ( t * testing . T ) {
2025-05-13 16:55:28 +01:00
require := require . New ( t )
2024-11-01 13:27:40 -04:00
actual , err := DevWorker ( WithIPv6Enabled ( true ))
require . NoError ( err )
2025-05-13 16:55:28 +01:00
// Expected an error here because PublicAddr does not have a port.
2024-11-01 13:27:40 -04:00
_ , _ , err = net . SplitHostPort ( actual . Worker . PublicAddr )
require . Error ( err )
2025-05-13 16:55:28 +01:00
// util.SplitHostPort, however, can handle it when ports are missing.
2024-11-01 13:27:40 -04:00
publicAddr , port , err := util . SplitHostPort ( actual . Worker . PublicAddr )
2025-05-13 16:55:28 +01:00
require . ErrorIs ( err , util . ErrMissingPort )
require . Empty ( port )
require . NotEmpty ( t , publicAddr )
2024-11-01 13:27:40 -04:00
ip := net . ParseIP ( publicAddr )
2025-05-13 16:55:28 +01:00
require . NotNil ( ip , "failed to parse worker public address" )
require . NotNil ( ip . To16 (), "worker public address is not IPv6 %s" , actual . Worker . PublicAddr )
2024-11-01 13:27:40 -04:00
require . NotEmpty ( actual . Listeners )
for _ , l := range actual . Listeners {
addr , _ , err := util . SplitHostPort ( l . Address )
2025-05-13 16:55:28 +01:00
require . ErrorIs ( err , util . ErrMissingPort )
require . NotEmpty ( addr )
2024-11-01 13:27:40 -04:00
ip := net . ParseIP ( addr )
2025-05-13 16:55:28 +01:00
require . NotNil ( ip , "failed to parse listener address for %v" , l . Purpose )
require . NotNil ( ip . To16 (), "failed to convert address to IPv6 for %v, found %v" , l . Purpose , addr )
2024-11-01 13:27:40 -04:00
}
}
2025-05-13 16:55:28 +01:00
// TestDevCombinedIpv6 validates that all listeners use an IPv6 address when the
// WithIPv6Enabled(true) option is passed into DevCombined.
2024-11-01 13:27:40 -04:00
func TestDevCombinedIpv6 ( t * testing . T ) {
2025-05-13 16:55:28 +01:00
require := require . New ( t )
2024-11-01 13:27:40 -04:00
actual , err := DevCombined ( WithIPv6Enabled ( true ))
require . NoError ( err )
2025-05-13 16:55:28 +01:00
// Expected to fail because PublicAddr does not have a port.
2024-11-01 13:27:40 -04:00
_ , _ , err = net . SplitHostPort ( actual . Worker . PublicAddr )
require . Error ( err )
2025-05-13 16:55:28 +01:00
// Expected to fail because PublicClusterAddr is not set.
2024-11-01 13:27:40 -04:00
_ , _ , err = net . SplitHostPort ( actual . Controller . PublicClusterAddr )
require . Error ( err )
2025-05-13 16:55:28 +01:00
// util.SplitHostPort, however, can handle it when ports are missing.
2024-11-01 13:27:40 -04:00
publicAddr , port , err := util . SplitHostPort ( actual . Worker . PublicAddr )
2025-05-13 16:55:28 +01:00
require . ErrorIs ( err , util . ErrMissingPort )
require . Empty ( port )
require . NotEmpty ( publicAddr )
2024-11-01 13:27:40 -04:00
ip := net . ParseIP ( publicAddr )
2025-05-13 16:55:28 +01:00
require . NotNil ( ip , "failed to parse worker public address" )
require . NotNil ( ip . To16 (), "worker public address is not IPv6 %s" , actual . Worker . PublicAddr )
2024-11-01 13:27:40 -04:00
2025-05-13 16:55:28 +01:00
// Expected to fail because PublicClusterAddr is not set.
2024-11-01 13:27:40 -04:00
publicAddr , port , err = util . SplitHostPort ( actual . Controller . PublicClusterAddr )
2025-05-13 16:55:28 +01:00
require . ErrorIs ( err , util . ErrMissingPort )
require . Empty ( port )
require . Empty ( publicAddr )
2024-11-01 13:27:40 -04:00
require . NotEmpty ( actual . Listeners )
for _ , l := range actual . Listeners {
addr , _ , err := util . SplitHostPort ( l . Address )
2025-05-13 16:55:28 +01:00
require . ErrorIs ( err , util . ErrMissingPort )
require . NotEmpty ( addr )
2024-11-01 13:27:40 -04:00
ip := net . ParseIP ( addr )
2025-05-13 16:55:28 +01:00
require . NotNil ( ip , "failed to parse listener address for %v" , l . Purpose )
require . NotNil ( ip . To16 (), "failed to convert address to IPv6 for %v, found %v" , l . Purpose , addr )
2024-11-01 13:27:40 -04:00
}
}
2022-06-03 13:25:58 -07:00
func TestDevKeyGeneration ( t * testing . T ) {
t . Parallel ()
2025-12-22 11:30:10 +05:30
dk := DevKeyGeneration ( WithRandomReader ( rand . Reader ))
2023-10-31 10:32:46 -04:00
buf , err := base64 . StdEncoding . DecodeString ( dk )
require . NoError ( t , err )
require . Len ( t , buf , 32 )
2022-06-03 13:25:58 -07:00
require . NotEqual ( t , dk , DevKeyGeneration ())
}
2021-05-04 08:37:56 -05:00
func TestParsingName ( t * testing . T ) {
t . Parallel ()
config := `
controller {
name = "%s"
}
worker {
name = "%s"
}
`
controllerEnv := "FOOENV"
workerEnv := "BARENV"
cases := [] struct {
name string
templateController string
templateWorker string
envController string
envWorker string
expectedController string
expectedWorker string
}{
{
name : "no env" ,
templateController : "foobar" ,
2022-05-16 15:14:12 -07:00
templateWorker : "test_worker_barfoo" ,
2021-05-04 08:37:56 -05:00
expectedController : "foobar" ,
2022-05-16 15:14:12 -07:00
expectedWorker : "test_worker_barfoo" ,
2021-05-04 08:37:56 -05:00
},
{
name : "env" ,
templateController : fmt . Sprintf ( "env://%s" , controllerEnv ),
templateWorker : fmt . Sprintf ( "env://%s" , workerEnv ),
envController : "foobar2" ,
2022-05-16 15:14:12 -07:00
envWorker : "env_name_barfoo2" ,
2021-05-04 08:37:56 -05:00
expectedController : "foobar2" ,
2022-05-16 15:14:12 -07:00
expectedWorker : "env_name_barfoo2" ,
2021-05-04 08:37:56 -05:00
},
}
for _ , tt := range cases {
t . Run ( tt . name , func ( t * testing . T ) {
if tt . envController != "" {
os . Setenv ( controllerEnv , tt . envController )
}
if tt . envWorker != "" {
os . Setenv ( workerEnv , tt . envWorker )
}
out , err := Parse ( fmt . Sprintf ( config , tt . templateController , tt . templateWorker ))
require . NoError ( t , err )
assert . Equal ( t , tt . expectedController , out . Controller . Name )
assert . Equal ( t , tt . expectedWorker , out . Worker . Name )
})
}
}
2021-07-08 15:50:25 -04:00
2022-09-13 10:01:49 -07:00
func TestParsingSchedulerIntervals ( t * testing . T ) {
t . Parallel ()
cases := [] struct {
name string
config string
wantErr bool
wantMonitorInterval time . Duration
wantRunJobInterval time . Duration
}{
{
name : "invalid-run-interval" ,
config : `
controller {
scheduler {
job_run_interval = "hello"
}
}
` ,
wantErr : true ,
},
{
name : "invalid-monitor-interval" ,
config : `
controller {
scheduler {
monitor_interval = "hello"
}
}
` ,
wantErr : true ,
},
{
name : "valid-undefined" ,
config : `controller { scheduler {} }` ,
wantMonitorInterval : 0 ,
wantRunJobInterval : 0 ,
},
{
name : "run-job-interval" ,
config : `
controller {
scheduler {
job_run_interval = "10m"
}
}
` ,
wantMonitorInterval : 0 ,
wantRunJobInterval : 10 * time . Minute ,
},
{
name : "monitor-interval" ,
config : `
controller {
scheduler {
monitor_interval = "6h"
}
}
` ,
wantMonitorInterval : 6 * time . Hour ,
wantRunJobInterval : 0 ,
},
{
name : "both" ,
config : `
controller {
scheduler {
monitor_interval = "7d"
job_run_interval = "20s"
}
}
` ,
wantMonitorInterval : 7 * 24 * time . Hour ,
wantRunJobInterval : 20 * time . Second ,
},
}
for _ , tt := range cases {
t . Run ( tt . name , func ( t * testing . T ) {
out , err := Parse ( tt . config )
if tt . wantErr {
require . Error ( t , err )
return
}
require . NoError ( t , err )
assert . Equal ( t , tt . wantMonitorInterval , out . Controller . Scheduler . MonitorIntervalDuration )
assert . Equal ( t , tt . wantRunJobInterval , out . Controller . Scheduler . JobRunIntervalDuration )
})
}
}
2021-12-03 11:47:00 +00:00
func TestWorkerTags ( t * testing . T ) {
defaultStateFn := func ( t * testing . T , tags string ) {
2026-10-12 10:20:06 -07:00
t . Setenv ( "THRESHOLD_WORKER_TAGS" , tags )
2021-12-03 11:47:00 +00:00
}
tests := [] struct {
name string
in string
stateFn func ( t * testing . T , tags string )
actualTags string
expWorkerTags map [ string ][] string
expErr bool
expErrStr string
}{
{
name : "tags in HCL" ,
in : `
worker {
tags {
type = ["dev", "local"]
typetwo = "devtwo"
}
}` ,
expWorkerTags : map [ string ][] string {
"type" : { "dev" , "local" },
"typetwo" : { "devtwo" },
},
expErr : false ,
},
{
name : "tags in HCL key=value" ,
in : `
worker {
tags = ["type=dev", "type=local", "typetwo=devtwo"]
}
` ,
expWorkerTags : map [ string ][] string {
"type" : { "dev" , "local" },
"typetwo" : { "devtwo" },
},
expErr : false ,
},
{
name : "no tags" ,
in : `
worker {
2022-05-16 15:14:12 -07:00
name = "w_1234567890"
2021-12-03 11:47:00 +00:00
}
` ,
expWorkerTags : nil ,
expErr : false ,
},
{
name : "empty tags" ,
in : `
worker {
2022-05-16 15:14:12 -07:00
name = "w_1234567890"
2021-12-03 11:47:00 +00:00
tags = {}
}
` ,
expWorkerTags : map [ string ][] string {},
expErr : false ,
},
{
name : "empty tags 2" ,
in : `
worker {
2022-05-16 15:14:12 -07:00
name = "w_1234567890"
2021-12-03 11:47:00 +00:00
tags = []
}
` ,
expWorkerTags : map [ string ][] string {},
expErr : false ,
},
{
name : "empty str" ,
in : `
worker {
tags = ""
}` ,
expWorkerTags : map [ string ][] string {},
expErr : false ,
},
{
name : "empty env var" ,
in : `
worker {
2026-10-12 10:20:06 -07:00
tags = "env://THRESHOLD_WORKER_TAGS"
2021-12-03 11:47:00 +00:00
}` ,
expWorkerTags : map [ string ][] string {},
expErr : false ,
},
{
name : "not a url - entire tags block" ,
in : `
worker {
tags = "\x00"
}` ,
expWorkerTags : map [ string ][] string {},
expErr : true ,
expErrStr : `Error parsing worker tags: error parsing url ("parse \"\\x00\": net/url: invalid control character in URL"): not a url` ,
},
{
name : "not a url - key's value set to string" ,
in : `
worker {
tags {
type = "\x00"
}
}
` ,
expWorkerTags : map [ string ][] string {
"type" : { "\x00" },
},
expErr : false ,
},
{
name : "one tag key" ,
in : `
worker {
2026-10-12 10:20:06 -07:00
tags = "env://THRESHOLD_WORKER_TAGS"
2021-12-03 11:47:00 +00:00
}` ,
stateFn : defaultStateFn ,
actualTags : `type = ["dev", "local"]` ,
expWorkerTags : map [ string ][] string {
"type" : { "dev" , "local" },
},
expErr : false ,
},
{
name : "multiple tag keys" ,
in : `
worker {
2026-10-12 10:20:06 -07:00
tags = "env://THRESHOLD_WORKER_TAGS"
2021-12-03 11:47:00 +00:00
}` ,
stateFn : defaultStateFn ,
actualTags : `
type = ["dev", "local"]
typetwo = ["devtwo", "localtwo"]
` ,
expWorkerTags : map [ string ][] string {
"type" : { "dev" , "local" },
"typetwo" : { "devtwo" , "localtwo" },
},
expErr : false ,
},
2022-08-26 13:37:26 -07:00
{
name : "comma in tag key string" ,
in : `
worker {
tags {
"key,"= ["value"],
}
}` ,
expErr : true ,
expErrStr : `Tag key "key," cannot contain commas` ,
},
{
name : "comma in tag value string" ,
in : `
worker {
tags {
"key"= ["va,lue","value2"],
}
}` ,
expErr : true ,
expErrStr : `Tag value "va,lue" for tag key "key" cannot contain commas` ,
},
2021-12-03 11:47:00 +00:00
{
name : "json tags - entire tags block" ,
in : `
worker {
2026-10-12 10:20:06 -07:00
tags = "env://THRESHOLD_WORKER_TAGS"
2021-12-03 11:47:00 +00:00
}` ,
stateFn : defaultStateFn ,
actualTags : `
{
"type": ["dev", "local"],
"typetwo": ["devtwo", "localtwo"]
}
` ,
expWorkerTags : map [ string ][] string {
"type" : { "dev" , "local" },
"typetwo" : { "devtwo" , "localtwo" },
},
expErr : false ,
},
{
name : "json tags - keys specified in the HCL file, values point to env/file" ,
in : `
worker {
tags = {
2026-10-12 10:20:06 -07:00
type = "env://THRESHOLD_WORKER_TAGS"
typetwo = "env://THRESHOLD_WORKER_TAGS_TWO"
2021-12-03 11:47:00 +00:00
}
}` ,
stateFn : func ( t * testing . T , tags string ) {
defaultStateFn ( t , tags )
2026-10-12 10:20:06 -07:00
t . Setenv ( "THRESHOLD_WORKER_TAGS_TWO" , `["devtwo", "localtwo"]` )
2021-12-03 11:47:00 +00:00
},
actualTags : `["dev","local"]` ,
expWorkerTags : map [ string ][] string {
"type" : { "dev" , "local" },
"typetwo" : { "devtwo" , "localtwo" },
},
expErr : false ,
},
{
name : "json tags - mix n' match" ,
in : `
worker {
name = "web-prod-us-east-1"
tags {
2026-10-12 10:20:06 -07:00
type = "env://THRESHOLD_WORKER_TYPE_TAGS"
2021-12-03 11:47:00 +00:00
typetwo = "file://type_two_tags.json"
typethree = ["devthree", "localthree"]
}
}
` ,
stateFn : func ( t * testing . T , tags string ) {
workerTypeTags := `["dev", "local"]`
2026-10-12 10:20:06 -07:00
t . Setenv ( "THRESHOLD_WORKER_TYPE_TAGS" , workerTypeTags )
2021-12-03 11:47:00 +00:00
filepath := "./type_two_tags.json"
err := os . WriteFile ( filepath , [] byte ( `["devtwo", "localtwo"]` ), 0 o666 )
require . NoError ( t , err )
t . Cleanup ( func () {
err := os . Remove ( filepath )
require . NoError ( t , err )
})
},
expWorkerTags : map [ string ][] string {
"type" : { "dev" , "local" },
"typetwo" : { "devtwo" , "localtwo" },
"typethree" : { "devthree" , "localthree" },
},
expErr : false ,
},
{
name : "bad json tags" ,
in : `
worker {
tags = {
2026-10-12 10:20:06 -07:00
type = "env://THRESHOLD_WORKER_TAGS"
typetwo = "env://THRESHOLD_WORKER_TAGS"
2021-12-03 11:47:00 +00:00
}
}` ,
stateFn : defaultStateFn ,
actualTags : `
{
"type": ["dev", "local"],
"typetwo": ["devtwo", "localtwo"]
}
` ,
expWorkerTags : nil ,
expErr : true ,
2022-08-03 19:29:17 -04:00
expErrStr : "Error unmarshaling env var/file contents: json: cannot unmarshal object into Go value of type []string" ,
2021-12-03 11:47:00 +00:00
},
{
name : "no clean mapping to internal structures" ,
in : `
worker {
2026-10-12 10:20:06 -07:00
tags = "env://THRESHOLD_WORKER_TAGS"
2021-12-03 11:47:00 +00:00
}` ,
stateFn : defaultStateFn ,
actualTags : `
worker {
tags {
type = "indeed"
}
}
` ,
expErr : true ,
expErrStr : "Error decoding the worker's tags: 1 error(s) decoding:\n\n* '[0][worker][0]' expected type 'string', got unconvertible type 'map[string]interface {}', value: 'map[tags:[map[type:indeed]]]'" ,
},
{
name : "not HCL" ,
in : `worker {
2026-10-12 10:20:06 -07:00
tags = "env://THRESHOLD_WORKER_TAGS"
2021-12-03 11:47:00 +00:00
}` ,
stateFn : defaultStateFn ,
actualTags : `not_hcl` ,
expErr : true ,
expErrStr : "Error decoding raw worker tags: At 1:9: key 'not_hcl' expected start of object ('{') or assignment ('=')" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if tt . stateFn != nil {
tt . stateFn ( t , tt . actualTags )
}
c , err := Parse ( tt . in )
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
require . Nil ( t , c )
return
}
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Worker )
require . Equal ( t , tt . expWorkerTags , c . Worker . Tags )
})
}
}
2021-07-08 15:50:25 -04:00
func TestController_EventingConfig ( t * testing . T ) {
t . Parallel ()
tests := [] struct {
name string
2021-08-04 14:01:58 -04:00
config [] string
2021-07-08 15:50:25 -04:00
wantEventerConfig * event . EventerConfig
2021-08-04 14:01:58 -04:00
wantErr string
2021-07-08 15:50:25 -04:00
}{
{
name : "default" ,
wantEventerConfig : event . DefaultEventerConfig (),
},
{
name : "audit-enabled" ,
2021-08-04 14:01:58 -04:00
config : [] string { `
2021-07-08 15:50:25 -04:00
events {
audit_enabled = true
}
2021-08-04 14:01:58 -04:00
` },
2021-07-08 15:50:25 -04:00
wantEventerConfig : & event . EventerConfig {
AuditEnabled : true ,
ObservationsEnabled : false ,
2021-08-30 19:02:48 -04:00
Sinks : [] * event . SinkConfig {
2021-07-08 15:50:25 -04:00
event . DefaultSink (),
},
},
},
{
name : "observations-enabled" ,
2021-08-04 14:01:58 -04:00
config : [] string { `
2021-07-08 15:50:25 -04:00
events {
observations_enabled = true
}
2021-08-04 14:01:58 -04:00
` },
2021-07-08 15:50:25 -04:00
wantEventerConfig : & event . EventerConfig {
AuditEnabled : false ,
ObservationsEnabled : true ,
2021-08-30 19:02:48 -04:00
Sinks : [] * event . SinkConfig {
2021-07-08 15:50:25 -04:00
event . DefaultSink (),
},
},
},
2021-08-04 14:01:58 -04:00
{
name : "no-sink-type-determined" ,
config : [] string {
`events {
audit_enabled = false
observations_enabled = true
sink {
format = "cloudevents-json"
name = "configured-sink"
event_types = [ "audit", "observation" ]
}
}` ,
},
wantErr : `error parsing "events": sink type could not be determined` ,
},
2021-07-08 15:50:25 -04:00
{
name : "sinks-configured" ,
2021-08-04 14:01:58 -04:00
config : [] string {
`events {
2021-07-08 15:50:25 -04:00
audit_enabled = false
observations_enabled = true
2021-08-04 14:01:58 -04:00
sink {
type = "file"
format = "cloudevents-json"
name = "configured-sink"
event_types = [ "audit", "observation" ]
file {
2021-07-08 15:50:25 -04:00
file_name = "file-name"
2021-08-04 17:57:36 -04:00
rotate_duration = "2m"
2021-07-08 15:50:25 -04:00
}
2021-08-04 14:01:58 -04:00
}
sink {
type = "stderr"
format = "hclog-text"
name = "stderr-sink"
event_types = [ "error" ]
}
}` ,
`events {
audit_enabled = false
observations_enabled = true
sink "file" {
format = "cloudevents-json"
name = "configured-sink"
event_types = [ "audit", "observation" ]
file {
file_name = "file-name"
2021-08-04 17:57:36 -04:00
rotate_duration = "2m"
2021-08-04 14:01:58 -04:00
}
}
sink "stderr" {
format = "hclog-text"
name = "stderr-sink"
event_types = [ "error" ]
}
}` ,
`events {
audit_enabled = false
observations_enabled = true
sink {
format = "cloudevents-json"
name = "configured-sink"
event_types = [ "audit", "observation" ]
file {
file_name = "file-name"
2021-08-04 17:57:36 -04:00
rotate_duration = "2m"
2021-08-04 14:01:58 -04:00
}
}
sink {
format = "hclog-text"
name = "stderr-sink"
event_types = [ "error" ]
stderr = {}
}
}` ,
`{
"events": {
"audit_enabled": false,
"observations_enabled": true,
"sink": [
{
"type": "file",
"format": "cloudevents-json",
"name": "configured-sink",
"event_types": ["audit", "observation"],
"file": {
2021-08-04 17:57:36 -04:00
"file_name": "file-name",
"rotate_duration": "2m"
2021-08-04 14:01:58 -04:00
}
},
{
"format": "hclog-text",
"name": "stderr-sink",
"event_types": ["error"],
"stderr": {}
}
]
}
}` ,
},
2021-07-08 15:50:25 -04:00
wantEventerConfig : & event . EventerConfig {
AuditEnabled : false ,
ObservationsEnabled : true ,
2021-08-30 19:02:48 -04:00
Sinks : [] * event . SinkConfig {
2021-07-08 15:50:25 -04:00
{
2021-08-04 14:01:58 -04:00
Type : "file" ,
2021-07-08 15:50:25 -04:00
Name : "configured-sink" ,
2021-08-04 14:01:58 -04:00
Format : "cloudevents-json" ,
2021-07-08 15:50:25 -04:00
EventTypes : [] event . Type { "audit" , "observation" },
2021-08-04 14:01:58 -04:00
FileConfig : & event . FileSinkTypeConfig {
2021-08-04 17:57:36 -04:00
FileName : "file-name" ,
RotateDurationHCL : "2m" ,
RotateDuration : 2 * time . Minute ,
2021-08-04 14:01:58 -04:00
},
},
{
Type : "stderr" ,
Name : "stderr-sink" ,
Format : "hclog-text" ,
EventTypes : [] event . Type { "error" },
StderrConfig : & event . StderrSinkTypeConfig {},
2021-07-08 15:50:25 -04:00
},
},
},
},
2022-04-18 11:03:10 -04:00
{
name : "audit_config" ,
config : [] string {
`events {
audit_enabled = true
sink {
name = "audit-sink"
format = "cloudevents-json"
event_types = ["audit"]
file {
file_name = "audit.log"
}
audit_config {
audit_filter_overrides {
sensitive = ""
secret = "hmac-sha256"
}
}
}
}` ,
},
wantEventerConfig : & event . EventerConfig {
AuditEnabled : true ,
Sinks : [] * event . SinkConfig {
{
Type : "file" ,
Name : "audit-sink" ,
Format : "cloudevents-json" ,
EventTypes : [] event . Type { "audit" },
FileConfig : & event . FileSinkTypeConfig {
FileName : "audit.log" ,
},
AuditConfig : & event . AuditConfig {
FilterOverridesHCL : map [ string ] string {
"sensitive" : "" ,
"secret" : "hmac-sha256" ,
},
FilterOverrides : event . AuditFilterOperations {
event . SensitiveClassification : event . NoOperation ,
event . SecretClassification : event . HmacSha256Operation ,
},
},
},
},
},
},
2021-07-08 15:50:25 -04:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
assert , require := assert . New ( t ), require . New ( t )
2021-08-04 14:01:58 -04:00
for i , conf := range tt . config {
c , err := Parse ( conf )
if tt . wantErr != "" {
require . Error ( err )
assert . Empty ( c )
assert . Equal ( tt . wantErr , err . Error (), "config %d want %q and got %q" , i , tt . wantErr , err . Error ())
return
}
2021-07-08 15:50:25 -04:00
require . NoError ( err )
2021-08-04 14:01:58 -04:00
assert . NotEmpty ( c )
assert . Equal ( tt . wantEventerConfig , c . Eventing )
2021-07-08 15:50:25 -04:00
}
})
}
}
2021-12-07 11:28:12 -05:00
2022-06-13 15:06:03 -07:00
func TestWorkerUpstreams ( t * testing . T ) {
2021-12-07 13:01:04 +00:00
tests := [] struct {
2022-06-13 15:06:03 -07:00
name string
in string
stateFn func ( t * testing . T )
expWorkerUpstreams [] string
expErr bool
expErrIs error
expErrStr string
2021-12-07 13:01:04 +00:00
}{
{
2022-06-13 15:06:03 -07:00
name : "No Upstreams" ,
2021-12-07 13:01:04 +00:00
in : `
worker {
name = "test"
}
` ,
2022-06-13 15:06:03 -07:00
expWorkerUpstreams : nil ,
expErr : false ,
2021-12-07 13:01:04 +00:00
},
{
2024-09-20 11:30:27 -07:00
name : "ipv4 Upstream" ,
2021-12-07 13:01:04 +00:00
in : `
worker {
name = "test"
2022-06-13 15:06:03 -07:00
initial_upstreams = ["127.0.0.1"]
2021-12-07 13:01:04 +00:00
}
` ,
2022-06-13 15:06:03 -07:00
expWorkerUpstreams : [] string { "127.0.0.1" },
expErr : false ,
2021-12-07 13:01:04 +00:00
},
2024-09-20 11:30:27 -07:00
{
name : "ipv6 Upstream" ,
in : `
worker {
name = "test"
2025-05-13 16:55:28 +01:00
initial_upstreams = ["2001:4860:4860:0:0:0:0:8888"]
2024-09-20 11:30:27 -07:00
}
` ,
2025-05-13 16:55:28 +01:00
expWorkerUpstreams : [] string { "2001:4860:4860::8888" },
2024-09-20 11:30:27 -07:00
expErr : false ,
},
{
name : "abbreviated ipv6 Upstream" ,
in : `
worker {
name = "test"
2025-05-13 16:55:28 +01:00
initial_upstreams = ["2001:4860:4860::8888"]
2024-09-20 11:30:27 -07:00
}
` ,
2025-05-13 16:55:28 +01:00
expWorkerUpstreams : [] string { "2001:4860:4860::8888" },
2024-09-20 11:30:27 -07:00
expErr : false ,
},
2021-12-07 13:01:04 +00:00
{
2022-06-13 15:06:03 -07:00
name : "Multiple Upstreams" ,
2021-12-07 13:01:04 +00:00
in : `
worker {
name = "test"
2022-06-13 15:06:03 -07:00
initial_upstreams = ["127.0.0.1", "127.0.0.2", "127.0.0.3"]
2021-12-07 13:01:04 +00:00
}
` ,
2022-06-13 15:06:03 -07:00
expWorkerUpstreams : [] string { "127.0.0.1" , "127.0.0.2" , "127.0.0.3" },
expErr : false ,
2021-12-07 13:01:04 +00:00
},
{
name : "Using env var" ,
in : `
worker {
name = "test"
2026-10-12 10:20:06 -07:00
initial_upstreams = "env://THRESHOLD_WORKER_UPSTREAMS"
2021-12-07 13:01:04 +00:00
}
` ,
2026-10-12 10:20:06 -07:00
stateFn : func ( t * testing . T ) { t . Setenv ( "THRESHOLD_WORKER_UPSTREAMS" , `["127.0.0.1", "127.0.0.2", "127.0.0.3"]` ) },
2022-06-13 15:06:03 -07:00
expWorkerUpstreams : [] string { "127.0.0.1" , "127.0.0.2" , "127.0.0.3" },
expErr : false ,
2021-12-07 13:01:04 +00:00
},
{
name : "Using env var - invalid input 1" ,
in : `
worker {
name = "test"
2026-10-12 10:20:06 -07:00
initial_upstreams = "env://THRESHOLD_WORKER_UPSTREAMS"
2021-12-07 13:01:04 +00:00
}
` ,
stateFn : func ( t * testing . T ) {
2022-06-13 15:06:03 -07:00
upstreams := `
2021-12-07 13:01:04 +00:00
worker {
2022-06-13 15:06:03 -07:00
initial_upstreams = ["127.0.0.1"]
2021-12-07 13:01:04 +00:00
}
`
2026-10-12 10:20:06 -07:00
t . Setenv ( "THRESHOLD_WORKER_UPSTREAMS" , upstreams )
2021-12-07 13:01:04 +00:00
},
2022-06-13 15:06:03 -07:00
expWorkerUpstreams : nil ,
expErr : true ,
expErrStr : "Failed to parse worker upstreams: failed to unmarshal env/file contents: invalid character 'w' looking for beginning of value" ,
2021-12-07 13:01:04 +00:00
},
{
name : "Using env var - invalid input 2" ,
in : `
worker {
name = "test"
2026-10-12 10:20:06 -07:00
initial_upstreams = "env://THRESHOLD_WORKER_UPSTREAMS"
2021-12-07 13:01:04 +00:00
}
` ,
2026-10-12 10:20:06 -07:00
stateFn : func ( t * testing . T ) { t . Setenv ( "THRESHOLD_WORKER_UPSTREAMS" , `initial_upstreams = ["127.0.0.1"]` ) },
2022-06-13 15:06:03 -07:00
expWorkerUpstreams : nil ,
expErr : true ,
expErrStr : "Failed to parse worker upstreams: failed to unmarshal env/file contents: invalid character 'i' looking for beginning of value" ,
2021-12-07 13:01:04 +00:00
},
{
2025-05-23 09:42:26 -07:00
name : "Duplicate field" ,
2021-12-07 13:01:04 +00:00
in : `
worker {
name = "test"
2022-06-13 15:06:03 -07:00
initial_upstreams = {
2021-12-07 13:01:04 +00:00
ip = "127.0.0.1"
ip = "127.0.0.2"
}
}
` ,
2022-06-13 15:06:03 -07:00
expWorkerUpstreams : nil ,
expErr : true ,
2025-05-23 09:42:26 -07:00
expErrStr : "The argument \"ip\" at 6:6 was already set. Each argument can only be defined once" ,
2022-06-13 15:06:03 -07:00
},
{
name : "Worker initial_upstreams set to invalid url" ,
in : `
worker {
name = "test"
initial_upstreams = "env://\x00"
}` ,
expWorkerUpstreams : nil ,
expErr : true ,
expErrIs : parseutil . ErrNotAUrl ,
},
2021-12-07 13:01:04 +00:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if tt . stateFn != nil {
tt . stateFn ( t )
}
c , err := Parse ( tt . in )
if tt . expErr {
if tt . expErrIs != nil {
require . ErrorIs ( t , err , tt . expErrIs )
} else {
require . EqualError ( t , err , tt . expErrStr )
}
require . Nil ( t , c )
return
}
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Worker )
2022-06-13 15:06:03 -07:00
require . EqualValues ( t , tt . expWorkerUpstreams , c . Worker . InitialUpstreams )
2021-12-07 13:01:04 +00:00
})
}
}
2021-12-07 11:28:12 -05:00
func TestControllerDescription ( t * testing . T ) {
tests := [] struct {
name string
in string
envDescription string
expDescription string
expErr bool
expErrStr string
}{
{
name : "Valid controller description from env var" ,
in : `
controller {
description = "env://CONTROLLER_DESCRIPTION"
}` ,
envDescription : "Test controller description" ,
expDescription : "Test controller description" ,
expErr : false ,
}, {
name : "Invalid controller description from env var" ,
in : `
controller {
description = "\uTest controller description"
}` ,
expErr : true ,
expErrStr : "At 3:22: illegal char escape" ,
2021-12-08 09:39:04 -05:00
}, {
2021-12-07 11:28:12 -05:00
name : "Not a URL, non-printable description" ,
in : `
controller {
description = "\x00"
}` ,
2021-12-08 09:39:04 -05:00
expErr : true ,
2021-12-07 11:28:12 -05:00
expErrStr : "Controller description contains non-printable characters" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
t . Setenv ( "CONTROLLER_DESCRIPTION" , tt . envDescription )
c , err := Parse ( tt . in )
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
require . Nil ( t , c )
return
}
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Controller )
require . Equal ( t , tt . expDescription , c . Controller . Description )
})
}
2021-12-08 09:39:04 -05:00
}
2023-10-02 21:02:17 +00:00
func TestControllerApiRateLimits ( t * testing . T ) {
tests := [] struct {
name string
in string
expLimits ratelimit . Configs
expErr bool
expErrStr string
}{
{
name : "Single Rate limit" ,
in : `
controller {
api_rate_limit {
resources = ["*"]
actions = ["*"]
per = "total"
limit = 50
period = "1m"
}
}` ,
expLimits : ratelimit . Configs {
{
Resources : [] string { "*" },
Actions : [] string { "*" },
Per : "total" ,
Limit : 50 ,
PeriodHCL : "1m" ,
Period : time . Minute ,
Unlimited : false ,
},
},
expErr : false ,
},
{
name : "Single Rate with name" ,
in : `
controller {
api_rate_limit "default" {
resources = ["*"]
actions = ["*"]
per = "total"
limit = 50
period = "1m"
}
}` ,
expLimits : ratelimit . Configs {
{
Resources : [] string { "*" },
Actions : [] string { "*" },
Per : "total" ,
Limit : 50 ,
PeriodHCL : "1m" ,
Period : time . Minute ,
Unlimited : false ,
},
},
expErr : false ,
},
{
name : "Multiple Rate limit" ,
in : `
controller {
api_rate_limit {
resources = ["*"]
actions = ["*"]
per = "total"
limit = 50
period = "1m"
}
api_rate_limit {
resources = ["*"]
actions = ["list"]
per = "total"
limit = 20
period = "1m"
}
}` ,
expLimits : ratelimit . Configs {
{
Resources : [] string { "*" },
Actions : [] string { "*" },
Per : "total" ,
Limit : 50 ,
PeriodHCL : "1m" ,
Period : time . Minute ,
Unlimited : false ,
},
{
Resources : [] string { "*" },
Actions : [] string { "list" },
Per : "total" ,
Limit : 20 ,
PeriodHCL : "1m" ,
Period : time . Minute ,
Unlimited : false ,
},
},
expErr : false ,
},
{
name : "Multiple Rate limit with names" ,
in : `
controller {
api_rate_limit "default" {
resources = ["*"]
actions = ["*"]
per = "total"
limit = 50
period = "1m"
}
api_rate_limit "default-list" {
resources = ["*"]
actions = ["list"]
per = "total"
limit = 20
period = "1m"
}
}` ,
expLimits : ratelimit . Configs {
{
Resources : [] string { "*" },
Actions : [] string { "*" },
Per : "total" ,
Limit : 50 ,
PeriodHCL : "1m" ,
Period : time . Minute ,
Unlimited : false ,
},
{
Resources : [] string { "*" },
Actions : [] string { "list" },
Per : "total" ,
Limit : 20 ,
PeriodHCL : "1m" ,
Period : time . Minute ,
Unlimited : false ,
},
},
expErr : false ,
},
{
name : "Multiple Rate limit with name and no name" ,
in : `
controller {
api_rate_limit {
resources = ["*"]
actions = ["*"]
per = "total"
limit = 50
period = "1m"
}
api_rate_limit "list" {
resources = ["*"]
actions = ["list"]
per = "total"
limit = 20
period = "1m"
}
}` ,
expLimits : ratelimit . Configs {
{
Resources : [] string { "*" },
Actions : [] string { "*" },
Per : "total" ,
Limit : 50 ,
PeriodHCL : "1m" ,
Period : time . Minute ,
Unlimited : false ,
},
{
Resources : [] string { "*" },
Actions : [] string { "list" },
Per : "total" ,
Limit : 20 ,
PeriodHCL : "1m" ,
Period : time . Minute ,
Unlimited : false ,
},
},
expErr : false ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
c , err := Parse ( tt . in )
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
require . Nil ( t , c )
return
}
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Controller )
require . Equal ( t , tt . expLimits , c . Controller . ApiRateLimits )
})
}
}
2021-12-10 18:16:20 +00:00
func TestWorkerDescription ( t * testing . T ) {
tests := [] struct {
name string
in string
envDescription string
expDescription string
expErr bool
expErrStr string
}{
{
name : "Valid worker description from env var" ,
in : `
worker {
description = "env://WORKER_DESCRIPTION"
}` ,
envDescription : "Test worker description" ,
expDescription : "Test worker description" ,
expErr : false ,
}, {
name : "Invalid worker description" ,
in : `
worker {
description = "\uTest worker description"
}` ,
expErr : true ,
expErrStr : "At 3:22: illegal char escape" ,
}, {
name : "Not a URL, non-printable description" ,
in : `
worker {
description = "\x00"
}` ,
expErr : true ,
expErrStr : "Worker description contains non-printable characters" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
t . Setenv ( "WORKER_DESCRIPTION" , tt . envDescription )
c , err := Parse ( tt . in )
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
require . Nil ( t , c )
return
}
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Worker )
require . Equal ( t , tt . expDescription , c . Worker . Description )
})
}
}
2021-12-08 09:39:04 -05:00
func TestPluginExecutionDir ( t * testing . T ) {
tests := [] struct {
name string
in string
envPluginExecutionDir string
expPluginExecutionDir string
expErr bool
expErrStr string
}{
{
name : "Valid plugin execution dir from env var" ,
in : `
plugins {
execution_dir = "env://PLUGIN_EXEC_DIR"
}` ,
envPluginExecutionDir : `/var/run/boundary/plugin-exec` ,
expPluginExecutionDir : `/var/run/boundary/plugin-exec` ,
expErr : false ,
}, {
name : "Invalid plugin execution dir from env var" ,
in : `
plugins {
execution_dir ="\ubad plugin directory"
}` ,
expErr : true ,
expErrStr : "At 3:28: illegal char escape" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
t . Setenv ( "PLUGIN_EXEC_DIR" , tt . envPluginExecutionDir )
p , err := Parse ( tt . in )
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
require . Nil ( t , p )
return
}
require . NoError ( t , err )
require . NotNil ( t , p )
require . NotNil ( t , p . Plugins )
require . Equal ( t , tt . expPluginExecutionDir , p . Plugins . ExecutionDir )
})
}
}
2021-12-08 15:44:13 -05:00
func TestDatabaseMaxConnections ( t * testing . T ) {
tests := [] struct {
name string
in string
envMaxOpenConnections string
expMaxOpenConnections int
expErr bool
expErrStr string
}{
{
name : "Valid integer value" ,
in : `
controller {
name = "example-controller"
database {
max_open_connections = 5
}
}` ,
expMaxOpenConnections : 5 ,
expErr : false ,
},
2022-07-05 15:25:55 -04:00
{
name : "Valid string value" ,
in : `
controller {
name = "example-controller"
database {
max_open_connections = "5"
}
}` ,
expMaxOpenConnections : 5 ,
expErr : false ,
},
2021-12-08 15:44:13 -05:00
{
name : "Invalid value string" ,
in : `
controller {
name = "example-controller"
database {
max_open_connections = "string bad"
}
}` ,
expErr : true ,
expErrStr : "Database max open connections value is not an int: " +
"strconv.Atoi: parsing \"string bad\": invalid syntax" ,
},
{
name : "Invalid value type" ,
in : `
controller {
name = "example-controller"
database {
max_open_connections = false
}
}` ,
expErr : true ,
expErrStr : "Database max open connections: unsupported type \"bool\"" ,
},
{
name : "Valid env var" ,
in : `
controller {
name = "example-controller"
database {
max_open_connections = "env://ENV_MAX_CONN"
}
}` ,
expMaxOpenConnections : 8 ,
envMaxOpenConnections : "8" ,
expErr : false ,
},
{
name : "Invalid env var" ,
in : `
controller {
name = "example-controller"
database {
max_open_connections = "env://ENV_MAX_CONN"
}
}` ,
envMaxOpenConnections : "bogus value" ,
expErr : true ,
expErrStr : "Database max open connections value is not an int: " +
"strconv.Atoi: parsing \"bogus value\": invalid syntax" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
t . Setenv ( "ENV_MAX_CONN" , tt . envMaxOpenConnections )
c , err := Parse ( tt . in )
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
require . Nil ( t , c )
return
}
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Controller )
require . NotNil ( t , c . Controller . Database )
require . Equal ( t , tt . expMaxOpenConnections , c . Controller . Database . MaxOpenConnections )
})
}
}
2022-07-05 15:25:55 -04:00
func TestDatabaseMaxIdleConnections ( t * testing . T ) {
tests := [] struct {
name string
in string
envMaxIdleConnections string
expMaxIdleConnections int
expErr bool
expErrStr string
}{
{
name : "Valid integer value" ,
in : `
controller {
name = "example-controller"
database {
max_idle_connections = 5
}
}` ,
expMaxIdleConnections : 5 ,
expErr : false ,
},
{
name : "Valid integer string" ,
in : `
controller {
name = "example-controller"
database {
max_idle_connections = "5"
}
}` ,
expMaxIdleConnections : 5 ,
expErr : false ,
},
{
name : "Invalid value string" ,
in : `
controller {
name = "example-controller"
database {
max_idle_connections = "string bad"
}
}` ,
expErr : true ,
expErrStr : "Database max idle connections value is not a uint: " +
"strconv.Atoi: parsing \"string bad\": invalid syntax" ,
},
{
name : "Invalid value type" ,
in : `
controller {
name = "example-controller"
database {
max_idle_connections = false
}
}` ,
expErr : true ,
expErrStr : "Database max idle connections: unsupported type \"bool\"" ,
},
{
name : "Valid env var" ,
in : `
controller {
name = "example-controller"
database {
max_idle_connections = "env://ENV_MAX_IDLE_CONN"
}
}` ,
expMaxIdleConnections : 8 ,
envMaxIdleConnections : "8" ,
expErr : false ,
},
{
name : "Invalid env var" ,
in : `
controller {
name = "example-controller"
database {
max_idle_connections = "env://ENV_MAX_IDLE_CONN"
}
}` ,
envMaxIdleConnections : "bogus value" ,
expErr : true ,
expErrStr : "Database max idle connections value is not a uint: " +
"strconv.Atoi: parsing \"bogus value\": invalid syntax" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
t . Setenv ( "ENV_MAX_IDLE_CONN" , tt . envMaxIdleConnections )
c , err := Parse ( tt . in )
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
require . Nil ( t , c )
return
}
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Controller )
require . NotNil ( t , c . Controller . Database )
require . Equal ( t , tt . expMaxIdleConnections , * c . Controller . Database . MaxIdleConnections )
})
}
}
func TestDatabaseConnMaxIdleTimeDuration ( t * testing . T ) {
tests := [] struct {
name string
in string
envConnMaxIdleTimeDuration string
expConnMaxIdleTimeDuration time . Duration
expErr bool
expErrStr string
}{
{
name : "Valid duration value" ,
in : `
controller {
name = "example-controller"
database {
max_idle_time = "5m"
}
}` ,
expConnMaxIdleTimeDuration : time . Minute * 5 ,
expErr : false ,
},
{
name : "Valid env var value" ,
envConnMaxIdleTimeDuration : "5m" ,
in : `
controller {
name = "example-controller"
database {
max_idle_time = "env://ENV_CONN_MAX_IDLE_TIME"
}
}` ,
expConnMaxIdleTimeDuration : time . Minute * 5 ,
expErr : false ,
},
{
name : "Invalid value string" ,
in : `
controller {
name = "example-controller"
database {
max_idle_time = "string bad"
}
}` ,
expErr : true ,
expErrStr : "Connection max idle time is not a duration: " +
"strconv.ParseInt: parsing \"string ba\": invalid syntax" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
t . Setenv ( "ENV_CONN_MAX_IDLE_TIME" , tt . envConnMaxIdleTimeDuration )
c , err := Parse ( tt . in )
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
require . Nil ( t , c )
return
}
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Controller )
require . NotNil ( t , c . Controller . Database )
require . Equal ( t , tt . expConnMaxIdleTimeDuration , * c . Controller . Database . ConnMaxIdleTimeDuration )
})
}
}
2022-09-07 16:32:18 -04:00
func TestDatabaseSkipSharedLockAcquisition ( t * testing . T ) {
tests := [] struct {
name string
in string
expSkipSharedLockAcquisition bool
}{
{
name : "not set" ,
in : `
controller {
name = "example-controller"
database {
}
}` ,
expSkipSharedLockAcquisition : false ,
},
{
name : "set" ,
in : `
controller {
name = "example-controller"
database {
skip_shared_lock_acquisition = true
}
}` ,
expSkipSharedLockAcquisition : true ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
c , err := Parse ( tt . in )
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Controller )
require . NotNil ( t , c . Controller . Database )
2022-09-07 16:44:23 -04:00
require . Equal ( t , tt . expSkipSharedLockAcquisition , c . Controller . Database . SkipSharedLockAcquisition )
2022-09-07 16:32:18 -04:00
})
}
}
2022-09-12 09:37:13 -04:00
func TestSetupControllerPublicClusterAddress ( t * testing . T ) {
tests := [] struct {
name string
inputConfig * Config
inputFlagValue string
stateFn func ( t * testing . T )
expErr bool
expErrStr string
expPublicClusterAddress string
}{
{
name : "nil controller" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : nil ,
},
inputFlagValue : "" ,
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : ":9201" ,
},
{
2024-09-20 11:30:27 -07:00
name : "setting public cluster address directly with ipv4" ,
2022-09-12 09:37:13 -04:00
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : "127.0.0.1" ,
},
},
inputFlagValue : "" ,
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:9201" ,
},
{
2024-09-20 11:30:27 -07:00
name : "setting public cluster address directly with ipv4:port" ,
2022-09-12 09:37:13 -04:00
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : "127.0.0.1:8080" ,
},
},
inputFlagValue : "" ,
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:8080" ,
},
2024-09-20 11:30:27 -07:00
{
name : "setting public cluster address directly with ipv6" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
2025-05-13 16:55:28 +01:00
PublicClusterAddr : "2001:4860:4860:0:0:0:0:8888" ,
2024-09-20 11:30:27 -07:00
},
},
inputFlagValue : "" ,
expErr : false ,
expErrStr : "" ,
2025-05-13 16:55:28 +01:00
expPublicClusterAddress : "[2001:4860:4860::8888]:9201" ,
2024-09-20 11:30:27 -07:00
},
{
name : "setting public cluster address directly with ipv6:port" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : "[2001:4860:4860:0:0:0:0:8888]:8080" ,
},
},
inputFlagValue : "" ,
expErr : false ,
expErrStr : "" ,
2025-05-13 16:55:28 +01:00
expPublicClusterAddress : "[2001:4860:4860::8888]:8080" ,
2024-09-20 11:30:27 -07:00
},
{
name : "setting public cluster address directly with abbreviated ipv6" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
2025-05-13 16:55:28 +01:00
PublicClusterAddr : "2001:4860:4860::8888" ,
2024-09-20 11:30:27 -07:00
},
},
inputFlagValue : "" ,
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "[2001:4860:4860::8888]:9201" ,
},
{
name : "setting public cluster address directly with abbreviated ipv6:port" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : "[2001:4860:4860::8888]:8080" ,
},
},
inputFlagValue : "" ,
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "[2001:4860:4860::8888]:8080" ,
},
2022-09-12 09:37:13 -04:00
{
name : "setting public cluster address to env var" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : "env://TEST_ENV_VAR_FOR_CONTROLLER_ADDR" ,
},
},
inputFlagValue : "" ,
stateFn : func ( t * testing . T ) {
t . Setenv ( "TEST_ENV_VAR_FOR_CONTROLLER_ADDR" , "127.0.0.1:8080" )
},
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:8080" ,
},
{
name : "setting public cluster address to env var that points to template" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : "env://TEST_ENV_VAR_FOR_CONTROLLER_ADDR" ,
},
},
inputFlagValue : "" ,
stateFn : func ( t * testing . T ) {
t . Setenv ( "TEST_ENV_VAR_FOR_CONTROLLER_ADDR" , ` {{ GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" | join "address" " " }} ` )
},
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:9201" ,
},
{
name : "setting public cluster address to ip template" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : ` {{ GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" | join "address" " " }} ` ,
},
},
inputFlagValue : "" ,
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:9201" ,
},
{
name : "setting public cluster address to multiline ip template" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : ` {{ with $local := GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" -}}
{{- $local | join "address" " " -}}
{{- end }} ` ,
},
},
inputFlagValue : "" ,
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:9201" ,
},
{
name : "using flag value with ip only" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {},
},
inputFlagValue : "127.0.0.1" ,
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:9201" ,
},
{
name : "using flag value with ip:port" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {},
},
inputFlagValue : "127.0.0.1:8080" ,
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:8080" ,
},
{
name : "using flag value with ip template" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {},
},
inputFlagValue : ` {{ GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" | join "address" " " }} ` ,
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:9201" ,
},
{
name : "using flag value with multiline ip template" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {},
},
inputFlagValue : ` {{ with $local := GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" -}}
{{- $local | join "address" " " -}}
{{- end }} ` ,
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:9201" ,
},
{
name : "using flag value to point to env var with ip only" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {},
},
inputFlagValue : "env://TEST_ENV_VAR_FOR_CONTROLLER_ADDR" ,
stateFn : func ( t * testing . T ) {
t . Setenv ( "TEST_ENV_VAR_FOR_CONTROLLER_ADDR" , "127.0.0.1" )
},
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:9201" ,
},
{
name : "using flag value to point to env var with ip:port" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {},
},
inputFlagValue : "env://TEST_ENV_VAR_FOR_CONTROLLER_ADDR" ,
stateFn : func ( t * testing . T ) {
t . Setenv ( "TEST_ENV_VAR_FOR_CONTROLLER_ADDR" , "127.0.0.1:8080" )
},
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:8080" ,
},
{
2024-09-20 11:30:27 -07:00
name : "read address from listeners ipv4 only" ,
2022-09-12 09:37:13 -04:00
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{ Purpose : [] string { "cluster" }, Address : "127.0.0.1" },
},
},
Controller : & Controller {},
},
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:9201" ,
},
{
2024-09-20 11:30:27 -07:00
name : "read address from listeners ipv4:port" ,
2022-09-12 09:37:13 -04:00
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{ Purpose : [] string { "cluster" }, Address : "127.0.0.1:8080" },
},
},
Controller : & Controller {},
},
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "127.0.0.1:8080" ,
},
2024-09-20 11:30:27 -07:00
{
name : "read address from listeners ipv6 only" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
2025-05-13 16:55:28 +01:00
{ Purpose : [] string { "cluster" }, Address : "2001:4860:4860:0:0:0:0:8888" },
2024-09-20 11:30:27 -07:00
},
},
Controller : & Controller {},
},
expErr : false ,
expErrStr : "" ,
2025-05-13 16:55:28 +01:00
expPublicClusterAddress : "[2001:4860:4860::8888]:9201" ,
2024-09-20 11:30:27 -07:00
},
{
name : "read address from listeners ipv6:port" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
2025-05-13 16:55:28 +01:00
{ Purpose : [] string { "cluster" }, Address : "[2001:4860:4860::8888]:8080" },
2024-09-20 11:30:27 -07:00
},
},
Controller : & Controller {},
},
expErr : false ,
expErrStr : "" ,
2025-05-13 16:55:28 +01:00
expPublicClusterAddress : "[2001:4860:4860::8888]:8080" ,
2024-09-20 11:30:27 -07:00
},
{
name : "read address from listeners abbreviated ipv6 only" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
2025-05-13 16:55:28 +01:00
{ Purpose : [] string { "cluster" }, Address : "2001:4860:4860::8888" },
2024-09-20 11:30:27 -07:00
},
},
Controller : & Controller {},
},
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "[2001:4860:4860::8888]:9201" ,
},
{
name : "read address from listeners abbreviated ipv6:port" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{ Purpose : [] string { "cluster" }, Address : "[2001:4860:4860::8888]:8080" },
},
},
Controller : & Controller {},
},
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : "[2001:4860:4860::8888]:8080" ,
},
2022-09-12 09:37:13 -04:00
{
name : "read address from listeners is ignored on different purpose" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{ Purpose : [] string { "somethingelse" }, Address : "127.0.0.1:8080" },
},
},
Controller : & Controller {},
},
expErr : false ,
expErrStr : "" ,
expPublicClusterAddress : ":9201" ,
},
{
name : "using flag value to point to nonexistent file" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {},
},
inputFlagValue : "file://this_doesnt_exist_for_sure" ,
expErr : true ,
expErrStr : "Error parsing public cluster addr: error reading file at file://this_doesnt_exist_for_sure: open this_doesnt_exist_for_sure: no such file or directory" ,
expPublicClusterAddress : "" ,
},
{
name : "using flag value to provoke error in SplitHostPort" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {},
},
2025-05-13 16:55:28 +01:00
inputFlagValue : "abc::123:::" ,
2022-09-12 09:37:13 -04:00
expErr : true ,
2025-05-13 16:55:28 +01:00
expErrStr : "Error splitting public cluster adddress host/port: too many colons in address" ,
2022-09-12 09:37:13 -04:00
expPublicClusterAddress : "" ,
},
{
name : "bad ip template" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {},
},
inputFlagValue : "{{ somethingthatdoesntexist }}" ,
expErr : true ,
expErrStr : "Error parsing IP template on controller public cluster addr: unable to parse address template \"{{ somethingthatdoesntexist }}\": unable to parse template \"{{ somethingthatdoesntexist }}\": template: sockaddr.Parse:1: function \"somethingthatdoesntexist\" not defined" ,
expPublicClusterAddress : "" ,
},
2025-05-13 16:55:28 +01:00
{
name : "unix listener" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{ Address : "someaddr" , Type : "unix" , Purpose : [] string { "cluster" }},
},
},
Controller : & Controller {},
},
expPublicClusterAddress : "someaddr:9201" ,
},
2022-09-12 09:37:13 -04:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if tt . stateFn != nil {
tt . stateFn ( t )
}
err := tt . inputConfig . SetupControllerPublicClusterAddress ( tt . inputFlagValue )
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
return
}
require . NoError ( t , err )
require . NotNil ( t , tt . inputConfig . Controller )
require . Equal ( t , tt . expPublicClusterAddress , tt . inputConfig . Controller . PublicClusterAddr )
})
}
}
func TestSetupWorkerInitialUpstreams ( t * testing . T ) {
t . Parallel ()
tests := [] struct {
name string
inputConfig * Config
stateFn func ( t * testing . T )
expErr bool
expErrStr string
expInitialUpstreams [] string
}{
{
name : "NilController" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : nil ,
Worker : & Worker {
InitialUpstreams : [] string { "192.168.0.2:9201" },
},
},
expErr : false ,
expErrStr : "" ,
expInitialUpstreams : [] string { "192.168.0.2:9201" },
},
{
name : "NilWorker" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : "192.168.0.3:9201" ,
},
Worker : nil ,
},
expErr : false ,
expErrStr : "" ,
expInitialUpstreams : nil ,
},
{
2024-09-20 11:30:27 -07:00
name : "ipv4 PublicClusterAddr" ,
2022-09-12 09:37:13 -04:00
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : "192.168.0.4:9201" ,
},
Worker : & Worker {},
},
expErr : false ,
expErrStr : "" ,
expInitialUpstreams : [] string { "192.168.0.4:9201" },
},
2024-09-20 11:30:27 -07:00
{
name : "ipv6 PublicClusterAddr" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : "[2001:4860:4860:0:0:0:0:8888]:9201" ,
},
Worker : & Worker {},
},
expErr : false ,
expErrStr : "" ,
expInitialUpstreams : [] string { "[2001:4860:4860:0:0:0:0:8888]:9201" },
},
{
name : "abbreviated ipv6 PublicClusterAddr" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : "[2001:4860:4860::8888]:9201" ,
},
Worker : & Worker {},
},
expErr : false ,
expErrStr : "" ,
expInitialUpstreams : [] string { "[2001:4860:4860::8888]:9201" },
},
2022-09-12 09:37:13 -04:00
{
name : "ListenerNoAddr" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{
Purpose : [] string { "cluster" },
},
},
},
Controller : & Controller {},
Worker : & Worker {},
},
expErr : false ,
expErrStr : "" ,
expInitialUpstreams : [] string { "127.0.0.1:9201" },
},
{
2024-09-20 11:30:27 -07:00
name : "ipv4 ListenerAddr" ,
2022-09-12 09:37:13 -04:00
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{
Purpose : [] string { "cluster" },
Address : "192.168.0.5:9201" ,
},
},
},
Controller : & Controller {},
Worker : & Worker {},
},
expErr : false ,
expErrStr : "" ,
expInitialUpstreams : [] string { "192.168.0.5:9201" },
},
2024-09-20 11:30:27 -07:00
{
name : "ipv6 ListenerAddr" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{
Purpose : [] string { "cluster" },
Address : "[2001:4860:4860:0:0:0:0:8888]:9201" ,
},
},
},
Controller : & Controller {},
Worker : & Worker {},
},
expErr : false ,
expErrStr : "" ,
expInitialUpstreams : [] string { "[2001:4860:4860:0:0:0:0:8888]:9201" },
},
{
name : "abbreviated ipv6 ListenerAddr" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{
Purpose : [] string { "cluster" },
Address : "[2001:4860:4860::8888]:9201" ,
},
},
},
Controller : & Controller {},
Worker : & Worker {},
},
expErr : false ,
expErrStr : "" ,
expInitialUpstreams : [] string { "[2001:4860:4860::8888]:9201" },
},
2022-09-12 09:37:13 -04:00
{
name : "ListenerAddrDomain" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{
Purpose : [] string { "cluster" },
Address : "foo.test" ,
},
},
},
Controller : & Controller {},
Worker : & Worker {},
},
expErr : false ,
expErrStr : "" ,
expInitialUpstreams : [] string { "foo.test" },
},
{
name : "ListenerAddrMultiplePurpose" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{
Purpose : [] string { "cluster" , "api" },
},
},
},
Controller : & Controller {},
Worker : & Worker {},
},
expErr : true ,
expErrStr : "Specifying a listener with more than one purpose is not supported" ,
expInitialUpstreams : nil ,
},
{
name : "ListenerAddrNoPurposes" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{
Purpose : [] string {},
},
},
},
Controller : & Controller {},
Worker : & Worker {},
},
expErr : true ,
expErrStr : "Listener specified without a purpose" ,
expInitialUpstreams : nil ,
},
{
name : "ListenerAddrMismatchAddress" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {
{
Purpose : [] string { "cluster" },
Address : "192.168.0.5:9201" ,
},
},
},
Controller : & Controller {},
Worker : & Worker {
InitialUpstreams : [] string { "192.168.0.2:9201" },
},
},
expErr : true ,
expErrStr : `When running a combined controller and worker, it's invalid to specify a "initial_upstreams" or "controllers" key in the worker block with any values other than the controller cluster or upstream worker address/port when using IPs rather than DNS names` ,
expInitialUpstreams : nil ,
},
{
name : "ClusterAddrMismatchAddress" ,
inputConfig : & Config {
SharedConfig : & configutil . SharedConfig {
Listeners : [] * listenerutil . ListenerConfig {},
},
Controller : & Controller {
PublicClusterAddr : "192.168.0.3:9201" ,
},
Worker : & Worker {
InitialUpstreams : [] string { "192.168.0.2:9201" },
},
},
expErr : true ,
expErrStr : `When running a combined controller and worker, it's invalid to specify a "initial_upstreams" or "controllers" key in the worker block with any values other than the controller cluster or upstream worker address/port when using IPs rather than DNS names` ,
expInitialUpstreams : nil ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if tt . stateFn != nil {
tt . stateFn ( t )
}
err := tt . inputConfig . SetupWorkerInitialUpstreams ()
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
return
}
require . NoError ( t , err )
if tt . inputConfig . Worker == nil {
require . Empty ( t , tt . expInitialUpstreams )
} else {
require . ElementsMatch ( t , tt . expInitialUpstreams , tt . inputConfig . Worker . InitialUpstreams )
}
})
}
}
2023-10-23 16:35:35 -07:00
2024-08-13 14:27:39 -07:00
func TestGetDownstreamWorkersTimeout ( t * testing . T ) {
tests := [] struct {
name string
in string
wantController bool
wantWorker bool
wantControllerTimeout time . Duration
wantWorkerTimeout time . Duration
assertErr func ( * testing . T , error )
}{
{
name : "controller_valid_time_value" ,
in : `
controller {
name = "example-controller"
get_downstream_workers_timeout = "10s"
}` ,
wantControllerTimeout : 10 * time . Second ,
wantWorkerTimeout : 0 ,
wantController : true ,
wantWorker : false ,
assertErr : nil ,
},
{
name : "worker_valid_time_value" ,
in : `
worker {
name = "example-worker"
get_downstream_workers_timeout = "5s"
}` ,
wantControllerTimeout : 0 ,
wantWorkerTimeout : 5 * time . Second ,
wantController : false ,
wantWorker : true ,
assertErr : nil ,
},
{
name : "both_valid_time_value" ,
in : `
controller {
name = "example-controller"
get_downstream_workers_timeout = "5s"
}
worker {
name = "example-worker"
get_downstream_workers_timeout = "500ms"
}` ,
wantControllerTimeout : 5 * time . Second ,
wantWorkerTimeout : 500 * time . Millisecond ,
wantController : true ,
wantWorker : true ,
assertErr : nil ,
},
{
name : "both_unspecified_defaults_to_zero" ,
in : `
controller {
name = "example-controller"
}
worker {
name = "example-worker"
}` ,
wantController : true ,
wantWorker : true ,
wantControllerTimeout : 0 ,
wantWorkerTimeout : 0 ,
assertErr : nil ,
},
{
name : "controller_int_value_no_unit_assumes_seconds" ,
in : `
controller {
name = "example-controller"
get_downstream_workers_timeout = 100
}` ,
wantController : true ,
wantWorker : false ,
wantControllerTimeout : 100 * time . Second ,
wantWorkerTimeout : 0 ,
},
{
name : "worker_int_value_no_unit_assumes_seconds" ,
in : `
worker {
name = "example-worker"
get_downstream_workers_timeout = 30
}` ,
wantController : false ,
wantWorker : true ,
wantWorkerTimeout : 30 * time . Second ,
},
{
name : "controller_invalid_bool_value" ,
in : `
controller {
name = "example-controller"
get_downstream_workers_timeout = true
}` ,
wantController : true ,
wantWorker : false ,
assertErr : func ( t * testing . T , err error ) {
require . Error ( t , err )
require . ErrorContains ( t , err , `error trying to parse controller get_downstream_workers_timeout` )
},
},
{
name : "worker_invalid_bool_value" ,
in : `
worker {
name = "example-worker"
get_downstream_workers_timeout = false
}` ,
wantController : false ,
wantWorker : true ,
assertErr : func ( t * testing . T , err error ) {
require . Error ( t , err )
require . ErrorContains ( t , err , `error trying to parse worker get_downstream_workers_timeout` )
},
},
{
name : "controller_invalid_empty_value" ,
in : `
controller {
name = "example-controller"
get_downstream_workers_timeout = ""
}` ,
wantController : true ,
wantWorker : false ,
wantControllerTimeout : 0 ,
},
{
name : "worker_invalid_empty_value" ,
in : `
worker {
name = "example-worker"
get_downstream_workers_timeout = ""
}` ,
wantController : false ,
wantWorker : true ,
wantWorkerTimeout : 0 ,
},
{
name : "controller_invalid_zero_value" ,
in : `
controller {
name = "example-controller"
get_downstream_workers_timeout = "0s"
}` ,
wantController : true ,
wantWorker : false ,
wantControllerTimeout : 0 ,
},
{
name : "worker_invalid_zero_value" ,
in : `
worker {
name = "example-worker"
get_downstream_workers_timeout = "0s"
}` ,
wantController : false ,
wantWorker : true ,
wantWorkerTimeout : 0 ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
c , err := Parse ( tt . in )
if tt . assertErr != nil {
tt . assertErr ( t , err )
return
}
require . NoError ( t , err )
if tt . wantController {
require . NotNil ( t , c . Controller )
require . Equal ( t , tt . wantControllerTimeout , c . Controller . GetDownstreamWorkersTimeoutDuration )
}
if tt . wantWorker {
require . NotNil ( t , c . Worker )
require . Equal ( t , tt . wantWorkerTimeout , c . Worker . GetDownstreamWorkersTimeoutDuration )
}
})
}
}
2023-10-23 16:35:35 -07:00
func TestMaxPageSize ( t * testing . T ) {
tests := [] struct {
name string
in string
envMaxPageSize string
expMaxPageSize uint
expErr bool
expErrStr string
}{
{
name : "Valid integer value" ,
in : `
controller {
name = "example-controller"
max_page_size = 5
}` ,
expMaxPageSize : 5 ,
expErr : false ,
},
{
name : "Valid string value" ,
in : `
controller {
name = "example-controller"
max_page_size = "5"
}` ,
expMaxPageSize : 5 ,
expErr : false ,
},
{
name : "Invalid value integer" ,
in : `
controller {
name = "example-controller"
max_page_size = 0
}` ,
expErr : true ,
expErrStr : "Max page size value must be at least 1, was 0" ,
},
{
name : "Invalid value string" ,
in : `
controller {
name = "example-controller"
max_page_size = "string bad"
}` ,
expErr : true ,
expErrStr : "Max page size value is not an int: " +
"strconv.Atoi: parsing \"string bad\": invalid syntax" ,
},
{
name : "Invalid value type" ,
in : `
controller {
name = "example-controller"
max_page_size = false
}` ,
expErr : true ,
expErrStr : "Max page size: unsupported type \"bool\"" ,
},
{
name : "Valid env var" ,
in : `
controller {
name = "example-controller"
2025-01-22 13:31:42 -08:00
max_page_size = "env://ENV_MAX_PAGE_SIZE"
2023-10-23 16:35:35 -07:00
}` ,
expMaxPageSize : 8 ,
envMaxPageSize : "8" ,
expErr : false ,
},
{
name : "Invalid env var" ,
in : `
controller {
name = "example-controller"
2025-01-22 13:31:42 -08:00
max_page_size = "env://ENV_MAX_PAGE_SIZE"
2023-10-23 16:35:35 -07:00
}` ,
envMaxPageSize : "bogus value" ,
expErr : true ,
expErrStr : "Max page size value is not an int: " +
"strconv.Atoi: parsing \"bogus value\": invalid syntax" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2025-01-22 13:31:42 -08:00
t . Setenv ( "ENV_MAX_PAGE_SIZE" , tt . envMaxPageSize )
2023-10-23 16:35:35 -07:00
c , err := Parse ( tt . in )
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
require . Nil ( t , c )
return
}
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Controller )
require . Equal ( t , tt . expMaxPageSize , c . Controller . MaxPageSize )
})
}
}
2025-01-22 13:31:42 -08:00
func TestConcurrentPasswordHashWorkers ( t * testing . T ) {
tests := [] struct {
name string
in string
envConcurrentPasswordHashWorkers string
expConcurrentPasswordHashWorkers uint
expErr bool
expErrStr string
}{
{
name : "Valid integer value" ,
in : `
controller {
name = "example-controller"
concurrent_password_hash_workers = 5
}` ,
expConcurrentPasswordHashWorkers : 5 ,
expErr : false ,
},
{
name : "Valid string value" ,
in : `
controller {
name = "example-controller"
concurrent_password_hash_workers = "5"
}` ,
expConcurrentPasswordHashWorkers : 5 ,
expErr : false ,
},
{
name : "Invalid value integer" ,
in : `
controller {
name = "example-controller"
concurrent_password_hash_workers = 0
}` ,
expErr : true ,
expErrStr : "Concurrent password hash workers value must be at least 1, was 0" ,
},
{
name : "Invalid value string" ,
in : `
controller {
name = "example-controller"
concurrent_password_hash_workers = "string bad"
}` ,
expErr : true ,
expErrStr : "Concurrent password hash workers value is not an int: " +
"strconv.Atoi: parsing \"string bad\": invalid syntax" ,
},
{
name : "Invalid value string integer" ,
in : `
controller {
name = "example-controller"
concurrent_password_hash_workers = "-1"
}` ,
expErr : true ,
expErrStr : "Concurrent password hash workers value must be at least 1, was -1" ,
},
{
name : "Invalid value type" ,
in : `
controller {
name = "example-controller"
concurrent_password_hash_workers = false
}` ,
expErr : true ,
expErrStr : "Concurrent password hash workers: unsupported type \"bool\"" ,
},
{
name : "Valid env var" ,
in : `
controller {
name = "example-controller"
concurrent_password_hash_workers = "env://ENV_MAX_PW_WORKERS"
}` ,
expConcurrentPasswordHashWorkers : 8 ,
envConcurrentPasswordHashWorkers : "8" ,
expErr : false ,
},
{
name : "Invalid env var" ,
in : `
controller {
name = "example-controller"
concurrent_password_hash_workers = "env://ENV_MAX_PW_WORKERS"
}` ,
envConcurrentPasswordHashWorkers : "bogus value" ,
expErr : true ,
expErrStr : "Concurrent password hash workers value is not an int: " +
"strconv.Atoi: parsing \"bogus value\": invalid syntax" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
t . Setenv ( "ENV_MAX_PW_WORKERS" , tt . envConcurrentPasswordHashWorkers )
c , err := Parse ( tt . in )
if tt . expErr {
require . EqualError ( t , err , tt . expErrStr )
require . Nil ( t , c )
return
}
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Controller )
require . Equal ( t , tt . expConcurrentPasswordHashWorkers , c . Controller . ConcurrentPasswordHashWorkers )
})
}
t . Run ( "using environment variable" , func ( t * testing . T ) {
2026-10-12 10:20:06 -07:00
t . Setenv ( "THRESHOLD_CONTROLLER_CONCURRENT_PASSWORD_HASH_WORKERS" , "2" )
2025-01-22 13:31:42 -08:00
in := `
controller {
name = "example-controller"
}`
c , err := Parse ( in )
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Controller )
require . EqualValues ( t , 2 , c . Controller . ConcurrentPasswordHashWorkers )
})
t . Run ( "using environment variable with invalid value" , func ( t * testing . T ) {
2026-10-12 10:20:06 -07:00
t . Setenv ( "THRESHOLD_CONTROLLER_CONCURRENT_PASSWORD_HASH_WORKERS" , "invalid" )
2025-01-22 13:31:42 -08:00
in := `
controller {
name = "example-controller"
}`
c , err := Parse ( in )
require . Error ( t , err )
require . Nil ( t , c )
2026-10-12 10:20:06 -07:00
require . Contains ( t , err . Error (), "THRESHOLD_CONTROLLER_CONCURRENT_PASSWORD_HASH_WORKERS value is not an int" )
2025-01-22 13:31:42 -08:00
})
t . Run ( "using environment variable and config value uses config value" , func ( t * testing . T ) {
2026-10-12 10:20:06 -07:00
t . Setenv ( "THRESHOLD_CONTROLLER_CONCURRENT_PASSWORD_HASH_WORKERS" , "2" )
2025-01-22 13:31:42 -08:00
in := `
controller {
name = "example-controller"
concurrent_password_hash_workers = 3
}`
c , err := Parse ( in )
require . NoError ( t , err )
require . NotNil ( t , c )
require . NotNil ( t , c . Controller )
require . EqualValues ( t , 3 , c . Controller . ConcurrentPasswordHashWorkers )
})
}