github.com/supabase/cli@v1.168.1/internal/utils/connect_test.go (about)

     1  package utils
     2  
     3  import (
     4  	"context"
     5  	"net"
     6  	"net/http"
     7  	"testing"
     8  
     9  	"github.com/go-errors/errors"
    10  	"github.com/jackc/pgconn"
    11  	"github.com/spf13/viper"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  	"github.com/supabase/cli/internal/testing/apitest"
    15  	"github.com/supabase/cli/internal/testing/pgtest"
    16  	"github.com/supabase/cli/internal/utils/cloudflare"
    17  	"gopkg.in/h2non/gock.v1"
    18  )
    19  
    20  var dbConfig = pgconn.Config{
    21  	Host:     GetSupabaseDbHost(apitest.RandomProjectRef()),
    22  	Port:     6543,
    23  	User:     "admin",
    24  	Password: "password",
    25  	Database: "postgres",
    26  }
    27  
    28  const (
    29  	PG13_POOLER_URL = "postgres://postgres:[YOUR-PASSWORD]@aws-0-ap-southeast-1.pooler.supabase.com:6543/postgres?options=reference%3Dzupyfdrjfhbeevcogohz"
    30  	PG15_POOLER_URL = "postgres://postgres.zupyfdrjfhbeevcogohz:[YOUR-PASSWORD]@fly-0-sin.pooler.supabase.com:6543/postgres"
    31  )
    32  
    33  func TestConnectByConfig(t *testing.T) {
    34  	t.Run("connects to remote postgres with DoH", func(t *testing.T) {
    35  		Config.Db.Pooler.ConnectionString = ""
    36  		DNSResolver.Value = DNS_OVER_HTTPS
    37  		// Setup http mock
    38  		defer gock.OffAll()
    39  		// pgx makes 2 calls to resolve ip for each connect request
    40  		gock.New("https://1.1.1.1").
    41  			Get("/dns-query").
    42  			MatchParam("name", dbConfig.Host).
    43  			MatchHeader("accept", "application/dns-json").
    44  			Reply(http.StatusOK).
    45  			JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{
    46  				{Type: cloudflare.TypeA, Data: "127.0.0.1"},
    47  			}})
    48  		gock.New("https://1.1.1.1").
    49  			Get("/dns-query").
    50  			MatchParam("name", dbConfig.Host).
    51  			MatchHeader("accept", "application/dns-json").
    52  			Reply(http.StatusOK).
    53  			JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{
    54  				{Type: cloudflare.TypeA, Data: "127.0.0.1"},
    55  			}})
    56  		// Setup mock postgres
    57  		conn := pgtest.NewConn()
    58  		defer conn.Close(t)
    59  		// Run test
    60  		c, err := ConnectByConfig(context.Background(), dbConfig, conn.Intercept)
    61  		require.NoError(t, err)
    62  		defer c.Close(context.Background())
    63  		assert.NoError(t, err)
    64  	})
    65  
    66  	t.Run("connects with unescaped db password", func(t *testing.T) {
    67  		DNSResolver.Value = DNS_GO_NATIVE
    68  		// Setup mock postgres
    69  		conn := pgtest.NewConn()
    70  		defer conn.Close(t)
    71  		// Run test
    72  		config := *dbConfig.Copy()
    73  		config.Host = "localhost"
    74  		config.Password = "pass word"
    75  		c, err := ConnectByConfig(context.Background(), config, conn.Intercept)
    76  		require.NoError(t, err)
    77  		defer c.Close(context.Background())
    78  		assert.Equal(t, config.Password, c.Config().Password)
    79  	})
    80  
    81  	t.Run("no retry on connecting successfully with pooler", func(t *testing.T) {
    82  		Config.Db.Pooler.ConnectionString = PG15_POOLER_URL
    83  		DNSResolver.Value = DNS_GO_NATIVE
    84  		// Setup mock postgres
    85  		conn := pgtest.NewConn()
    86  		defer conn.Close(t)
    87  		// Run test
    88  		c, err := ConnectByConfig(context.Background(), dbConfig, conn.Intercept)
    89  		// Check error
    90  		require.NoError(t, err)
    91  		defer c.Close(context.Background())
    92  		assert.Empty(t, apitest.ListUnmatchedRequests())
    93  	})
    94  
    95  	t.Run("fallback to postgres port on dial error", func(t *testing.T) {
    96  		Config.Db.Pooler.ConnectionString = PG15_POOLER_URL
    97  		DNSResolver.Value = DNS_OVER_HTTPS
    98  		netErr := errors.New("network error")
    99  		// Setup http mock
   100  		defer gock.OffAll()
   101  		gock.New("https://1.1.1.1").
   102  			Get("/dns-query").
   103  			MatchParam("name", dbConfig.Host).
   104  			MatchHeader("accept", "application/dns-json").
   105  			ReplyError(&net.OpError{Op: "dial", Err: netErr})
   106  		gock.New("https://1.1.1.1").
   107  			Get("/dns-query").
   108  			MatchParam("name", "fly-0-sin.pooler.supabase.com").
   109  			MatchHeader("accept", "application/dns-json").
   110  			ReplyError(&net.OpError{Op: "dial", Err: netErr})
   111  		// Run test
   112  		_, err := ConnectByConfig(context.Background(), dbConfig)
   113  		// Check error
   114  		require.ErrorIs(t, err, netErr)
   115  		assert.Empty(t, apitest.ListUnmatchedRequests())
   116  	})
   117  }
   118  
   119  func TestConnectLocal(t *testing.T) {
   120  	t.Run("connects with debug log", func(t *testing.T) {
   121  		viper.Set("DEBUG", true)
   122  		_, err := ConnectLocalPostgres(context.Background(), pgconn.Config{Host: "0", Port: 6543})
   123  		assert.ErrorContains(t, err, "failed to connect to postgres")
   124  	})
   125  
   126  	t.Run("throws error on invalid port", func(t *testing.T) {
   127  		Config.Db.Port = 0
   128  		_, err := ConnectLocalPostgres(context.Background(), pgconn.Config{})
   129  		assert.ErrorContains(t, err, "invalid port (outside range)")
   130  	})
   131  }
   132  
   133  func TestPoolerConfig(t *testing.T) {
   134  	t.Run("parses options ref", func(t *testing.T) {
   135  		Config.Db.Pooler.ConnectionString = PG13_POOLER_URL
   136  		assert.NotNil(t, GetPoolerConfig("zupyfdrjfhbeevcogohz"))
   137  	})
   138  
   139  	t.Run("parses username ref", func(t *testing.T) {
   140  		Config.Db.Pooler.ConnectionString = PG15_POOLER_URL
   141  		assert.NotNil(t, GetPoolerConfig("zupyfdrjfhbeevcogohz"))
   142  	})
   143  
   144  	t.Run("returns nil on missing url", func(t *testing.T) {
   145  		Config.Db.Pooler.ConnectionString = ""
   146  		assert.Nil(t, GetPoolerConfig("zupyfdrjfhbeevcogohz"))
   147  	})
   148  
   149  	t.Run("returns nil on malformed url", func(t *testing.T) {
   150  		Config.Db.Pooler.ConnectionString = "malformed"
   151  		assert.Nil(t, GetPoolerConfig("zupyfdrjfhbeevcogohz"))
   152  	})
   153  
   154  	t.Run("returns nil on mismatched project", func(t *testing.T) {
   155  		Config.Db.Pooler.ConnectionString = PG13_POOLER_URL
   156  		assert.Nil(t, GetPoolerConfig("nlhaskwsizylhnffaqkd"))
   157  		Config.Db.Pooler.ConnectionString = PG15_POOLER_URL
   158  		assert.Nil(t, GetPoolerConfig("nlhaskwsizylhnffaqkd"))
   159  	})
   160  
   161  	t.Run("returns nil on invalid host", func(t *testing.T) {
   162  		Config.Db.Pooler.ConnectionString = "postgres://postgres.zupyfdrjfhbeevcogohz:[YOUR-PASSWORD]@localhost:6543/postgres"
   163  		assert.Nil(t, GetPoolerConfig("zupyfdrjfhbeevcogohz"))
   164  	})
   165  }
   166  
   167  func TestPostgresURL(t *testing.T) {
   168  	url := ToPostgresURL(pgconn.Config{
   169  		Host:     "2406:da18:4fd:9b0d:80ec:9812:3e65:450b",
   170  		Port:     5432,
   171  		User:     "postgres",
   172  		Password: "!@#$%^&*()",
   173  		RuntimeParams: map[string]string{
   174  			"options": "test",
   175  		},
   176  	})
   177  	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)
   178  }