github.com/Redstoneguy129/cli@v0.0.0-20230211220159-15dca4e91917/internal/db/reset/reset_test.go (about) 1 package reset 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "os" 8 "path/filepath" 9 "testing" 10 "time" 11 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/client" 14 "github.com/jackc/pgerrcode" 15 "github.com/spf13/afero" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 "github.com/Redstoneguy129/cli/internal/testing/apitest" 19 "github.com/Redstoneguy129/cli/internal/testing/pgtest" 20 "github.com/Redstoneguy129/cli/internal/utils" 21 "gopkg.in/h2non/gock.v1" 22 ) 23 24 func TestResetCommand(t *testing.T) { 25 t.Run("throws error on missing config", func(t *testing.T) { 26 err := Run(context.Background(), afero.NewMemMapFs()) 27 assert.ErrorIs(t, err, os.ErrNotExist) 28 }) 29 30 t.Run("throws error on db is not started", func(t *testing.T) { 31 // Setup in-memory fs 32 fsys := afero.NewMemMapFs() 33 require.NoError(t, utils.WriteConfig(fsys, false)) 34 // Setup mock docker 35 require.NoError(t, apitest.MockDocker(utils.Docker)) 36 defer gock.OffAll() 37 gock.New(utils.Docker.DaemonHost()). 38 Get("/v" + utils.Docker.ClientVersion() + "/containers"). 39 Reply(http.StatusServiceUnavailable) 40 // Run test 41 err := Run(context.Background(), fsys) 42 // Check error 43 assert.ErrorContains(t, err, "supabase start is not running.") 44 assert.Empty(t, apitest.ListUnmatchedRequests()) 45 }) 46 47 t.Run("throws error on failure to recreate", func(t *testing.T) { 48 // Setup in-memory fs 49 fsys := afero.NewMemMapFs() 50 require.NoError(t, utils.WriteConfig(fsys, false)) 51 // Setup mock docker 52 require.NoError(t, client.WithHTTPClient(http.DefaultClient)(utils.Docker)) 53 defer gock.OffAll() 54 gock.New(utils.Docker.DaemonHost()). 55 Get("/v" + utils.Docker.ClientVersion() + "/containers"). 56 Reply(http.StatusOK). 57 JSON(types.ContainerJSON{}) 58 // Setup mock postgres 59 conn := pgtest.NewConn() 60 defer conn.Close(t) 61 conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). 62 ReplyError(pgerrcode.InvalidParameterValue, `cannot disallow connections for current database`) 63 // Run test 64 err := Run(context.Background(), fsys, conn.Intercept) 65 // Check error 66 assert.ErrorContains(t, err, "ERROR: cannot disallow connections for current database (SQLSTATE 22023)") 67 assert.Empty(t, apitest.ListUnmatchedRequests()) 68 }) 69 } 70 71 func TestResetDatabase(t *testing.T) { 72 t.Run("initialises postgres database", func(t *testing.T) { 73 utils.Config.Db.Port = 54322 74 utils.InitialSchemaSql = "CREATE SCHEMA public" 75 // Setup in-memory fs 76 fsys := afero.NewMemMapFs() 77 // Setup mock postgres 78 conn := pgtest.NewConn() 79 defer conn.Close(t) 80 conn.Query(utils.InitialSchemaSql). 81 Reply("CREATE SCHEMA") 82 // Run test 83 assert.NoError(t, resetDatabase(context.Background(), fsys, conn.Intercept)) 84 }) 85 86 t.Run("throws error on connect failure", func(t *testing.T) { 87 utils.Config.Db.Port = 0 88 // Setup in-memory fs 89 fsys := afero.NewMemMapFs() 90 // Run test 91 err := resetDatabase(context.Background(), fsys) 92 // Check error 93 assert.ErrorContains(t, err, "invalid port") 94 }) 95 96 t.Run("throws error on duplicate schema", func(t *testing.T) { 97 utils.Config.Db.Port = 54322 98 utils.InitialSchemaSql = "CREATE SCHEMA public" 99 // Setup in-memory fs 100 fsys := afero.NewMemMapFs() 101 // Setup mock postgres 102 conn := pgtest.NewConn() 103 defer conn.Close(t) 104 conn.Query(utils.InitialSchemaSql). 105 ReplyError(pgerrcode.DuplicateSchema, `schema "public" already exists`) 106 // Run test 107 err := resetDatabase(context.Background(), fsys, conn.Intercept) 108 // Check error 109 assert.ErrorContains(t, err, `ERROR: schema "public" already exists (SQLSTATE 42P06)`) 110 }) 111 112 t.Run("throws error on migration failure", func(t *testing.T) { 113 utils.Config.Db.Port = 54322 114 utils.InitialSchemaSql = "CREATE SCHEMA public" 115 // Setup in-memory fs 116 fsys := afero.NewMemMapFs() 117 path := filepath.Join(utils.MigrationsDir, "0_table.sql") 118 sql := "CREATE TABLE example()" 119 require.NoError(t, afero.WriteFile(fsys, path, []byte(sql), 0644)) 120 // Setup mock postgres 121 conn := pgtest.NewConn() 122 defer conn.Close(t) 123 conn.Query(utils.InitialSchemaSql). 124 Reply("CREATE SCHEMA"). 125 Query(sql). 126 ReplyError(pgerrcode.DuplicateObject, `table "example" already exists`) 127 // Run test 128 err := resetDatabase(context.Background(), fsys, conn.Intercept) 129 // Check error 130 assert.ErrorContains(t, err, `ERROR: table "example" already exists (SQLSTATE 42710)`) 131 }) 132 } 133 134 func TestSeedDatabase(t *testing.T) { 135 t.Run("seeds from file", func(t *testing.T) { 136 // Setup in-memory fs 137 fsys := afero.NewMemMapFs() 138 // Setup seed file 139 sql := "INSERT INTO employees(name) VALUES ('Alice')" 140 require.NoError(t, afero.WriteFile(fsys, utils.SeedDataPath, []byte(sql), 0644)) 141 // Setup mock postgres 142 conn := pgtest.NewConn() 143 defer conn.Close(t) 144 conn.Query(sql). 145 Reply("INSERT 0 1") 146 // Connect to mock 147 ctx := context.Background() 148 mock, err := utils.ConnectLocalPostgres(ctx, utils.Config.Hostname, 54322, "postgres", conn.Intercept) 149 require.NoError(t, err) 150 defer mock.Close(ctx) 151 // Run test 152 assert.NoError(t, SeedDatabase(ctx, mock, fsys)) 153 }) 154 155 t.Run("ignores missing seed", func(t *testing.T) { 156 assert.NoError(t, SeedDatabase(context.Background(), nil, afero.NewMemMapFs())) 157 }) 158 159 t.Run("throws error on insert failure", func(t *testing.T) { 160 // Setup in-memory fs 161 fsys := afero.NewMemMapFs() 162 // Setup seed file 163 sql := "INSERT INTO employees(name) VALUES ('Alice')" 164 require.NoError(t, afero.WriteFile(fsys, utils.SeedDataPath, []byte(sql), 0644)) 165 // Setup mock postgres 166 conn := pgtest.NewConn() 167 defer conn.Close(t) 168 conn.Query(sql). 169 ReplyError(pgerrcode.NotNullViolation, `null value in column "age" of relation "employees"`) 170 // Connect to mock 171 ctx := context.Background() 172 mock, err := utils.ConnectLocalPostgres(ctx, utils.Config.Hostname, 54322, "postgres", conn.Intercept) 173 require.NoError(t, err) 174 defer mock.Close(ctx) 175 // Run test 176 err = SeedDatabase(ctx, mock, fsys) 177 // Check error 178 assert.ErrorContains(t, err, `ERROR: null value in column "age" of relation "employees" (SQLSTATE 23502)`) 179 }) 180 } 181 182 func TestRecreateDatabase(t *testing.T) { 183 t.Run("resets postgres database", func(t *testing.T) { 184 utils.Config.Db.Port = 54322 185 // Setup mock postgres 186 conn := pgtest.NewConn() 187 defer conn.Close(t) 188 conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). 189 Reply("ALTER DATABASE"). 190 Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")). 191 Reply("DO"). 192 Query("DROP DATABASE IF EXISTS postgres WITH (FORCE);"). 193 Reply("DROP DATABASE"). 194 Query("CREATE DATABASE postgres;"). 195 Reply("CREATE DATABASE") 196 // Run test 197 assert.NoError(t, RecreateDatabase(context.Background(), conn.Intercept)) 198 }) 199 200 t.Run("throws error on invalid port", func(t *testing.T) { 201 utils.Config.Db.Port = 0 202 assert.ErrorContains(t, RecreateDatabase(context.Background()), "invalid port") 203 }) 204 205 t.Run("continues on disconnecting missing database", func(t *testing.T) { 206 utils.Config.Db.Port = 54322 207 // Setup mock postgres 208 conn := pgtest.NewConn() 209 defer conn.Close(t) 210 conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). 211 ReplyError(pgerrcode.InvalidCatalogName, `database "postgres" does not exist`). 212 Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")). 213 ReplyError(pgerrcode.UndefinedTable, `relation "pg_stat_activity" does not exist`) 214 // Run test 215 err := RecreateDatabase(context.Background(), conn.Intercept) 216 // Check error 217 assert.ErrorContains(t, err, `ERROR: relation "pg_stat_activity" does not exist (SQLSTATE 42P01)`) 218 }) 219 220 t.Run("throws error on failure to disconnect", func(t *testing.T) { 221 utils.Config.Db.Port = 54322 222 // Setup mock postgres 223 conn := pgtest.NewConn() 224 defer conn.Close(t) 225 conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). 226 ReplyError(pgerrcode.InvalidParameterValue, `cannot disallow connections for current database`) 227 // Run test 228 err := RecreateDatabase(context.Background(), conn.Intercept) 229 // Check error 230 assert.ErrorContains(t, err, "ERROR: cannot disallow connections for current database (SQLSTATE 22023)") 231 }) 232 233 t.Run("throws error on failure to drop", func(t *testing.T) { 234 utils.Config.Db.Port = 54322 235 // Setup mock postgres 236 conn := pgtest.NewConn() 237 defer conn.Close(t) 238 conn.Query("ALTER DATABASE postgres ALLOW_CONNECTIONS false;"). 239 Reply("ALTER DATABASE"). 240 Query(fmt.Sprintf(utils.TerminateDbSqlFmt, "postgres")). 241 Reply("DO"). 242 Query("DROP DATABASE IF EXISTS postgres WITH (FORCE);"). 243 ReplyError(pgerrcode.ObjectInUse, `database "postgres" is used by an active logical replication slot`) 244 // Run test 245 err := RecreateDatabase(context.Background(), conn.Intercept) 246 // Check error 247 assert.ErrorContains(t, err, `ERROR: database "postgres" is used by an active logical replication slot (SQLSTATE 55006)`) 248 }) 249 } 250 251 func TestRestartDatabase(t *testing.T) { 252 t.Run("restarts affected services", func(t *testing.T) { 253 utils.DbId = "test-reset" 254 // Setup mock docker 255 require.NoError(t, apitest.MockDocker(utils.Docker)) 256 defer gock.OffAll() 257 // Restarts storage api 258 gock.New(utils.Docker.DaemonHost()). 259 Post("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/restart"). 260 Reply(http.StatusOK) 261 gock.New(utils.Docker.DaemonHost()). 262 Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json"). 263 Reply(http.StatusOK). 264 JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ 265 State: &types.ContainerState{ 266 Running: true, 267 Health: &types.Health{Status: "healthy"}, 268 }, 269 }}) 270 // Restarts postgREST 271 utils.RestId = "test-rest" 272 gock.New(utils.Docker.DaemonHost()). 273 Post("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.RestId + "/kill"). 274 Reply(http.StatusOK) 275 utils.StorageId = "test-storage" 276 gock.New(utils.Docker.DaemonHost()). 277 Post("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.StorageId + "/restart"). 278 Reply(http.StatusServiceUnavailable) 279 // Run test 280 RestartDatabase(context.Background()) 281 // Check error 282 assert.Empty(t, apitest.ListUnmatchedRequests()) 283 }) 284 285 t.Run("timeout health check", func(t *testing.T) { 286 utils.DbId = "test-reset" 287 healthTimeout = 0 * time.Second 288 // Setup mock docker 289 require.NoError(t, apitest.MockDocker(utils.Docker)) 290 defer gock.OffAll() 291 gock.New(utils.Docker.DaemonHost()). 292 Post("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/restart"). 293 Reply(http.StatusOK) 294 // Run test 295 RestartDatabase(context.Background()) 296 // Check error 297 assert.Empty(t, apitest.ListUnmatchedRequests()) 298 }) 299 }