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 }