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 }