supabase-cli/internal/utils/connect_test.go

179 lines
5.8 KiB
Go

package utils
import (
"context"
"net"
"net/http"
"testing"
"github.com/go-errors/errors"
"github.com/h2non/gock"
"github.com/jackc/pgconn"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/supabase/cli/internal/testing/apitest"
"github.com/supabase/cli/internal/utils/cloudflare"
"github.com/supabase/cli/pkg/pgtest"
)
var dbConfig = pgconn.Config{
Host: GetSupabaseDbHost(apitest.RandomProjectRef()),
Port: 6543,
User: "admin",
Password: "password",
Database: "postgres",
}
const (
PG13_POOLER_URL = "postgres://postgres:[YOUR-PASSWORD]@aws-0-ap-southeast-1.pooler.supabase.com:6543/postgres?options=reference%3Dzupyfdrjfhbeevcogohz"
PG15_POOLER_URL = "postgres://postgres.zupyfdrjfhbeevcogohz:[YOUR-PASSWORD]@fly-0-sin.pooler.supabase.com:6543/postgres"
)
func TestConnectByConfig(t *testing.T) {
t.Run("connects to remote postgres with DoH", func(t *testing.T) {
Config.Db.Pooler.ConnectionString = ""
DNSResolver.Value = DNS_OVER_HTTPS
// Setup http mock
defer gock.OffAll()
// pgx makes 2 calls to resolve ip for each connect request
gock.New("https://1.1.1.1").
Get("/dns-query").
MatchParam("name", dbConfig.Host).
MatchHeader("accept", "application/dns-json").
Reply(http.StatusOK).
JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{
{Type: cloudflare.TypeA, Data: "127.0.0.1"},
}})
gock.New("https://1.1.1.1").
Get("/dns-query").
MatchParam("name", dbConfig.Host).
MatchHeader("accept", "application/dns-json").
Reply(http.StatusOK).
JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{
{Type: cloudflare.TypeA, Data: "127.0.0.1"},
}})
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
// Run test
c, err := ConnectByConfig(context.Background(), dbConfig, conn.Intercept)
require.NoError(t, err)
defer c.Close(context.Background())
assert.NoError(t, err)
})
t.Run("connects with unescaped db password", func(t *testing.T) {
DNSResolver.Value = DNS_GO_NATIVE
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
// Run test
config := *dbConfig.Copy()
config.Host = "localhost"
config.Password = "pass word"
c, err := ConnectByConfig(context.Background(), config, conn.Intercept)
require.NoError(t, err)
defer c.Close(context.Background())
assert.Equal(t, config.Password, c.Config().Password)
})
t.Run("no retry on connecting successfully with pooler", func(t *testing.T) {
Config.Db.Pooler.ConnectionString = PG15_POOLER_URL
DNSResolver.Value = DNS_GO_NATIVE
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
// Run test
c, err := ConnectByConfig(context.Background(), dbConfig, conn.Intercept)
// Check error
require.NoError(t, err)
defer c.Close(context.Background())
assert.Empty(t, apitest.ListUnmatchedRequests())
})
t.Run("fallback to postgres port on dial error", func(t *testing.T) {
Config.Db.Pooler.ConnectionString = PG15_POOLER_URL
DNSResolver.Value = DNS_OVER_HTTPS
netErr := errors.New("network error")
// Setup http mock
defer gock.OffAll()
gock.New("https://1.1.1.1").
Get("/dns-query").
MatchParam("name", dbConfig.Host).
MatchHeader("accept", "application/dns-json").
ReplyError(&net.OpError{Op: "dial", Err: netErr})
gock.New("https://1.1.1.1").
Get("/dns-query").
MatchParam("name", "fly-0-sin.pooler.supabase.com").
MatchHeader("accept", "application/dns-json").
ReplyError(&net.OpError{Op: "dial", Err: netErr})
// Run test
_, err := ConnectByConfig(context.Background(), dbConfig)
// Check error
require.ErrorIs(t, err, netErr)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
}
func TestConnectLocal(t *testing.T) {
t.Run("connects with debug log", func(t *testing.T) {
viper.Set("DEBUG", true)
_, err := ConnectLocalPostgres(context.Background(), pgconn.Config{Host: "0", Port: 6543})
assert.ErrorContains(t, err, "failed to connect to postgres")
})
t.Run("throws error on invalid port", func(t *testing.T) {
Config.Db.Port = 0
_, err := ConnectLocalPostgres(context.Background(), pgconn.Config{})
assert.ErrorContains(t, err, "invalid port (outside range)")
})
}
func TestPoolerConfig(t *testing.T) {
t.Run("parses options ref", func(t *testing.T) {
Config.Db.Pooler.ConnectionString = PG13_POOLER_URL
assert.NotNil(t, GetPoolerConfig("zupyfdrjfhbeevcogohz"))
})
t.Run("parses username ref", func(t *testing.T) {
Config.Db.Pooler.ConnectionString = PG15_POOLER_URL
assert.NotNil(t, GetPoolerConfig("zupyfdrjfhbeevcogohz"))
})
t.Run("returns nil on missing url", func(t *testing.T) {
Config.Db.Pooler.ConnectionString = ""
assert.Nil(t, GetPoolerConfig("zupyfdrjfhbeevcogohz"))
})
t.Run("returns nil on malformed url", func(t *testing.T) {
Config.Db.Pooler.ConnectionString = "malformed"
assert.Nil(t, GetPoolerConfig("zupyfdrjfhbeevcogohz"))
})
t.Run("returns nil on mismatched project", func(t *testing.T) {
Config.Db.Pooler.ConnectionString = PG13_POOLER_URL
assert.Nil(t, GetPoolerConfig("nlhaskwsizylhnffaqkd"))
Config.Db.Pooler.ConnectionString = PG15_POOLER_URL
assert.Nil(t, GetPoolerConfig("nlhaskwsizylhnffaqkd"))
})
t.Run("returns nil on invalid host", func(t *testing.T) {
Config.Db.Pooler.ConnectionString = "postgres://postgres.zupyfdrjfhbeevcogohz:[YOUR-PASSWORD]@localhost:6543/postgres"
assert.Nil(t, GetPoolerConfig("zupyfdrjfhbeevcogohz"))
})
}
func TestPostgresURL(t *testing.T) {
url := ToPostgresURL(pgconn.Config{
Host: "2406:da18:4fd:9b0d:80ec:9812:3e65:450b",
Port: 5432,
User: "postgres",
Password: "!@#$%^&*()",
RuntimeParams: map[string]string{
"options": "test",
},
})
assert.Equal(t, `postgresql://postgres:%21%40%23$%25%5E&%2A%28%29@[2406:da18:4fd:9b0d:80ec:9812:3e65:450b]:5432/?connect_timeout=10&options=test`, url)
}