github.com/Redstoneguy129/cli@v0.0.0-20230211220159-15dca4e91917/internal/db/push/push_test.go (about) 1 package push 2 3 import ( 4 "bytes" 5 "context" 6 "path/filepath" 7 "testing" 8 9 "github.com/jackc/pgerrcode" 10 "github.com/spf13/afero" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 "github.com/Redstoneguy129/cli/internal/migration/list" 14 "github.com/Redstoneguy129/cli/internal/migration/repair" 15 "github.com/Redstoneguy129/cli/internal/testing/pgtest" 16 "github.com/Redstoneguy129/cli/internal/utils" 17 "github.com/Redstoneguy129/cli/internal/utils/parser" 18 ) 19 20 const ( 21 user = "admin" 22 pass = "password" 23 database = "postgres" 24 host = "localhost" 25 ) 26 27 func TestMigrationPush(t *testing.T) { 28 t.Run("dry run", func(t *testing.T) { 29 // Setup in-memory fs 30 fsys := afero.NewMemMapFs() 31 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 32 require.NoError(t, afero.WriteFile(fsys, path, []byte(""), 0644)) 33 // Setup mock postgres 34 conn := pgtest.NewConn() 35 defer conn.Close(t) 36 conn.Query(list.LIST_MIGRATION_VERSION). 37 Reply("SELECT 0") 38 // Run test 39 err := Run(context.Background(), true, user, pass, database, host, fsys, conn.Intercept) 40 // Check error 41 assert.NoError(t, err) 42 }) 43 44 t.Run("ignores up to date", func(t *testing.T) { 45 // Setup in-memory fs 46 fsys := afero.NewMemMapFs() 47 // Setup mock postgres 48 conn := pgtest.NewConn() 49 defer conn.Close(t) 50 conn.Query(list.LIST_MIGRATION_VERSION). 51 Reply("SELECT 0") 52 // Run test 53 err := Run(context.Background(), false, user, pass, database, host, fsys, conn.Intercept) 54 // Check error 55 assert.NoError(t, err) 56 }) 57 58 t.Run("throws error on connect failure", func(t *testing.T) { 59 // Setup in-memory fs 60 fsys := afero.NewMemMapFs() 61 // Run test 62 err := Run(context.Background(), false, user, pass, database, "0", fsys) 63 // Check error 64 assert.ErrorContains(t, err, "dial error (dial tcp 0.0.0.0:6543: connect: connection refused)") 65 }) 66 67 t.Run("throws error on remote load failure", func(t *testing.T) { 68 // Setup in-memory fs 69 fsys := afero.NewMemMapFs() 70 // Setup mock postgres 71 conn := pgtest.NewConn() 72 defer conn.Close(t) 73 conn.Query(list.LIST_MIGRATION_VERSION). 74 ReplyError(pgerrcode.UndefinedTable, `relation "supabase_migrations.schema_migrations" does not exist`) 75 // Run test 76 err := Run(context.Background(), false, user, pass, database, host, fsys, conn.Intercept) 77 // Check error 78 assert.ErrorContains(t, err, `ERROR: relation "supabase_migrations.schema_migrations" does not exist (SQLSTATE 42P01)`) 79 }) 80 81 t.Run("throws error on push failure", func(t *testing.T) { 82 // Setup in-memory fs 83 fsys := afero.NewMemMapFs() 84 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 85 require.NoError(t, afero.WriteFile(fsys, path, []byte(""), 0644)) 86 // Setup mock postgres 87 conn := pgtest.NewConn() 88 defer conn.Close(t) 89 conn.Query(list.LIST_MIGRATION_VERSION). 90 Reply("SELECT 0"). 91 Query(repair.INSERT_MIGRATION_VERSION, "0"). 92 ReplyError(pgerrcode.NotNullViolation, `null value in column "version" of relation "schema_migrations"`) 93 // Run test 94 err := Run(context.Background(), false, user, pass, database, host, fsys, conn.Intercept) 95 // Check error 96 assert.ErrorContains(t, err, `ERROR: null value in column "version" of relation "schema_migrations" (SQLSTATE 23502)`) 97 assert.ErrorContains(t, err, "At statement 0: "+repair.INSERT_MIGRATION_VERSION) 98 }) 99 } 100 101 func TestPendingMigrations(t *testing.T) { 102 t.Run("finds pending migrations", func(t *testing.T) { 103 // Setup in-memory fs 104 fsys := afero.NewMemMapFs() 105 files := []string{ 106 "20221201000000_test.sql", 107 "20221201000001_test.sql", 108 "20221201000002_test.sql", 109 "20221201000003_test.sql", 110 } 111 for _, name := range files { 112 path := filepath.Join(utils.MigrationsDir, name) 113 require.NoError(t, afero.WriteFile(fsys, path, []byte(""), 0644)) 114 } 115 // Setup mock postgres 116 conn := pgtest.NewConn() 117 defer conn.Close(t) 118 conn.Query(list.LIST_MIGRATION_VERSION). 119 Reply("SELECT 2", []interface{}{"20221201000000"}, []interface{}{"20221201000001"}) 120 // Connect to mock 121 ctx := context.Background() 122 mock, err := utils.ConnectRemotePostgres(ctx, user, pass, database, host, conn.Intercept) 123 require.NoError(t, err) 124 defer mock.Close(ctx) 125 // Run test 126 pending, err := getPendingMigrations(ctx, mock, fsys) 127 // Check error 128 assert.NoError(t, err) 129 assert.ElementsMatch(t, files[2:], pending) 130 }) 131 132 t.Run("throws error on local load failure", func(t *testing.T) { 133 // Setup in-memory fs 134 fsys := afero.NewReadOnlyFs(afero.NewMemMapFs()) 135 // Setup mock postgres 136 conn := pgtest.NewConn() 137 defer conn.Close(t) 138 conn.Query(list.LIST_MIGRATION_VERSION). 139 Reply("SELECT 0") 140 // Connect to mock 141 ctx := context.Background() 142 mock, err := utils.ConnectRemotePostgres(ctx, user, pass, database, host, conn.Intercept) 143 require.NoError(t, err) 144 defer mock.Close(ctx) 145 // Run test 146 _, err = getPendingMigrations(ctx, mock, fsys) 147 // Check error 148 assert.ErrorContains(t, err, "operation not permitted") 149 }) 150 151 t.Run("throws error on missing migration", func(t *testing.T) { 152 // Setup in-memory fs 153 fsys := afero.NewMemMapFs() 154 // Setup mock postgres 155 conn := pgtest.NewConn() 156 defer conn.Close(t) 157 conn.Query(list.LIST_MIGRATION_VERSION). 158 Reply("SELECT 1", []interface{}{"0"}) 159 // Connect to mock 160 ctx := context.Background() 161 mock, err := utils.ConnectRemotePostgres(ctx, user, pass, database, host, conn.Intercept) 162 require.NoError(t, err) 163 defer mock.Close(ctx) 164 // Run test 165 _, err = getPendingMigrations(ctx, mock, fsys) 166 // Check error 167 assert.ErrorContains(t, err, "Found 1 versions and 0 migrations.") 168 }) 169 170 t.Run("throws error on version mismatch", func(t *testing.T) { 171 // Setup in-memory fs 172 fsys := afero.NewMemMapFs() 173 path := filepath.Join(utils.MigrationsDir, "1_test.sql") 174 require.NoError(t, afero.WriteFile(fsys, path, []byte(""), 0644)) 175 // Setup mock postgres 176 conn := pgtest.NewConn() 177 defer conn.Close(t) 178 conn.Query(list.LIST_MIGRATION_VERSION). 179 Reply("SELECT 1", []interface{}{"0"}) 180 // Connect to mock 181 ctx := context.Background() 182 mock, err := utils.ConnectRemotePostgres(ctx, user, pass, database, host, conn.Intercept) 183 require.NoError(t, err) 184 defer mock.Close(ctx) 185 // Run test 186 _, err = getPendingMigrations(ctx, mock, fsys) 187 // Check error 188 assert.ErrorContains(t, err, "Expected version 0 but found migration 1_test.sql at index 0.") 189 }) 190 } 191 192 func TestPushLocal(t *testing.T) { 193 t.Run("pushes local migration", func(t *testing.T) { 194 // Setup in-memory fs 195 fsys := afero.NewMemMapFs() 196 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 197 sql := "create schema public" 198 require.NoError(t, afero.WriteFile(fsys, path, []byte(sql), 0644)) 199 // Setup mock postgres 200 conn := pgtest.NewConn() 201 defer conn.Close(t) 202 conn.Query(sql). 203 Reply("CREATE SCHEMA"). 204 Query(repair.INSERT_MIGRATION_VERSION, "0"). 205 Reply("INSERT 0 1") 206 // Connect to mock 207 ctx := context.Background() 208 mock, err := utils.ConnectRemotePostgres(ctx, user, pass, database, host, conn.Intercept) 209 require.NoError(t, err) 210 defer mock.Close(ctx) 211 // Run test 212 err = pushMigration(ctx, mock, "0_test.sql", fsys) 213 // Check error 214 assert.NoError(t, err) 215 }) 216 217 t.Run("throws error on missing file", func(t *testing.T) { 218 // Setup in-memory fs 219 fsys := afero.NewMemMapFs() 220 // Run test 221 err := pushMigration(context.Background(), nil, "0_test.sql", fsys) 222 // Check error 223 assert.ErrorContains(t, err, "open supabase/migrations/0_test.sql: file does not exist") 224 }) 225 226 t.Run("throws error on split failure", func(t *testing.T) { 227 // Setup in-memory fs 228 fsys := afero.NewMemMapFs() 229 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 230 sql := bytes.Repeat([]byte{'a'}, parser.MaxScannerCapacity) 231 require.NoError(t, afero.WriteFile(fsys, path, sql, 0644)) 232 // Run test 233 err := pushMigration(context.Background(), nil, "0_test.sql", fsys) 234 // Check error 235 assert.ErrorContains(t, err, "bufio.Scanner: token too long\nAfter statement 0: ") 236 }) 237 238 t.Run("throws error on exec failure", func(t *testing.T) { 239 // Setup in-memory fs 240 fsys := afero.NewMemMapFs() 241 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 242 require.NoError(t, afero.WriteFile(fsys, path, []byte(""), 0644)) 243 // Setup mock postgres 244 conn := pgtest.NewConn() 245 defer conn.Close(t) 246 conn.Query(repair.INSERT_MIGRATION_VERSION, "0"). 247 ReplyError(pgerrcode.NotNullViolation, `null value in column "version" of relation "schema_migrations"`) 248 // Connect to mock 249 ctx := context.Background() 250 mock, err := utils.ConnectRemotePostgres(ctx, user, pass, database, host, conn.Intercept) 251 require.NoError(t, err) 252 defer mock.Close(ctx) 253 // Run test 254 err = pushMigration(ctx, mock, "0_test.sql", fsys) 255 // Check error 256 assert.ErrorContains(t, err, `ERROR: null value in column "version" of relation "schema_migrations" (SQLSTATE 23502)`) 257 assert.ErrorContains(t, err, "At statement 0: "+repair.INSERT_MIGRATION_VERSION) 258 }) 259 }