github.com/supabase/cli@v1.168.1/internal/db/branch/switch_/switch__test.go (about)

     1  package switch_
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/jackc/pgerrcode"
    13  	"github.com/spf13/afero"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  	"github.com/supabase/cli/internal/testing/apitest"
    17  	"github.com/supabase/cli/internal/testing/pgtest"
    18  	"github.com/supabase/cli/internal/utils"
    19  	"gopkg.in/h2non/gock.v1"
    20  )
    21  
    22  func TestSwitchCommand(t *testing.T) {
    23  	t.Run("switches local branch", func(t *testing.T) {
    24  		// Setup in-memory fs
    25  		fsys := afero.NewMemMapFs()
    26  		require.NoError(t, utils.WriteConfig(fsys, false))
    27  		// Setup target branch
    28  		branch := "target"
    29  		branchPath := filepath.Join(filepath.Dir(utils.CurrBranchPath), branch)
    30  		require.NoError(t, fsys.Mkdir(branchPath, 0755))
    31  		require.NoError(t, afero.WriteFile(fsys, utils.CurrBranchPath, []byte("main"), 0644))
    32  		// Setup mock docker
    33  		require.NoError(t, apitest.MockDocker(utils.Docker))
    34  		defer gock.OffAll()
    35  		gock.New(utils.Docker.DaemonHost()).
    36  			Get("/v" + utils.Docker.ClientVersion() + "/containers").
    37  			Reply(http.StatusOK).
    38  			JSON(types.ContainerJSON{})
    39  		gock.New(utils.Docker.DaemonHost()).
    40  			Post("/v" + utils.Docker.ClientVersion() + "/containers").
    41  			Reply(http.StatusServiceUnavailable)
    42  		// Setup mock postgres
    43  		conn := pgtest.NewConn()
    44  		defer conn.Close(t)
    45  		conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;").
    46  			Reply("ALTER DATABASE").
    47  			Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")).
    48  			Reply("DO").
    49  			Query("ALTER DATABASE postgres RENAME TO main;").
    50  			Reply("ALTER DATABASE").
    51  			Query("ALTER DATABASE " + branch + " RENAME TO postgres;").
    52  			Reply("ALTER DATABASE")
    53  		// Run test
    54  		assert.NoError(t, Run(context.Background(), branch, fsys, conn.Intercept))
    55  		// Validate output
    56  		assert.Empty(t, apitest.ListUnmatchedRequests())
    57  		contents, err := afero.ReadFile(fsys, utils.CurrBranchPath)
    58  		assert.NoError(t, err)
    59  		assert.Equal(t, []byte(branch), contents)
    60  	})
    61  
    62  	t.Run("throws error on missing config", func(t *testing.T) {
    63  		// Setup in-memory fs
    64  		fsys := afero.NewMemMapFs()
    65  		// Run test
    66  		err := Run(context.Background(), "target", fsys)
    67  		// Check error
    68  		assert.ErrorIs(t, err, os.ErrNotExist)
    69  	})
    70  
    71  	t.Run("throws error on malformed config", func(t *testing.T) {
    72  		// Setup in-memory fs
    73  		fsys := afero.NewMemMapFs()
    74  		require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644))
    75  		// Run test
    76  		err := Run(context.Background(), "target", fsys)
    77  		// Check error
    78  		assert.ErrorContains(t, err, "toml: line 0: unexpected EOF; expected key separator '='")
    79  	})
    80  
    81  	t.Run("throws error on missing database", func(t *testing.T) {
    82  		// Setup in-memory fs
    83  		fsys := afero.NewMemMapFs()
    84  		require.NoError(t, utils.WriteConfig(fsys, false))
    85  		// Setup mock docker
    86  		require.NoError(t, apitest.MockDocker(utils.Docker))
    87  		defer gock.OffAll()
    88  		gock.New(utils.Docker.DaemonHost()).
    89  			Get("/v" + utils.Docker.ClientVersion() + "/containers").
    90  			Reply(http.StatusNotFound)
    91  		// Run test
    92  		err := Run(context.Background(), "target", fsys)
    93  		// Check error
    94  		assert.ErrorIs(t, err, utils.ErrNotRunning)
    95  		assert.Empty(t, apitest.ListUnmatchedRequests())
    96  	})
    97  
    98  	t.Run("throws error on reserved branch", func(t *testing.T) {
    99  		// Setup in-memory fs
   100  		fsys := afero.NewMemMapFs()
   101  		require.NoError(t, utils.WriteConfig(fsys, false))
   102  		// Setup mock docker
   103  		require.NoError(t, apitest.MockDocker(utils.Docker))
   104  		defer gock.OffAll()
   105  		gock.New(utils.Docker.DaemonHost()).
   106  			Get("/v" + utils.Docker.ClientVersion() + "/containers").
   107  			Reply(http.StatusOK).
   108  			JSON(types.ContainerJSON{})
   109  		// Run test
   110  		err := Run(context.Background(), "postgres", fsys)
   111  		// Check error
   112  		assert.ErrorContains(t, err, "branch name is reserved.")
   113  		assert.Empty(t, apitest.ListUnmatchedRequests())
   114  	})
   115  
   116  	t.Run("throws error on missing branch", func(t *testing.T) {
   117  		// Setup in-memory fs
   118  		fsys := afero.NewMemMapFs()
   119  		require.NoError(t, utils.WriteConfig(fsys, false))
   120  		// Setup mock docker
   121  		require.NoError(t, apitest.MockDocker(utils.Docker))
   122  		defer gock.OffAll()
   123  		gock.New(utils.Docker.DaemonHost()).
   124  			Get("/v" + utils.Docker.ClientVersion() + "/containers").
   125  			Reply(http.StatusOK).
   126  			JSON(types.ContainerJSON{})
   127  		// Run test
   128  		err := Run(context.Background(), "main", fsys)
   129  		// Check error
   130  		assert.ErrorContains(t, err, "Branch main does not exist.")
   131  		assert.Empty(t, apitest.ListUnmatchedRequests())
   132  	})
   133  
   134  	t.Run("noop on current branch", func(t *testing.T) {
   135  		// Setup in-memory fs
   136  		fsys := afero.NewMemMapFs()
   137  		require.NoError(t, utils.WriteConfig(fsys, false))
   138  		// Setup mock docker
   139  		require.NoError(t, apitest.MockDocker(utils.Docker))
   140  		defer gock.OffAll()
   141  		gock.New(utils.Docker.DaemonHost()).
   142  			Get("/v" + utils.Docker.ClientVersion() + "/containers").
   143  			Reply(http.StatusOK).
   144  			JSON(types.ContainerJSON{})
   145  		// Setup target branch
   146  		branch := "main"
   147  		branchPath := filepath.Join(filepath.Dir(utils.CurrBranchPath), branch)
   148  		require.NoError(t, fsys.Mkdir(branchPath, 0755))
   149  		// Run test
   150  		assert.NoError(t, Run(context.Background(), branch, fsys))
   151  		// Check error
   152  		assert.Empty(t, apitest.ListUnmatchedRequests())
   153  		contents, err := afero.ReadFile(fsys, utils.CurrBranchPath)
   154  		assert.NoError(t, err)
   155  		assert.Equal(t, []byte(branch), contents)
   156  	})
   157  
   158  	t.Run("throws error on failure to switch", func(t *testing.T) {
   159  		// Setup in-memory fs
   160  		fsys := afero.NewMemMapFs()
   161  		require.NoError(t, utils.WriteConfig(fsys, false))
   162  		// Setup mock docker
   163  		require.NoError(t, apitest.MockDocker(utils.Docker))
   164  		defer gock.OffAll()
   165  		gock.New(utils.Docker.DaemonHost()).
   166  			Get("/v" + utils.Docker.ClientVersion() + "/containers").
   167  			Reply(http.StatusOK).
   168  			JSON(types.ContainerJSON{})
   169  		// Setup target branch
   170  		branch := "target"
   171  		branchPath := filepath.Join(filepath.Dir(utils.CurrBranchPath), branch)
   172  		require.NoError(t, fsys.Mkdir(branchPath, 0755))
   173  		// Setup mock postgres
   174  		conn := pgtest.NewConn()
   175  		// Run test
   176  		err := Run(context.Background(), branch, fsys, conn.Intercept)
   177  		// Check error
   178  		assert.ErrorContains(t, err, "Error switching to branch target")
   179  		assert.Empty(t, apitest.ListUnmatchedRequests())
   180  	})
   181  
   182  	t.Run("throws error on failure to write", func(t *testing.T) {
   183  		// Setup in-memory fs
   184  		fsys := afero.NewMemMapFs()
   185  		require.NoError(t, utils.WriteConfig(fsys, false))
   186  		// Setup mock docker
   187  		require.NoError(t, apitest.MockDocker(utils.Docker))
   188  		defer gock.OffAll()
   189  		gock.New(utils.Docker.DaemonHost()).
   190  			Get("/v" + utils.Docker.ClientVersion() + "/containers").
   191  			Reply(http.StatusOK).
   192  			JSON(types.ContainerJSON{})
   193  		// Setup target branch
   194  		branch := "main"
   195  		branchPath := filepath.Join(filepath.Dir(utils.CurrBranchPath), branch)
   196  		require.NoError(t, fsys.Mkdir(branchPath, 0755))
   197  		// Run test
   198  		err := Run(context.Background(), branch, afero.NewReadOnlyFs(fsys))
   199  		// Check error
   200  		assert.ErrorContains(t, err, "Unable to update local branch file.")
   201  		assert.Empty(t, apitest.ListUnmatchedRequests())
   202  	})
   203  }
   204  
   205  func TestSwitchDatabase(t *testing.T) {
   206  	t.Run("throws error on failure to connect", func(t *testing.T) {
   207  		// Setup invalid port
   208  		utils.Config.Db.Port = 0
   209  		// Run test
   210  		err := switchDatabase(context.Background(), "main", "target")
   211  		// Check error
   212  		assert.ErrorContains(t, err, "invalid port")
   213  	})
   214  
   215  	t.Run("throws error on failure to disconnect", func(t *testing.T) {
   216  		// Setup valid config
   217  		utils.Config.Db.Port = 54322
   218  		// Setup mock postgres
   219  		conn := pgtest.NewConn()
   220  		defer conn.Close(t)
   221  		conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;").
   222  			ReplyError(pgerrcode.InvalidParameterValue, `cannot disallow connections for current database`)
   223  		// Run test
   224  		err := switchDatabase(context.Background(), "main", "target", conn.Intercept)
   225  		// Check error
   226  		assert.ErrorContains(t, err, pgerrcode.InvalidParameterValue)
   227  		assert.Empty(t, apitest.ListUnmatchedRequests())
   228  	})
   229  
   230  	t.Run("throws error on failure to backup", func(t *testing.T) {
   231  		// Setup valid config
   232  		utils.DbId = "test-switch"
   233  		utils.Config.Db.Port = 54322
   234  		// Setup mock postgres
   235  		conn := pgtest.NewConn()
   236  		defer conn.Close(t)
   237  		conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;").
   238  			Reply("ALTER DATABASE").
   239  			Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")).
   240  			Reply("DO").
   241  			Query("ALTER DATABASE postgres RENAME TO main;").
   242  			ReplyError(pgerrcode.DuplicateDatabase, `database "main" already exists`)
   243  		// Setup mock docker
   244  		require.NoError(t, apitest.MockDocker(utils.Docker))
   245  		defer gock.OffAll()
   246  		gock.New(utils.Docker.DaemonHost()).
   247  			Post("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/restart").
   248  			Reply(http.StatusServiceUnavailable)
   249  		// Run test
   250  		err := switchDatabase(context.Background(), "main", "target", conn.Intercept)
   251  		// Check error
   252  		assert.ErrorContains(t, err, pgerrcode.DuplicateDatabase)
   253  		assert.Empty(t, apitest.ListUnmatchedRequests())
   254  	})
   255  
   256  	t.Run("throws error on failure to rename", func(t *testing.T) {
   257  		// Setup valid config
   258  		utils.DbId = "test-switch"
   259  		utils.Config.Db.Port = 54322
   260  		// Setup mock postgres
   261  		conn := pgtest.NewConn()
   262  		defer conn.Close(t)
   263  		conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;").
   264  			Reply("ALTER DATABASE").
   265  			Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")).
   266  			Reply("DO").
   267  			Query("ALTER DATABASE postgres RENAME TO main;").
   268  			Reply("ALTER DATABASE").
   269  			Query("ALTER DATABASE target RENAME TO postgres;").
   270  			ReplyError(pgerrcode.InvalidCatalogName, `database "target" does not exist`).
   271  			// Attempt to rollback
   272  			Query("ALTER DATABASE main RENAME TO postgres;").
   273  			ReplyError(pgerrcode.DuplicateDatabase, `database "postgres" already exists`)
   274  		// Setup mock docker
   275  		require.NoError(t, apitest.MockDocker(utils.Docker))
   276  		defer gock.OffAll()
   277  		gock.New(utils.Docker.DaemonHost()).
   278  			Post("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/restart").
   279  			Reply(http.StatusServiceUnavailable)
   280  		// Run test
   281  		err := switchDatabase(context.Background(), "main", "target", conn.Intercept)
   282  		// Check error
   283  		assert.ErrorContains(t, err, pgerrcode.InvalidCatalogName)
   284  		assert.Empty(t, apitest.ListUnmatchedRequests())
   285  	})
   286  }