github.com/Redstoneguy129/cli@v0.0.0-20230211220159-15dca4e91917/internal/migration/list/list_test.go (about) 1 package list 2 3 import ( 4 "context" 5 "io/fs" 6 "path/filepath" 7 "strings" 8 "testing" 9 10 "github.com/Redstoneguy129/cli/internal/testing/pgtest" 11 "github.com/Redstoneguy129/cli/internal/utils" 12 "github.com/jackc/pgerrcode" 13 "github.com/spf13/afero" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 var ( 19 user = "admin" 20 pass = "password" 21 host = utils.Config.Hostname 22 db = "postgres" 23 ) 24 25 func TestMigrationList(t *testing.T) { 26 t.Run("lists remote migrations", func(t *testing.T) { 27 // Setup in-memory fs 28 fsys := afero.NewMemMapFs() 29 // Setup mock postgres 30 conn := pgtest.NewConn() 31 defer conn.Close(t) 32 conn.Query(LIST_MIGRATION_VERSION). 33 Reply("SELECT 0") 34 // Run test 35 err := Run(context.Background(), user, pass, db, host, fsys, conn.Intercept) 36 // Check error 37 assert.NoError(t, err) 38 }) 39 40 t.Run("throws error on remote failure", func(t *testing.T) { 41 // Setup in-memory fs 42 fsys := afero.NewMemMapFs() 43 // Run test 44 err := Run(context.Background(), user, pass, db, "0", fsys) 45 // Check error 46 assert.ErrorContains(t, err, "dial error (dial tcp 0.0.0.0:6543: connect: connection refused)") 47 }) 48 49 t.Run("throws error on local failure", func(t *testing.T) { 50 // Setup in-memory fs 51 fsys := afero.NewMemMapFs() 52 // Setup mock postgres 53 conn := pgtest.NewConn() 54 defer conn.Close(t) 55 conn.Query(LIST_MIGRATION_VERSION). 56 Reply("SELECT 0") 57 // Run test 58 err := Run(context.Background(), user, pass, db, host, afero.NewReadOnlyFs(fsys), conn.Intercept) 59 // Check error 60 assert.ErrorContains(t, err, "operation not permitted") 61 }) 62 } 63 64 func TestRemoteMigrations(t *testing.T) { 65 t.Run("loads migration versions", func(t *testing.T) { 66 // Setup mock postgres 67 conn := pgtest.NewConn() 68 defer conn.Close(t) 69 conn.Query(LIST_MIGRATION_VERSION). 70 Reply("SELECT 1", []interface{}{"20220727064247"}) 71 // Run test 72 versions, err := loadRemoteVersions(context.Background(), user, pass, db, host, conn.Intercept) 73 // Check error 74 assert.NoError(t, err) 75 assert.ElementsMatch(t, []string{"20220727064247"}, versions) 76 }) 77 78 t.Run("throws error on connect failure", func(t *testing.T) { 79 // Run test 80 _, err := loadRemoteVersions(context.Background(), user, pass, db, "0") 81 // Check error 82 assert.ErrorContains(t, err, "dial error (dial tcp 0.0.0.0:6543: connect: connection refused)") 83 }) 84 85 t.Run("throws error on missing schema", func(t *testing.T) { 86 // Setup mock postgres 87 conn := pgtest.NewConn() 88 defer conn.Close(t) 89 conn.Query(LIST_MIGRATION_VERSION). 90 ReplyError(pgerrcode.UndefinedTable, "relation \"supabase_migrations.schema_migrations\" does not exist") 91 // Run test 92 _, err := loadRemoteVersions(context.Background(), user, pass, db, host, conn.Intercept) 93 // Check error 94 assert.ErrorContains(t, err, `ERROR: relation "supabase_migrations.schema_migrations" does not exist (SQLSTATE 42P01)`) 95 }) 96 97 t.Run("throws error on invalid row", func(t *testing.T) { 98 // Setup mock postgres 99 conn := pgtest.NewConn() 100 defer conn.Close(t) 101 conn.Query(LIST_MIGRATION_VERSION). 102 Reply("SELECT 1", nil) 103 // Run test 104 _, err := loadRemoteVersions(context.Background(), user, pass, db, host, conn.Intercept) 105 // Check error 106 assert.ErrorContains(t, err, "number of field descriptions must equal number of destinations, got 0 and 1") 107 }) 108 } 109 110 type MockFs struct { 111 afero.MemMapFs 112 DenyPath string 113 } 114 115 func (m *MockFs) Open(name string) (afero.File, error) { 116 if strings.HasPrefix(name, m.DenyPath) { 117 return nil, fs.ErrPermission 118 } 119 return m.MemMapFs.Open(name) 120 } 121 122 func TestLocalMigrations(t *testing.T) { 123 t.Run("loads migration versions", func(t *testing.T) { 124 // Setup in-memory fs 125 fsys := afero.NewMemMapFs() 126 path := filepath.Join(utils.MigrationsDir, "20220727064246_test.sql") 127 require.NoError(t, afero.WriteFile(fsys, path, []byte{}, 0644)) 128 path = filepath.Join(utils.MigrationsDir, "20220727064248_test.sql") 129 require.NoError(t, afero.WriteFile(fsys, path, []byte{}, 0644)) 130 // Run test 131 versions, err := loadLocalVersions(fsys) 132 // Check error 133 assert.NoError(t, err) 134 assert.ElementsMatch(t, []string{"20220727064246", "20220727064248"}, versions) 135 }) 136 137 t.Run("ignores outdated and invalid files", func(t *testing.T) { 138 // Setup in-memory fs 139 fsys := afero.NewMemMapFs() 140 path := filepath.Join(utils.MigrationsDir, "20211208000000_init.sql") 141 require.NoError(t, afero.WriteFile(fsys, path, []byte{}, 0644)) 142 path = filepath.Join(utils.MigrationsDir, "20211208000001_invalid.ts") 143 require.NoError(t, afero.WriteFile(fsys, path, []byte{}, 0644)) 144 // Run test 145 versions, err := loadLocalVersions(fsys) 146 // Check error 147 assert.NoError(t, err) 148 assert.Empty(t, versions) 149 }) 150 151 t.Run("throws error on permission denied", func(t *testing.T) { 152 // Setup in-memory fs 153 fsys := afero.NewMemMapFs() 154 // Run test 155 _, err := loadLocalVersions(afero.NewReadOnlyFs(fsys)) 156 // Check error 157 assert.ErrorContains(t, err, "operation not permitted") 158 }) 159 160 t.Run("throws error on open failure", func(t *testing.T) { 161 // Setup in-memory fs 162 fsys := MockFs{DenyPath: utils.MigrationsDir} 163 // Run test 164 _, err := loadLocalVersions(&fsys) 165 // Check error 166 assert.ErrorContains(t, err, "permission denied") 167 }) 168 } 169 170 func TestMakeTable(t *testing.T) { 171 t.Run("tabulate version", func(t *testing.T) { 172 // Run test 173 table := makeTable([]string{"0", "2"}, []string{"0", "1"}) 174 // Check error 175 lines := strings.Split(strings.TrimSpace(table), "\n") 176 assert.ElementsMatch(t, []string{ 177 "|Local|Remote|Time (UTC)|", 178 "|-|-|-|", 179 "|`0`|`0`|`0`|", 180 "|`1`|` `|`1`|", 181 "|` `|`2`|`2`|", 182 }, lines) 183 }) 184 185 t.Run("tabulate timestamp", func(t *testing.T) { 186 // Run test 187 table := makeTable([]string{"20220727064246", "20220727064248"}, []string{"20220727064246", "20220727064247"}) 188 // Check error 189 lines := strings.Split(strings.TrimSpace(table), "\n") 190 assert.ElementsMatch(t, []string{ 191 "|Local|Remote|Time (UTC)|", 192 "|-|-|-|", 193 "|`20220727064246`|`20220727064246`|`2022-07-27 06:42:46`|", 194 "|`20220727064247`|` `|`2022-07-27 06:42:47`|", 195 "|` `|`20220727064248`|`2022-07-27 06:42:48`|", 196 }, lines) 197 }) 198 199 t.Run("ignores string values", func(t *testing.T) { 200 // Run test 201 table := makeTable([]string{"a", "c"}, []string{"a", "b"}) 202 // Check error 203 lines := strings.Split(strings.TrimSpace(table), "\n") 204 assert.ElementsMatch(t, []string{ 205 "|Local|Remote|Time (UTC)|", 206 "|-|-|-|", 207 }, lines) 208 }) 209 }