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