github.com/supabase/cli@v1.168.1/internal/db/start/start_test.go (about)

     1  package start
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"testing"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/api/types/volume"
    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/fstest"
    18  	"github.com/supabase/cli/internal/testing/pgtest"
    19  	"github.com/supabase/cli/internal/utils"
    20  	"gopkg.in/h2non/gock.v1"
    21  )
    22  
    23  func TestInitBranch(t *testing.T) {
    24  	t.Run("throws error on permission denied", func(t *testing.T) {
    25  		// Setup in-memory fs
    26  		fsys := afero.NewReadOnlyFs(afero.NewMemMapFs())
    27  		// Run test
    28  		err := initCurrentBranch(fsys)
    29  		// Check error
    30  		assert.ErrorContains(t, err, "operation not permitted")
    31  	})
    32  
    33  	t.Run("throws error on stat failure", func(t *testing.T) {
    34  		// Setup in-memory fs
    35  		fsys := &fstest.StatErrorFs{DenyPath: utils.CurrBranchPath}
    36  		// Run test
    37  		err := initCurrentBranch(fsys)
    38  		// Check error
    39  		assert.ErrorContains(t, err, "permission denied")
    40  	})
    41  
    42  	t.Run("throws error on write failure", func(t *testing.T) {
    43  		// Setup in-memory fs
    44  		fsys := &fstest.OpenErrorFs{DenyPath: utils.CurrBranchPath}
    45  		// Run test
    46  		err := initCurrentBranch(fsys)
    47  		// Check error
    48  		assert.ErrorContains(t, err, "permission denied")
    49  	})
    50  }
    51  
    52  func TestStartDatabase(t *testing.T) {
    53  	t.Run("initialize main branch", func(t *testing.T) {
    54  		utils.Config.Db.MajorVersion = 15
    55  		utils.Config.Db.Image = utils.Pg15Image
    56  		utils.DbId = "supabase_db_test"
    57  		utils.ConfigId = "supabase_config_test"
    58  		utils.Config.Db.Port = 5432
    59  		// Setup in-memory fs
    60  		fsys := afero.NewMemMapFs()
    61  		roles := "create role test"
    62  		require.NoError(t, afero.WriteFile(fsys, utils.CustomRolesPath, []byte(roles), 0644))
    63  		seed := "INSERT INTO employees(name) VALUES ('Alice')"
    64  		require.NoError(t, afero.WriteFile(fsys, utils.SeedDataPath, []byte(seed), 0644))
    65  		// Setup mock docker
    66  		require.NoError(t, apitest.MockDocker(utils.Docker))
    67  		defer gock.OffAll()
    68  		gock.New(utils.Docker.DaemonHost()).
    69  			Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId).
    70  			Reply(http.StatusNotFound).
    71  			JSON(volume.Volume{})
    72  		apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Db.Image), utils.DbId)
    73  		gock.New(utils.Docker.DaemonHost()).
    74  			Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json").
    75  			Reply(http.StatusOK).
    76  			JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{
    77  				State: &types.ContainerState{
    78  					Running: true,
    79  					Health:  &types.Health{Status: "healthy"},
    80  				},
    81  			}})
    82  		apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.RealtimeImage), "test-realtime")
    83  		require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-realtime", ""))
    84  		apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.StorageImage), "test-storage")
    85  		require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-storage", ""))
    86  		apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.GotrueImage), "test-auth")
    87  		require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-auth", ""))
    88  		// Setup mock postgres
    89  		conn := pgtest.NewConn()
    90  		defer conn.Close(t)
    91  		conn.Query(roles).
    92  			Reply("CREATE ROLE").
    93  			Query(seed).
    94  			Reply("INSERT 0 1")
    95  		// Run test
    96  		err := StartDatabase(context.Background(), fsys, io.Discard, conn.Intercept)
    97  		// Check error
    98  		assert.NoError(t, err)
    99  		assert.Empty(t, apitest.ListUnmatchedRequests())
   100  		// Check current branch
   101  		contents, err := afero.ReadFile(fsys, utils.CurrBranchPath)
   102  		assert.NoError(t, err)
   103  		assert.Equal(t, []byte("main"), contents)
   104  	})
   105  
   106  	t.Run("recover from backup volume", func(t *testing.T) {
   107  		utils.Config.Db.MajorVersion = 14
   108  		utils.Config.Db.Image = utils.Pg15Image
   109  		utils.DbId = "supabase_db_test"
   110  		utils.ConfigId = "supabase_config_test"
   111  		utils.Config.Db.Port = 5432
   112  		// Setup in-memory fs
   113  		fsys := afero.NewMemMapFs()
   114  		// Setup mock docker
   115  		require.NoError(t, apitest.MockDocker(utils.Docker))
   116  		defer gock.OffAll()
   117  		gock.New(utils.Docker.DaemonHost()).
   118  			Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId).
   119  			Reply(http.StatusOK).
   120  			JSON(volume.Volume{})
   121  		apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Db.Image), utils.DbId)
   122  		gock.New(utils.Docker.DaemonHost()).
   123  			Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json").
   124  			Reply(http.StatusOK).
   125  			JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{
   126  				State: &types.ContainerState{
   127  					Running: true,
   128  					Health:  &types.Health{Status: "healthy"},
   129  				},
   130  			}})
   131  		// Run test
   132  		err := StartDatabase(context.Background(), fsys, io.Discard)
   133  		// Check error
   134  		assert.NoError(t, err)
   135  		assert.Empty(t, apitest.ListUnmatchedRequests())
   136  		// Check current branch
   137  		contents, err := afero.ReadFile(fsys, utils.CurrBranchPath)
   138  		assert.NoError(t, err)
   139  		assert.Equal(t, []byte("main"), contents)
   140  	})
   141  
   142  	t.Run("throws error on start failure", func(t *testing.T) {
   143  		utils.Config.Db.MajorVersion = 15
   144  		utils.Config.Db.Image = utils.Pg15Image
   145  		utils.DbId = "supabase_db_test"
   146  		// Setup in-memory fs
   147  		fsys := afero.NewMemMapFs()
   148  		// Setup mock docker
   149  		require.NoError(t, apitest.MockDocker(utils.Docker))
   150  		defer gock.OffAll()
   151  		gock.New(utils.Docker.DaemonHost()).
   152  			Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId).
   153  			ReplyError(errors.New("network error"))
   154  		gock.New(utils.Docker.DaemonHost()).
   155  			Get("/v" + utils.Docker.ClientVersion() + "/images/" + utils.GetRegistryImageUrl(utils.Config.Db.Image) + "/json").
   156  			Reply(http.StatusInternalServerError)
   157  		// Run test
   158  		err := StartDatabase(context.Background(), fsys, io.Discard)
   159  		// Check error
   160  		assert.ErrorContains(t, err, "request returned Internal Server Error for API route and version")
   161  		assert.Empty(t, apitest.ListUnmatchedRequests())
   162  	})
   163  }
   164  
   165  func TestStartCommand(t *testing.T) {
   166  	t.Run("throws error on missing config", func(t *testing.T) {
   167  		// Setup in-memory fs
   168  		fsys := afero.NewMemMapFs()
   169  		// Run test
   170  		err := Run(context.Background(), fsys)
   171  		// Check error
   172  		assert.ErrorIs(t, err, os.ErrNotExist)
   173  	})
   174  
   175  	t.Run("throws error on missing docker", func(t *testing.T) {
   176  		// Setup in-memory fs
   177  		fsys := afero.NewMemMapFs()
   178  		require.NoError(t, utils.WriteConfig(fsys, false))
   179  		// Setup mock docker
   180  		require.NoError(t, apitest.MockDocker(utils.Docker))
   181  		defer gock.OffAll()
   182  		gock.New(utils.Docker.DaemonHost()).
   183  			Get("/v" + utils.Docker.ClientVersion() + "/containers").
   184  			ReplyError(errors.New("network error"))
   185  		// Run test
   186  		err := Run(context.Background(), fsys)
   187  		// Check error
   188  		assert.ErrorContains(t, err, "network error")
   189  		assert.Empty(t, apitest.ListUnmatchedRequests())
   190  	})
   191  
   192  	t.Run("exits if already started", func(t *testing.T) {
   193  		// Setup in-memory fs
   194  		fsys := afero.NewMemMapFs()
   195  		require.NoError(t, utils.WriteConfig(fsys, false))
   196  		// Setup mock docker
   197  		require.NoError(t, apitest.MockDocker(utils.Docker))
   198  		defer gock.OffAll()
   199  		gock.New(utils.Docker.DaemonHost()).
   200  			Get("/v" + utils.Docker.ClientVersion() + "/containers/").
   201  			Reply(http.StatusOK).
   202  			JSON(types.ContainerJSON{})
   203  		// Run test
   204  		err := Run(context.Background(), fsys)
   205  		// Check error
   206  		assert.NoError(t, err)
   207  		assert.Empty(t, apitest.ListUnmatchedRequests())
   208  	})
   209  
   210  	t.Run("throws error on start failure", func(t *testing.T) {
   211  		// Setup in-memory fs
   212  		fsys := afero.NewMemMapFs()
   213  		require.NoError(t, utils.WriteConfig(fsys, false))
   214  		// Setup mock docker
   215  		require.NoError(t, apitest.MockDocker(utils.Docker))
   216  		defer gock.OffAll()
   217  		gock.New(utils.Docker.DaemonHost()).
   218  			Get("/v" + utils.Docker.ClientVersion() + "/containers/").
   219  			Reply(http.StatusNotFound)
   220  		// Fail to start
   221  		gock.New(utils.Docker.DaemonHost()).
   222  			Get("/v" + utils.Docker.ClientVersion() + "/volumes/").
   223  			ReplyError(errors.New("network error"))
   224  		gock.New(utils.Docker.DaemonHost()).
   225  			Get("/v" + utils.Docker.ClientVersion() + "/images/" + utils.GetRegistryImageUrl(utils.Pg15Image) + "/json").
   226  			ReplyError(errors.New("network error"))
   227  		// Cleanup resources
   228  		apitest.MockDockerStop(utils.Docker)
   229  		// Run test
   230  		err := Run(context.Background(), fsys)
   231  		// Check error
   232  		assert.ErrorContains(t, err, "network error")
   233  		assert.Empty(t, apitest.ListUnmatchedRequests())
   234  	})
   235  }
   236  
   237  func TestSetupDatabase(t *testing.T) {
   238  	utils.Config.Db.MajorVersion = 15
   239  
   240  	t.Run("initializes database 14", func(t *testing.T) {
   241  		utils.Config.Db.MajorVersion = 14
   242  		defer func() {
   243  			utils.Config.Db.MajorVersion = 15
   244  		}()
   245  		utils.Config.Db.Port = 5432
   246  		utils.GlobalsSql = "create schema public"
   247  		utils.InitialSchemaSql = "create schema private"
   248  		// Setup in-memory fs
   249  		fsys := afero.NewMemMapFs()
   250  		roles := "create role postgres"
   251  		require.NoError(t, afero.WriteFile(fsys, utils.CustomRolesPath, []byte(roles), 0644))
   252  		// Setup mock postgres
   253  		conn := pgtest.NewConn()
   254  		defer conn.Close(t)
   255  		conn.Query(utils.GlobalsSql).
   256  			Reply("CREATE SCHEMA").
   257  			Query(utils.InitialSchemaSql).
   258  			Reply("CREATE SCHEMA").
   259  			Query(roles).
   260  			Reply("CREATE ROLE")
   261  		// Run test
   262  		err := setupDatabase(context.Background(), fsys, io.Discard, conn.Intercept)
   263  		// Check error
   264  		assert.NoError(t, err)
   265  		assert.Empty(t, apitest.ListUnmatchedRequests())
   266  	})
   267  
   268  	t.Run("throws error on connect failure", func(t *testing.T) {
   269  		utils.Config.Db.Port = 0
   270  		// Run test
   271  		err := setupDatabase(context.Background(), nil, io.Discard)
   272  		// Check error
   273  		assert.ErrorContains(t, err, "invalid port (outside range)")
   274  	})
   275  
   276  	t.Run("throws error on init failure", func(t *testing.T) {
   277  		utils.Config.Db.Port = 5432
   278  		// Setup mock docker
   279  		require.NoError(t, apitest.MockDocker(utils.Docker))
   280  		defer gock.OffAll()
   281  		gock.New(utils.Docker.DaemonHost()).
   282  			Get("/v" + utils.Docker.ClientVersion() + "/images/" + utils.GetRegistryImageUrl(utils.RealtimeImage) + "/json").
   283  			ReplyError(errors.New("network error"))
   284  		// Setup mock postgres
   285  		conn := pgtest.NewConn()
   286  		defer conn.Close(t)
   287  		// Run test
   288  		err := setupDatabase(context.Background(), nil, io.Discard, conn.Intercept)
   289  		// Check error
   290  		assert.ErrorContains(t, err, "network error")
   291  		assert.Empty(t, apitest.ListUnmatchedRequests())
   292  	})
   293  
   294  	t.Run("throws error on read failure", func(t *testing.T) {
   295  		utils.Config.Db.Port = 5432
   296  		// Setup in-memory fs
   297  		fsys := &fstest.OpenErrorFs{DenyPath: utils.CustomRolesPath}
   298  		// Setup mock docker
   299  		require.NoError(t, apitest.MockDocker(utils.Docker))
   300  		defer gock.OffAll()
   301  		apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.RealtimeImage), "test-realtime")
   302  		require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-realtime", ""))
   303  		apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.StorageImage), "test-storage")
   304  		require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-storage", ""))
   305  		apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.GotrueImage), "test-auth")
   306  		require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-auth", ""))
   307  		// Setup mock postgres
   308  		conn := pgtest.NewConn()
   309  		defer conn.Close(t)
   310  		// Run test
   311  		err := setupDatabase(context.Background(), fsys, io.Discard, conn.Intercept)
   312  		// Check error
   313  		assert.ErrorIs(t, err, os.ErrPermission)
   314  		assert.Empty(t, apitest.ListUnmatchedRequests())
   315  	})
   316  }