15f8e6323e
is filled, unless SNI is delibaretely disabled. Also, do not set SNI when host is an IP address as per RFC 6066.
1147 lines
32 KiB
Go
1147 lines
32 KiB
Go
package pgconn_test
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/user"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgconn"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestParseConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var osUserName string
|
|
osUser, err := user.Current()
|
|
if err == nil {
|
|
// Windows gives us the username here as `DOMAIN\user` or `LOCALPCNAME\user`,
|
|
// but the libpq default is just the `user` portion, so we strip off the first part.
|
|
if runtime.GOOS == "windows" && strings.Contains(osUser.Username, "\\") {
|
|
osUserName = osUser.Username[strings.LastIndex(osUser.Username, "\\")+1:]
|
|
} else {
|
|
osUserName = osUser.Username
|
|
}
|
|
}
|
|
|
|
config, err := pgconn.ParseConfig("")
|
|
require.NoError(t, err)
|
|
defaultHost := config.Host
|
|
|
|
tests := []struct {
|
|
name string
|
|
connString string
|
|
config *pgconn.Config
|
|
}{
|
|
// Test all sslmodes
|
|
{
|
|
name: "sslmode not set (prefer)",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: "localhost",
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
Fallbacks: []*pgconn.FallbackConfig{
|
|
&pgconn.FallbackConfig{
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
TLSConfig: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "sslmode disable",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "sslmode allow",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=allow",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
Fallbacks: []*pgconn.FallbackConfig{
|
|
&pgconn.FallbackConfig{
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: "localhost",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "sslmode prefer",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=prefer",
|
|
config: &pgconn.Config{
|
|
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: "localhost",
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
Fallbacks: []*pgconn.FallbackConfig{
|
|
&pgconn.FallbackConfig{
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
TLSConfig: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "sslmode require",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=require",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: "localhost",
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "sslmode verify-ca",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=verify-ca",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: "localhost",
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "sslmode verify-full",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=verify-full",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: &tls.Config{ServerName: "localhost"},
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "database url everything",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&application_name=pgxtest&search_path=myschema&connect_timeout=5",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
ConnectTimeout: 5 * time.Second,
|
|
RuntimeParams: map[string]string{
|
|
"application_name": "pgxtest",
|
|
"search_path": "myschema",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "database url missing password",
|
|
connString: "postgres://jack@localhost:5432/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "database url missing user and password",
|
|
connString: "postgres://localhost:5432/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: osUserName,
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "database url missing port",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "database url unix domain socket host",
|
|
connString: "postgres:///foo?host=/tmp",
|
|
config: &pgconn.Config{
|
|
User: osUserName,
|
|
Host: "/tmp",
|
|
Port: 5432,
|
|
Database: "foo",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "database url unix domain socket host on windows",
|
|
connString: "postgres:///foo?host=C:\\tmp",
|
|
config: &pgconn.Config{
|
|
User: osUserName,
|
|
Host: "C:\\tmp",
|
|
Port: 5432,
|
|
Database: "foo",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "database url dbname",
|
|
connString: "postgres://localhost/?dbname=foo&sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: osUserName,
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "foo",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "database url postgresql protocol",
|
|
connString: "postgresql://jack@localhost:5432/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "database url IPv4 with port",
|
|
connString: "postgresql://jack@127.0.0.1:5433/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Host: "127.0.0.1",
|
|
Port: 5433,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "database url IPv6 with port",
|
|
connString: "postgresql://jack@[2001:db8::1]:5433/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Host: "2001:db8::1",
|
|
Port: 5433,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "database url IPv6 no port",
|
|
connString: "postgresql://jack@[2001:db8::1]/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Host: "2001:db8::1",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "DSN everything",
|
|
connString: "user=jack password=secret host=localhost port=5432 dbname=mydb sslmode=disable application_name=pgxtest search_path=myschema connect_timeout=5",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
ConnectTimeout: 5 * time.Second,
|
|
RuntimeParams: map[string]string{
|
|
"application_name": "pgxtest",
|
|
"search_path": "myschema",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "DSN with escaped single quote",
|
|
connString: "user=jack\\'s password=secret host=localhost port=5432 dbname=mydb sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack's",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "DSN with escaped backslash",
|
|
connString: "user=jack password=sooper\\\\secret host=localhost port=5432 dbname=mydb sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "sooper\\secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "DSN with single quoted values",
|
|
connString: "user='jack' host='localhost' dbname='mydb' sslmode='disable'",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "DSN with single quoted value with escaped single quote",
|
|
connString: "user='jack\\'s' host='localhost' dbname='mydb' sslmode='disable'",
|
|
config: &pgconn.Config{
|
|
User: "jack's",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "DSN with empty single quoted value",
|
|
connString: "user='jack' password='' host='localhost' dbname='mydb' sslmode='disable'",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "DSN with space between key and value",
|
|
connString: "user = 'jack' password = '' host = 'localhost' dbname = 'mydb' sslmode='disable'",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "URL multiple hosts",
|
|
connString: "postgres://jack:secret@foo,bar,baz/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "foo",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
Fallbacks: []*pgconn.FallbackConfig{
|
|
&pgconn.FallbackConfig{
|
|
Host: "bar",
|
|
Port: 5432,
|
|
TLSConfig: nil,
|
|
},
|
|
&pgconn.FallbackConfig{
|
|
Host: "baz",
|
|
Port: 5432,
|
|
TLSConfig: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "URL multiple hosts and ports",
|
|
connString: "postgres://jack:secret@foo:1,bar:2,baz:3/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "foo",
|
|
Port: 1,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
Fallbacks: []*pgconn.FallbackConfig{
|
|
&pgconn.FallbackConfig{
|
|
Host: "bar",
|
|
Port: 2,
|
|
TLSConfig: nil,
|
|
},
|
|
&pgconn.FallbackConfig{
|
|
Host: "baz",
|
|
Port: 3,
|
|
TLSConfig: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// https://github.com/jackc/pgconn/issues/72
|
|
{
|
|
name: "URL without host but with port still uses default host",
|
|
connString: "postgres://jack:secret@:1/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: defaultHost,
|
|
Port: 1,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "DSN multiple hosts one port",
|
|
connString: "user=jack password=secret host=foo,bar,baz port=5432 dbname=mydb sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "foo",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
Fallbacks: []*pgconn.FallbackConfig{
|
|
&pgconn.FallbackConfig{
|
|
Host: "bar",
|
|
Port: 5432,
|
|
TLSConfig: nil,
|
|
},
|
|
&pgconn.FallbackConfig{
|
|
Host: "baz",
|
|
Port: 5432,
|
|
TLSConfig: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "DSN multiple hosts multiple ports",
|
|
connString: "user=jack password=secret host=foo,bar,baz port=1,2,3 dbname=mydb sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "foo",
|
|
Port: 1,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
Fallbacks: []*pgconn.FallbackConfig{
|
|
&pgconn.FallbackConfig{
|
|
Host: "bar",
|
|
Port: 2,
|
|
TLSConfig: nil,
|
|
},
|
|
&pgconn.FallbackConfig{
|
|
Host: "baz",
|
|
Port: 3,
|
|
TLSConfig: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "multiple hosts and fallback tsl",
|
|
connString: "user=jack password=secret host=foo,bar,baz dbname=mydb sslmode=prefer",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "foo",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: "foo",
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
Fallbacks: []*pgconn.FallbackConfig{
|
|
&pgconn.FallbackConfig{
|
|
Host: "foo",
|
|
Port: 5432,
|
|
TLSConfig: nil,
|
|
},
|
|
&pgconn.FallbackConfig{
|
|
Host: "bar",
|
|
Port: 5432,
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: "bar",
|
|
}},
|
|
&pgconn.FallbackConfig{
|
|
Host: "bar",
|
|
Port: 5432,
|
|
TLSConfig: nil,
|
|
},
|
|
&pgconn.FallbackConfig{
|
|
Host: "baz",
|
|
Port: 5432,
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: "baz",
|
|
}},
|
|
&pgconn.FallbackConfig{
|
|
Host: "baz",
|
|
Port: 5432,
|
|
TLSConfig: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "target_session_attrs read-write",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=read-write",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsReadWrite,
|
|
},
|
|
},
|
|
{
|
|
name: "target_session_attrs read-only",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=read-only",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsReadOnly,
|
|
},
|
|
},
|
|
{
|
|
name: "target_session_attrs primary",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=primary",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsPrimary,
|
|
},
|
|
},
|
|
{
|
|
name: "target_session_attrs standby",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=standby",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsStandby,
|
|
},
|
|
},
|
|
{
|
|
name: "target_session_attrs prefer-standby",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=prefer-standby",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsPreferStandby,
|
|
},
|
|
},
|
|
{
|
|
name: "target_session_attrs any",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=any",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "target_session_attrs not set (any)",
|
|
connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "SNI is set by default",
|
|
connString: "postgres://jack:secret@sni.test:5432/mydb?sslmode=require",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "sni.test",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: "sni.test",
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "SNI is not set for IPv4",
|
|
connString: "postgres://jack:secret@1.1.1.1:5432/mydb?sslmode=require",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "1.1.1.1",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "SNI is not set for IPv6",
|
|
connString: "postgres://jack:secret@[::1]:5432/mydb?sslmode=require",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "::1",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "SNI is not set when disabled (URL-style)",
|
|
connString: "postgres://jack:secret@sni.test:5432/mydb?sslmode=require&sslsni=0",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "sni.test",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
{
|
|
name: "SNI is not set when disabled (key/value style)",
|
|
connString: "user=jack password=secret host=sni.test dbname=mydb sslmode=require sslsni=0",
|
|
config: &pgconn.Config{
|
|
User: "jack",
|
|
Password: "secret",
|
|
Host: "sni.test",
|
|
Port: 5432,
|
|
Database: "mydb",
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
config, err := pgconn.ParseConfig(tt.connString)
|
|
if !assert.Nilf(t, err, "Test %d (%s)", i, tt.name) {
|
|
continue
|
|
}
|
|
|
|
assertConfigsEqual(t, tt.config, config, fmt.Sprintf("Test %d (%s)", i, tt.name))
|
|
}
|
|
}
|
|
|
|
// https://github.com/jackc/pgconn/issues/47
|
|
func TestParseConfigDSNWithTrailingEmptyEqualDoesNotPanic(t *testing.T) {
|
|
_, err := pgconn.ParseConfig("host= user= password= port= database=")
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestParseConfigDSNLeadingEqual(t *testing.T) {
|
|
_, err := pgconn.ParseConfig("= user=jack")
|
|
require.Error(t, err)
|
|
}
|
|
|
|
// https://github.com/jackc/pgconn/issues/49
|
|
func TestParseConfigDSNTrailingBackslash(t *testing.T) {
|
|
_, err := pgconn.ParseConfig(`x=x\`)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "invalid backslash")
|
|
}
|
|
|
|
func TestConfigCopyReturnsEqualConfig(t *testing.T) {
|
|
connString := "postgres://jack:secret@localhost:5432/mydb?application_name=pgxtest&search_path=myschema&connect_timeout=5"
|
|
original, err := pgconn.ParseConfig(connString)
|
|
require.NoError(t, err)
|
|
|
|
copied := original.Copy()
|
|
assertConfigsEqual(t, original, copied, "Test Config.Copy() returns equal config")
|
|
}
|
|
|
|
func TestConfigCopyOriginalConfigDidNotChange(t *testing.T) {
|
|
connString := "postgres://jack:secret@localhost:5432/mydb?application_name=pgxtest&search_path=myschema&connect_timeout=5&sslmode=prefer"
|
|
original, err := pgconn.ParseConfig(connString)
|
|
require.NoError(t, err)
|
|
|
|
copied := original.Copy()
|
|
assertConfigsEqual(t, original, copied, "Test Config.Copy() returns equal config")
|
|
|
|
copied.Port = uint16(5433)
|
|
copied.RuntimeParams["foo"] = "bar"
|
|
copied.Fallbacks[0].Port = uint16(5433)
|
|
|
|
assert.Equal(t, uint16(5432), original.Port)
|
|
assert.Equal(t, "", original.RuntimeParams["foo"])
|
|
assert.Equal(t, uint16(5432), original.Fallbacks[0].Port)
|
|
}
|
|
|
|
func TestConfigCopyCanBeUsedToConnect(t *testing.T) {
|
|
connString := os.Getenv("PGX_TEST_CONN_STRING")
|
|
original, err := pgconn.ParseConfig(connString)
|
|
require.NoError(t, err)
|
|
|
|
copied := original.Copy()
|
|
assert.NotPanics(t, func() {
|
|
_, err = pgconn.ConnectConfig(context.Background(), copied)
|
|
})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestNetworkAddress(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
host string
|
|
wantNet string
|
|
}{
|
|
{
|
|
name: "Default Unix socket address",
|
|
host: "/var/run/postgresql",
|
|
wantNet: "unix",
|
|
},
|
|
{
|
|
name: "Windows Unix socket address (standard drive name)",
|
|
host: "C:\\tmp",
|
|
wantNet: "unix",
|
|
},
|
|
{
|
|
name: "Windows Unix socket address (first drive name)",
|
|
host: "A:\\tmp",
|
|
wantNet: "unix",
|
|
},
|
|
{
|
|
name: "Windows Unix socket address (last drive name)",
|
|
host: "Z:\\tmp",
|
|
wantNet: "unix",
|
|
},
|
|
{
|
|
name: "Assume TCP for unknown formats",
|
|
host: "a/tmp",
|
|
wantNet: "tcp",
|
|
},
|
|
{
|
|
name: "loopback interface",
|
|
host: "localhost",
|
|
wantNet: "tcp",
|
|
},
|
|
{
|
|
name: "IP address",
|
|
host: "127.0.0.1",
|
|
wantNet: "tcp",
|
|
},
|
|
}
|
|
for i, tt := range tests {
|
|
gotNet, _ := pgconn.NetworkAddress(tt.host, 5432)
|
|
|
|
assert.Equalf(t, tt.wantNet, gotNet, "Test %d (%s)", i, tt.name)
|
|
}
|
|
}
|
|
|
|
func assertConfigsEqual(t *testing.T, expected, actual *pgconn.Config, testName string) {
|
|
if !assert.NotNil(t, expected) {
|
|
return
|
|
}
|
|
if !assert.NotNil(t, actual) {
|
|
return
|
|
}
|
|
|
|
assert.Equalf(t, expected.Host, actual.Host, "%s - Host", testName)
|
|
assert.Equalf(t, expected.Database, actual.Database, "%s - Database", testName)
|
|
assert.Equalf(t, expected.Port, actual.Port, "%s - Port", testName)
|
|
assert.Equalf(t, expected.User, actual.User, "%s - User", testName)
|
|
assert.Equalf(t, expected.Password, actual.Password, "%s - Password", testName)
|
|
assert.Equalf(t, expected.ConnectTimeout, actual.ConnectTimeout, "%s - ConnectTimeout", testName)
|
|
assert.Equalf(t, expected.RuntimeParams, actual.RuntimeParams, "%s - RuntimeParams", testName)
|
|
|
|
// Can't test function equality, so just test that they are set or not.
|
|
assert.Equalf(t, expected.ValidateConnect == nil, actual.ValidateConnect == nil, "%s - ValidateConnect", testName)
|
|
assert.Equalf(t, expected.AfterConnect == nil, actual.AfterConnect == nil, "%s - AfterConnect", testName)
|
|
|
|
if assert.Equalf(t, expected.TLSConfig == nil, actual.TLSConfig == nil, "%s - TLSConfig", testName) {
|
|
if expected.TLSConfig != nil {
|
|
assert.Equalf(t, expected.TLSConfig.InsecureSkipVerify, actual.TLSConfig.InsecureSkipVerify, "%s - TLSConfig InsecureSkipVerify", testName)
|
|
assert.Equalf(t, expected.TLSConfig.ServerName, actual.TLSConfig.ServerName, "%s - TLSConfig ServerName", testName)
|
|
}
|
|
}
|
|
|
|
if assert.Equalf(t, len(expected.Fallbacks), len(actual.Fallbacks), "%s - Fallbacks", testName) {
|
|
for i := range expected.Fallbacks {
|
|
assert.Equalf(t, expected.Fallbacks[i].Host, actual.Fallbacks[i].Host, "%s - Fallback %d - Host", testName, i)
|
|
assert.Equalf(t, expected.Fallbacks[i].Port, actual.Fallbacks[i].Port, "%s - Fallback %d - Port", testName, i)
|
|
|
|
if assert.Equalf(t, expected.Fallbacks[i].TLSConfig == nil, actual.Fallbacks[i].TLSConfig == nil, "%s - Fallback %d - TLSConfig", testName, i) {
|
|
if expected.Fallbacks[i].TLSConfig != nil {
|
|
assert.Equalf(t, expected.Fallbacks[i].TLSConfig.InsecureSkipVerify, actual.Fallbacks[i].TLSConfig.InsecureSkipVerify, "%s - Fallback %d - TLSConfig InsecureSkipVerify", testName)
|
|
assert.Equalf(t, expected.Fallbacks[i].TLSConfig.ServerName, actual.Fallbacks[i].TLSConfig.ServerName, "%s - Fallback %d - TLSConfig ServerName", testName)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseConfigEnvLibpq(t *testing.T) {
|
|
var osUserName string
|
|
osUser, err := user.Current()
|
|
if err == nil {
|
|
// Windows gives us the username here as `DOMAIN\user` or `LOCALPCNAME\user`,
|
|
// but the libpq default is just the `user` portion, so we strip off the first part.
|
|
if runtime.GOOS == "windows" && strings.Contains(osUser.Username, "\\") {
|
|
osUserName = osUser.Username[strings.LastIndex(osUser.Username, "\\")+1:]
|
|
} else {
|
|
osUserName = osUser.Username
|
|
}
|
|
}
|
|
|
|
pgEnvvars := []string{"PGHOST", "PGPORT", "PGDATABASE", "PGUSER", "PGPASSWORD", "PGAPPNAME", "PGSSLMODE", "PGCONNECT_TIMEOUT", "PGSSLSNI"}
|
|
|
|
savedEnv := make(map[string]string)
|
|
for _, n := range pgEnvvars {
|
|
savedEnv[n] = os.Getenv(n)
|
|
}
|
|
defer func() {
|
|
for k, v := range savedEnv {
|
|
err := os.Setenv(k, v)
|
|
if err != nil {
|
|
t.Fatalf("Unable to restore environment: %v", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
tests := []struct {
|
|
name string
|
|
envvars map[string]string
|
|
config *pgconn.Config
|
|
}{
|
|
{
|
|
// not testing no environment at all as that would use default host and that can vary.
|
|
name: "PGHOST only",
|
|
envvars: map[string]string{"PGHOST": "123.123.123.123"},
|
|
config: &pgconn.Config{
|
|
User: osUserName,
|
|
Host: "123.123.123.123",
|
|
Port: 5432,
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
Fallbacks: []*pgconn.FallbackConfig{
|
|
&pgconn.FallbackConfig{
|
|
Host: "123.123.123.123",
|
|
Port: 5432,
|
|
TLSConfig: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "All non-TLS environment",
|
|
envvars: map[string]string{
|
|
"PGHOST": "123.123.123.123",
|
|
"PGPORT": "7777",
|
|
"PGDATABASE": "foo",
|
|
"PGUSER": "bar",
|
|
"PGPASSWORD": "baz",
|
|
"PGCONNECT_TIMEOUT": "10",
|
|
"PGSSLMODE": "disable",
|
|
"PGAPPNAME": "pgxtest",
|
|
},
|
|
config: &pgconn.Config{
|
|
Host: "123.123.123.123",
|
|
Port: 7777,
|
|
Database: "foo",
|
|
User: "bar",
|
|
Password: "baz",
|
|
ConnectTimeout: 10 * time.Second,
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{"application_name": "pgxtest"},
|
|
},
|
|
},
|
|
{
|
|
name: "SNI can be disabled via environment variable",
|
|
envvars: map[string]string{
|
|
"PGHOST": "test.foo",
|
|
"PGSSLMODE": "require",
|
|
"PGSSLSNI": "0",
|
|
},
|
|
config: &pgconn.Config{
|
|
User: osUserName,
|
|
Host: "test.foo",
|
|
Port: 5432,
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
for _, n := range pgEnvvars {
|
|
err := os.Unsetenv(n)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
for k, v := range tt.envvars {
|
|
err := os.Setenv(k, v)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
config, err := pgconn.ParseConfig("")
|
|
if !assert.Nilf(t, err, "Test %d (%s)", i, tt.name) {
|
|
continue
|
|
}
|
|
|
|
assertConfigsEqual(t, tt.config, config, fmt.Sprintf("Test %d (%s)", i, tt.name))
|
|
}
|
|
}
|
|
|
|
func TestParseConfigReadsPgPassfile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tf, err := ioutil.TempFile("", "")
|
|
require.NoError(t, err)
|
|
|
|
defer tf.Close()
|
|
defer os.Remove(tf.Name())
|
|
|
|
_, err = tf.Write([]byte("test1:5432:curlydb:curly:nyuknyuknyuk"))
|
|
require.NoError(t, err)
|
|
|
|
connString := fmt.Sprintf("postgres://curly@test1:5432/curlydb?sslmode=disable&passfile=%s", tf.Name())
|
|
expected := &pgconn.Config{
|
|
User: "curly",
|
|
Password: "nyuknyuknyuk",
|
|
Host: "test1",
|
|
Port: 5432,
|
|
Database: "curlydb",
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
}
|
|
|
|
actual, err := pgconn.ParseConfig(connString)
|
|
assert.NoError(t, err)
|
|
|
|
assertConfigsEqual(t, expected, actual, "passfile")
|
|
}
|
|
|
|
func TestParseConfigReadsPgServiceFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tf, err := ioutil.TempFile("", "")
|
|
require.NoError(t, err)
|
|
|
|
defer tf.Close()
|
|
defer os.Remove(tf.Name())
|
|
|
|
_, err = tf.Write([]byte(`
|
|
[abc]
|
|
host=abc.example.com
|
|
port=9999
|
|
dbname=abcdb
|
|
user=abcuser
|
|
|
|
[def]
|
|
host = def.example.com
|
|
dbname = defdb
|
|
user = defuser
|
|
application_name = spaced string
|
|
`))
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
connString string
|
|
config *pgconn.Config
|
|
}{
|
|
{
|
|
name: "abc",
|
|
connString: fmt.Sprintf("postgres:///?servicefile=%s&service=%s", tf.Name(), "abc"),
|
|
config: &pgconn.Config{
|
|
Host: "abc.example.com",
|
|
Database: "abcdb",
|
|
User: "abcuser",
|
|
Port: 9999,
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: "abc.example.com",
|
|
},
|
|
RuntimeParams: map[string]string{},
|
|
Fallbacks: []*pgconn.FallbackConfig{
|
|
&pgconn.FallbackConfig{
|
|
Host: "abc.example.com",
|
|
Port: 9999,
|
|
TLSConfig: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "def",
|
|
connString: fmt.Sprintf("postgres:///?servicefile=%s&service=%s", tf.Name(), "def"),
|
|
config: &pgconn.Config{
|
|
Host: "def.example.com",
|
|
Port: 5432,
|
|
Database: "defdb",
|
|
User: "defuser",
|
|
TLSConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: "def.example.com",
|
|
},
|
|
RuntimeParams: map[string]string{"application_name": "spaced string"},
|
|
Fallbacks: []*pgconn.FallbackConfig{
|
|
&pgconn.FallbackConfig{
|
|
Host: "def.example.com",
|
|
Port: 5432,
|
|
TLSConfig: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "conn string has precedence",
|
|
connString: fmt.Sprintf("postgres://other.example.com:7777/?servicefile=%s&service=%s&sslmode=disable", tf.Name(), "abc"),
|
|
config: &pgconn.Config{
|
|
Host: "other.example.com",
|
|
Database: "abcdb",
|
|
User: "abcuser",
|
|
Port: 7777,
|
|
TLSConfig: nil,
|
|
RuntimeParams: map[string]string{},
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
config, err := pgconn.ParseConfig(tt.connString)
|
|
if !assert.NoErrorf(t, err, "Test %d (%s)", i, tt.name) {
|
|
continue
|
|
}
|
|
|
|
assertConfigsEqual(t, tt.config, config, fmt.Sprintf("Test %d (%s)", i, tt.name))
|
|
}
|
|
}
|
|
|
|
func TestParseConfigExtractsMinReadBufferSize(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
config, err := pgconn.ParseConfig("min_read_buffer_size=0")
|
|
require.NoError(t, err)
|
|
_, present := config.RuntimeParams["min_read_buffer_size"]
|
|
require.False(t, present)
|
|
|
|
// The buffer size is internal so there isn't much that can be done to test it other than see that the runtime param
|
|
// was removed.
|
|
}
|