github.com/supabase/cli@v1.168.1/internal/migration/repair/repair_test.go (about) 1 package repair 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 12 "github.com/jackc/pgconn" 13 "github.com/jackc/pgerrcode" 14 "github.com/jackc/pgx/v4" 15 "github.com/spf13/afero" 16 "github.com/spf13/viper" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 "github.com/supabase/cli/internal/migration/history" 20 "github.com/supabase/cli/internal/testing/fstest" 21 "github.com/supabase/cli/internal/testing/pgtest" 22 "github.com/supabase/cli/internal/utils" 23 "github.com/supabase/cli/internal/utils/parser" 24 ) 25 26 var dbConfig = pgconn.Config{ 27 Host: "db.supabase.com", 28 Port: 5432, 29 User: "admin", 30 Password: "password", 31 Database: "postgres", 32 } 33 34 func TestRepairCommand(t *testing.T) { 35 t.Run("applies new version", func(t *testing.T) { 36 // Setup in-memory fs 37 fsys := afero.NewMemMapFs() 38 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 39 require.NoError(t, afero.WriteFile(fsys, path, []byte("select 1"), 0644)) 40 // Setup mock postgres 41 conn := pgtest.NewConn() 42 defer conn.Close(t) 43 pgtest.MockMigrationHistory(conn) 44 conn.Query(history.INSERT_MIGRATION_VERSION, "0", "test", []string{"select 1"}). 45 Reply("INSERT 0 1") 46 // Run test 47 err := Run(context.Background(), dbConfig, []string{"0"}, Applied, fsys, conn.Intercept) 48 // Check error 49 assert.NoError(t, err) 50 }) 51 52 t.Run("reverts old version", func(t *testing.T) { 53 // Setup in-memory fs 54 fsys := afero.NewMemMapFs() 55 // Setup mock postgres 56 conn := pgtest.NewConn() 57 defer conn.Close(t) 58 pgtest.MockMigrationHistory(conn) 59 conn.Query(history.DELETE_MIGRATION_VERSION, []string{"0"}). 60 Reply("DELETE 1") 61 // Run test 62 err := Run(context.Background(), dbConfig, []string{"0"}, Reverted, fsys, conn.Intercept) 63 // Check error 64 assert.NoError(t, err) 65 }) 66 67 t.Run("throws error on invalid version", func(t *testing.T) { 68 // Setup in-memory fs 69 fsys := afero.NewMemMapFs() 70 // Run test 71 err := Run(context.Background(), pgconn.Config{}, []string{"invalid"}, Applied, fsys) 72 // Check error 73 assert.ErrorIs(t, err, ErrInvalidVersion) 74 }) 75 76 t.Run("throws error on connect failure", func(t *testing.T) { 77 // Setup in-memory fs 78 fsys := afero.NewMemMapFs() 79 // Run test 80 err := Run(context.Background(), pgconn.Config{}, []string{"0"}, Applied, fsys) 81 // Check error 82 assert.ErrorContains(t, err, "invalid port (outside range)") 83 }) 84 85 t.Run("throws error on insert failure", func(t *testing.T) { 86 // Setup in-memory fs 87 fsys := afero.NewMemMapFs() 88 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 89 require.NoError(t, afero.WriteFile(fsys, path, []byte(""), 0644)) 90 // Setup mock postgres 91 conn := pgtest.NewConn() 92 defer conn.Close(t) 93 pgtest.MockMigrationHistory(conn) 94 conn.Query(history.INSERT_MIGRATION_VERSION, "0", "test", nil). 95 ReplyError(pgerrcode.DuplicateObject, `relation "supabase_migrations.schema_migrations" does not exist`) 96 // Run test 97 err := Run(context.Background(), dbConfig, []string{"0"}, Applied, fsys, conn.Intercept) 98 // Check error 99 assert.ErrorContains(t, err, `ERROR: relation "supabase_migrations.schema_migrations" does not exist (SQLSTATE 42710)`) 100 }) 101 } 102 103 func TestRepairAll(t *testing.T) { 104 t.Run("repairs whole history", func(t *testing.T) { 105 defer fstest.MockStdin(t, "y")() 106 // Setup in-memory fs 107 fsys := afero.NewMemMapFs() 108 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 109 require.NoError(t, afero.WriteFile(fsys, path, []byte("select 1"), 0644)) 110 // Setup mock postgres 111 conn := pgtest.NewConn() 112 defer conn.Close(t) 113 pgtest.MockMigrationHistory(conn) 114 conn.Query(history.TRUNCATE_VERSION_TABLE + `;INSERT INTO supabase_migrations.schema_migrations(version, name, statements) VALUES( '0' , 'test' , '{select 1}' )`). 115 Reply("TRUNCATE TABLE"). 116 Reply("INSERT 0 1") 117 // Run test 118 err := Run(context.Background(), dbConfig, nil, Applied, fsys, conn.Intercept, func(cc *pgx.ConnConfig) { 119 cc.PreferSimpleProtocol = true 120 }) 121 // Check error 122 assert.NoError(t, err) 123 }) 124 125 t.Run("reverts whole history", func(t *testing.T) { 126 defer fstest.MockStdin(t, "y")() 127 // Setup in-memory fs 128 fsys := afero.NewMemMapFs() 129 // Setup mock postgres 130 conn := pgtest.NewConn() 131 defer conn.Close(t) 132 pgtest.MockMigrationHistory(conn) 133 conn.Query(history.TRUNCATE_VERSION_TABLE). 134 Reply("TRUNCATE TABLE") 135 // Run test 136 err := Run(context.Background(), dbConfig, nil, Reverted, fsys, conn.Intercept) 137 // Check error 138 assert.NoError(t, err) 139 }) 140 141 t.Run("throws error on cancel", func(t *testing.T) { 142 // Setup in-memory fs 143 fsys := afero.NewMemMapFs() 144 // Run test 145 err := Run(context.Background(), dbConfig, nil, Applied, fsys) 146 // Check error 147 assert.ErrorIs(t, err, context.Canceled) 148 }) 149 150 t.Run("throws error on permission denied", func(t *testing.T) { 151 defer fstest.MockStdin(t, "y")() 152 // Setup in-memory fs 153 fsys := &fstest.OpenErrorFs{DenyPath: utils.MigrationsDir} 154 // Run test 155 err := Run(context.Background(), dbConfig, nil, Applied, fsys) 156 // Check error 157 assert.ErrorIs(t, err, os.ErrPermission) 158 }) 159 } 160 161 func TestMigrationFile(t *testing.T) { 162 t.Run("new from file sets max token", func(t *testing.T) { 163 viper.Reset() 164 // Setup in-memory fs 165 fsys := afero.NewMemMapFs() 166 // Setup initial migration 167 name := "20220727064247_create_table.sql" 168 path := filepath.Join(utils.MigrationsDir, name) 169 query := "BEGIN; " + strings.Repeat("a", parser.MaxScannerCapacity) 170 require.NoError(t, afero.WriteFile(fsys, path, []byte(query), 0644)) 171 // Run test 172 migration, err := NewMigrationFromFile(path, fsys) 173 // Check error 174 assert.NoError(t, err) 175 assert.Len(t, migration.Lines, 2) 176 assert.Equal(t, "20220727064247", migration.Version) 177 }) 178 179 t.Run("new from reader errors on max token", func(t *testing.T) { 180 viper.Reset() 181 sql := "\tBEGIN; " + strings.Repeat("a", parser.MaxScannerCapacity) 182 // Run test 183 migration, err := NewMigrationFromReader(strings.NewReader(sql)) 184 // Check error 185 assert.ErrorIs(t, err, bufio.ErrTooLong) 186 assert.ErrorContains(t, err, "After statement 1: \tBEGIN;") 187 assert.Nil(t, migration) 188 }) 189 190 t.Run("encodes statements in binary format", func(t *testing.T) { 191 migration := MigrationFile{ 192 Lines: []string{"create schema public"}, 193 Version: "0", 194 } 195 // Setup mock postgres 196 conn := pgtest.NewConn() 197 defer conn.Close(t) 198 conn.Query(migration.Lines[0]). 199 Reply("CREATE SCHEMA"). 200 Query(history.INSERT_MIGRATION_VERSION, "0", "", migration.Lines). 201 Reply("INSERT 0 1") 202 // Connect to mock 203 ctx := context.Background() 204 mock, err := utils.ConnectByConfig(ctx, dbConfig, conn.Intercept) 205 require.NoError(t, err) 206 defer mock.Close(ctx) 207 // Run test 208 err = migration.ExecBatch(context.Background(), mock) 209 // Check error 210 assert.NoError(t, err) 211 }) 212 213 t.Run("throws error on insert failure", func(t *testing.T) { 214 migration := MigrationFile{ 215 Lines: []string{"create schema public"}, 216 Version: "0", 217 } 218 // Setup mock postgres 219 conn := pgtest.NewConn() 220 defer conn.Close(t) 221 conn.Query(migration.Lines[0]). 222 ReplyError(pgerrcode.DuplicateSchema, `schema "public" already exists`). 223 Query(history.INSERT_MIGRATION_VERSION, "0", "", fmt.Sprintf("{%s}", migration.Lines[0])). 224 Reply("INSERT 0 1") 225 // Connect to mock via text protocol 226 ctx := context.Background() 227 mock, err := utils.ConnectByConfig(ctx, dbConfig, conn.Intercept, func(cc *pgx.ConnConfig) { 228 cc.PreferSimpleProtocol = true 229 }) 230 require.NoError(t, err) 231 defer mock.Close(ctx) 232 // Run test 233 err = migration.ExecBatch(context.Background(), mock) 234 // Check error 235 assert.ErrorContains(t, err, "ERROR: schema \"public\" already exists (SQLSTATE 42P06)") 236 assert.ErrorContains(t, err, "At statement 0: create schema public") 237 }) 238 }