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 }