291 lines
10 KiB
Go
291 lines
10 KiB
Go
package switch_
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/h2non/gock"
|
|
"github.com/jackc/pgerrcode"
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/supabase/cli/internal/db/reset"
|
|
"github.com/supabase/cli/internal/testing/apitest"
|
|
"github.com/supabase/cli/internal/utils"
|
|
"github.com/supabase/cli/pkg/pgtest"
|
|
)
|
|
|
|
func TestSwitchCommand(t *testing.T) {
|
|
t.Run("switches local branch", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
// Setup target branch
|
|
branch := "target"
|
|
branchPath := filepath.Join(filepath.Dir(utils.CurrBranchPath), branch)
|
|
require.NoError(t, fsys.Mkdir(branchPath, 0755))
|
|
require.NoError(t, afero.WriteFile(fsys, utils.CurrBranchPath, []byte("main"), 0644))
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
defer gock.OffAll()
|
|
gock.New(utils.Docker.DaemonHost()).
|
|
Get("/v" + utils.Docker.ClientVersion() + "/containers").
|
|
Reply(http.StatusOK).
|
|
JSON(types.ContainerJSON{})
|
|
gock.New(utils.Docker.DaemonHost()).
|
|
Post("/v" + utils.Docker.ClientVersion() + "/containers").
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Setup mock postgres
|
|
conn := pgtest.NewConn()
|
|
defer conn.Close(t)
|
|
conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false").
|
|
Reply("ALTER DATABASE").
|
|
Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false").
|
|
Reply("ALTER DATABASE").
|
|
Query(reset.TERMINATE_BACKENDS).
|
|
Reply("SELECT 1").
|
|
Query(reset.COUNT_REPLICATION_SLOTS).
|
|
Reply("SELECT 1", []interface{}{0}).
|
|
Query("ALTER DATABASE postgres RENAME TO main;").
|
|
Reply("ALTER DATABASE").
|
|
Query("ALTER DATABASE " + branch + " RENAME TO postgres;").
|
|
Reply("ALTER DATABASE")
|
|
// Run test
|
|
assert.NoError(t, Run(context.Background(), branch, fsys, conn.Intercept))
|
|
// Validate output
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
contents, err := afero.ReadFile(fsys, utils.CurrBranchPath)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []byte(branch), contents)
|
|
})
|
|
|
|
t.Run("throws error on malformed config", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644))
|
|
// Run test
|
|
err := Run(context.Background(), "target", fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "toml: expected = after a key, but the document ends there")
|
|
})
|
|
|
|
t.Run("throws error on missing database", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
defer gock.OffAll()
|
|
gock.New(utils.Docker.DaemonHost()).
|
|
Get("/v" + utils.Docker.ClientVersion() + "/containers").
|
|
Reply(http.StatusNotFound)
|
|
// Run test
|
|
err := Run(context.Background(), "target", fsys)
|
|
// Check error
|
|
assert.ErrorIs(t, err, utils.ErrNotRunning)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on reserved branch", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
defer gock.OffAll()
|
|
gock.New(utils.Docker.DaemonHost()).
|
|
Get("/v" + utils.Docker.ClientVersion() + "/containers").
|
|
Reply(http.StatusOK).
|
|
JSON(types.ContainerJSON{})
|
|
// Run test
|
|
err := Run(context.Background(), "postgres", fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "branch name is reserved.")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on missing branch", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
defer gock.OffAll()
|
|
gock.New(utils.Docker.DaemonHost()).
|
|
Get("/v" + utils.Docker.ClientVersion() + "/containers").
|
|
Reply(http.StatusOK).
|
|
JSON(types.ContainerJSON{})
|
|
// Run test
|
|
err := Run(context.Background(), "main", fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Branch main does not exist.")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("noop on current branch", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
defer gock.OffAll()
|
|
gock.New(utils.Docker.DaemonHost()).
|
|
Get("/v" + utils.Docker.ClientVersion() + "/containers").
|
|
Reply(http.StatusOK).
|
|
JSON(types.ContainerJSON{})
|
|
// Setup target branch
|
|
branch := "main"
|
|
branchPath := filepath.Join(filepath.Dir(utils.CurrBranchPath), branch)
|
|
require.NoError(t, fsys.Mkdir(branchPath, 0755))
|
|
// Run test
|
|
assert.NoError(t, Run(context.Background(), branch, fsys))
|
|
// Check error
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
contents, err := afero.ReadFile(fsys, utils.CurrBranchPath)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []byte(branch), contents)
|
|
})
|
|
|
|
t.Run("throws error on failure to switch", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
defer gock.OffAll()
|
|
gock.New(utils.Docker.DaemonHost()).
|
|
Get("/v" + utils.Docker.ClientVersion() + "/containers").
|
|
Reply(http.StatusOK).
|
|
JSON(types.ContainerJSON{})
|
|
// Setup target branch
|
|
branch := "target"
|
|
branchPath := filepath.Join(filepath.Dir(utils.CurrBranchPath), branch)
|
|
require.NoError(t, fsys.Mkdir(branchPath, 0755))
|
|
// Setup mock postgres
|
|
conn := pgtest.NewConn()
|
|
// Run test
|
|
err := Run(context.Background(), branch, fsys, conn.Intercept)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Error switching to branch target")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on failure to write", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
defer gock.OffAll()
|
|
gock.New(utils.Docker.DaemonHost()).
|
|
Get("/v" + utils.Docker.ClientVersion() + "/containers").
|
|
Reply(http.StatusOK).
|
|
JSON(types.ContainerJSON{})
|
|
// Setup target branch
|
|
branch := "main"
|
|
branchPath := filepath.Join(filepath.Dir(utils.CurrBranchPath), branch)
|
|
require.NoError(t, fsys.Mkdir(branchPath, 0755))
|
|
// Run test
|
|
err := Run(context.Background(), branch, afero.NewReadOnlyFs(fsys))
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Unable to update local branch file.")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
}
|
|
|
|
func TestSwitchDatabase(t *testing.T) {
|
|
t.Run("throws error on failure to connect", func(t *testing.T) {
|
|
// Setup invalid port
|
|
utils.Config.Db.Port = 0
|
|
// Run test
|
|
err := switchDatabase(context.Background(), "main", "target")
|
|
// Check error
|
|
assert.ErrorContains(t, err, "invalid port")
|
|
})
|
|
|
|
t.Run("throws error on failure to disconnect", func(t *testing.T) {
|
|
// Setup valid config
|
|
utils.Config.Db.Port = 54322
|
|
// Setup mock postgres
|
|
conn := pgtest.NewConn()
|
|
defer conn.Close(t)
|
|
conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false").
|
|
ReplyError(pgerrcode.InvalidParameterValue, `cannot disallow connections for current database`).
|
|
Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false").
|
|
Query(reset.TERMINATE_BACKENDS)
|
|
// Run test
|
|
err := switchDatabase(context.Background(), "main", "target", conn.Intercept)
|
|
// Check error
|
|
assert.ErrorContains(t, err, pgerrcode.InvalidParameterValue)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on failure to backup", func(t *testing.T) {
|
|
// Setup valid config
|
|
utils.DbId = "test-switch"
|
|
utils.Config.Db.Port = 54322
|
|
// Setup mock postgres
|
|
conn := pgtest.NewConn()
|
|
defer conn.Close(t)
|
|
conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false").
|
|
Reply("ALTER DATABASE").
|
|
Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false").
|
|
Reply("ALTER DATABASE").
|
|
Query(reset.TERMINATE_BACKENDS).
|
|
Reply("SELECT 1").
|
|
Query(reset.COUNT_REPLICATION_SLOTS).
|
|
Reply("SELECT 1", []interface{}{0}).
|
|
Query("ALTER DATABASE postgres RENAME TO main;").
|
|
ReplyError(pgerrcode.DuplicateDatabase, `database "main" already exists`)
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
defer gock.OffAll()
|
|
gock.New(utils.Docker.DaemonHost()).
|
|
Post("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/restart").
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Run test
|
|
err := switchDatabase(context.Background(), "main", "target", conn.Intercept)
|
|
// Check error
|
|
assert.ErrorContains(t, err, pgerrcode.DuplicateDatabase)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on failure to rename", func(t *testing.T) {
|
|
// Setup valid config
|
|
utils.DbId = "test-switch"
|
|
utils.Config.Db.Port = 54322
|
|
// Setup mock postgres
|
|
conn := pgtest.NewConn()
|
|
defer conn.Close(t)
|
|
conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false").
|
|
Reply("ALTER DATABASE").
|
|
Query("ALTER DATABASE _supabase ALLOW_CONNECTIONS false").
|
|
Reply("ALTER DATABASE").
|
|
Query(reset.TERMINATE_BACKENDS).
|
|
Reply("SELECT 1").
|
|
Query(reset.COUNT_REPLICATION_SLOTS).
|
|
Reply("SELECT 1", []interface{}{0}).
|
|
Query("ALTER DATABASE postgres RENAME TO main;").
|
|
Reply("ALTER DATABASE").
|
|
Query("ALTER DATABASE target RENAME TO postgres;").
|
|
ReplyError(pgerrcode.InvalidCatalogName, `database "target" does not exist`).
|
|
// Attempt to rollback
|
|
Query("ALTER DATABASE main RENAME TO postgres;").
|
|
ReplyError(pgerrcode.DuplicateDatabase, `database "postgres" already exists`)
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
defer gock.OffAll()
|
|
gock.New(utils.Docker.DaemonHost()).
|
|
Post("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/restart").
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Run test
|
|
err := switchDatabase(context.Background(), "main", "target", conn.Intercept)
|
|
// Check error
|
|
assert.ErrorContains(t, err, pgerrcode.InvalidCatalogName)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
}
|