github.com/supabase/cli@v1.168.1/internal/db/pull/pull_test.go (about) 1 package pull 2 3 import ( 4 "context" 5 "errors" 6 "os" 7 "path/filepath" 8 "testing" 9 10 "github.com/jackc/pgconn" 11 "github.com/jackc/pgerrcode" 12 "github.com/spf13/afero" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 "github.com/supabase/cli/internal/db/reset" 16 "github.com/supabase/cli/internal/migration/list" 17 "github.com/supabase/cli/internal/testing/apitest" 18 "github.com/supabase/cli/internal/testing/fstest" 19 "github.com/supabase/cli/internal/testing/pgtest" 20 "github.com/supabase/cli/internal/utils" 21 "gopkg.in/h2non/gock.v1" 22 ) 23 24 var dbConfig = pgconn.Config{ 25 Host: "db.supabase.co", 26 Port: 5432, 27 User: "admin", 28 Password: "password", 29 Database: "postgres", 30 } 31 32 var escapedSchemas = []string{ 33 "pgbouncer", 34 "pgsodium", 35 "pgtle", 36 `supabase\_migrations`, 37 "vault", 38 `information\_schema`, 39 `pg\_%`, 40 } 41 42 func TestPullCommand(t *testing.T) { 43 t.Run("throws error on missing config", func(t *testing.T) { 44 // Setup in-memory fs 45 fsys := afero.NewMemMapFs() 46 // Run test 47 err := Run(context.Background(), nil, pgconn.Config{}, "", fsys) 48 // Check error 49 assert.ErrorIs(t, err, os.ErrNotExist) 50 assert.Empty(t, apitest.ListUnmatchedRequests()) 51 }) 52 53 t.Run("throws error on connect failure", func(t *testing.T) { 54 // Setup in-memory fs 55 fsys := afero.NewMemMapFs() 56 require.NoError(t, utils.WriteConfig(fsys, false)) 57 // Run test 58 err := Run(context.Background(), nil, pgconn.Config{}, "", fsys) 59 // Check error 60 assert.ErrorContains(t, err, "invalid port (outside range)") 61 assert.Empty(t, apitest.ListUnmatchedRequests()) 62 }) 63 64 t.Run("throws error on sync failure", func(t *testing.T) { 65 // Setup in-memory fs 66 fsys := afero.NewMemMapFs() 67 require.NoError(t, utils.WriteConfig(fsys, false)) 68 // Setup mock postgres 69 conn := pgtest.NewConn() 70 defer conn.Close(t) 71 conn.Query(list.LIST_MIGRATION_VERSION). 72 ReplyError(pgerrcode.InvalidCatalogName, `database "postgres" does not exist`) 73 // Run test 74 err := Run(context.Background(), nil, dbConfig, "", fsys, conn.Intercept) 75 // Check error 76 assert.ErrorContains(t, err, `ERROR: database "postgres" does not exist (SQLSTATE 3D000)`) 77 assert.Empty(t, apitest.ListUnmatchedRequests()) 78 }) 79 } 80 81 func TestPullSchema(t *testing.T) { 82 t.Run("dumps remote schema", func(t *testing.T) { 83 // Setup in-memory fs 84 fsys := afero.NewMemMapFs() 85 // Setup mock docker 86 require.NoError(t, apitest.MockDocker(utils.Docker)) 87 defer gock.OffAll() 88 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Pg15Image), "test-db") 89 require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-db", "test")) 90 // Setup mock postgres 91 conn := pgtest.NewConn() 92 defer conn.Close(t) 93 conn.Query(list.LIST_MIGRATION_VERSION). 94 Reply("SELECT 0") 95 // Connect to mock 96 ctx := context.Background() 97 mock, err := utils.ConnectByConfig(ctx, dbConfig, conn.Intercept) 98 require.NoError(t, err) 99 defer mock.Close(ctx) 100 // Run test 101 err = utils.RunProgram(context.Background(), func(p utils.Program, ctx context.Context) error { 102 return run(p, ctx, nil, "0_test.sql", mock, fsys) 103 }) 104 // Check error 105 assert.NoError(t, err) 106 assert.Empty(t, apitest.ListUnmatchedRequests()) 107 contents, err := afero.ReadFile(fsys, "0_test.sql") 108 assert.NoError(t, err) 109 assert.Equal(t, []byte("test"), contents) 110 }) 111 112 t.Run("throws error on load user schema failure", func(t *testing.T) { 113 // Setup in-memory fs 114 fsys := afero.NewMemMapFs() 115 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 116 require.NoError(t, afero.WriteFile(fsys, path, []byte(""), 0644)) 117 // Setup mock postgres 118 conn := pgtest.NewConn() 119 defer conn.Close(t) 120 conn.Query(list.LIST_MIGRATION_VERSION). 121 Reply("SELECT 1", []interface{}{"0"}). 122 Query(reset.ListSchemas, escapedSchemas). 123 ReplyError(pgerrcode.DuplicateTable, `relation "test" already exists`) 124 // Connect to mock 125 ctx := context.Background() 126 mock, err := utils.ConnectByConfig(ctx, dbConfig, conn.Intercept) 127 require.NoError(t, err) 128 defer mock.Close(ctx) 129 // Run test 130 err = utils.RunProgram(context.Background(), func(p utils.Program, ctx context.Context) error { 131 return run(p, ctx, nil, "", mock, fsys) 132 }) 133 // Check error 134 assert.ErrorContains(t, err, `ERROR: relation "test" already exists (SQLSTATE 42P07)`) 135 }) 136 137 t.Run("throws error on diff failure", func(t *testing.T) { 138 // Setup in-memory fs 139 fsys := afero.NewMemMapFs() 140 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 141 require.NoError(t, afero.WriteFile(fsys, path, []byte(""), 0644)) 142 // Setup mock docker 143 require.NoError(t, apitest.MockDocker(utils.Docker)) 144 defer gock.OffAll() 145 gock.New(utils.Docker.DaemonHost()). 146 Get("/v" + utils.Docker.ClientVersion() + "/images/" + utils.GetRegistryImageUrl(utils.Config.Db.Image) + "/json"). 147 ReplyError(errors.New("network error")) 148 // Setup mock postgres 149 conn := pgtest.NewConn() 150 defer conn.Close(t) 151 conn.Query(list.LIST_MIGRATION_VERSION). 152 Reply("SELECT 1", []interface{}{"0"}) 153 // Connect to mock 154 ctx := context.Background() 155 mock, err := utils.ConnectByConfig(ctx, dbConfig, conn.Intercept) 156 require.NoError(t, err) 157 defer mock.Close(ctx) 158 // Run test 159 err = utils.RunProgram(context.Background(), func(p utils.Program, ctx context.Context) error { 160 return run(p, ctx, []string{"public"}, "", mock, fsys) 161 }) 162 // Check error 163 assert.ErrorContains(t, err, "network error") 164 assert.Empty(t, apitest.ListUnmatchedRequests()) 165 }) 166 } 167 168 func TestSyncRemote(t *testing.T) { 169 t.Run("throws error on permission denied", func(t *testing.T) { 170 // Setup in-memory fs 171 fsys := &fstest.OpenErrorFs{DenyPath: utils.MigrationsDir} 172 // Setup mock postgres 173 conn := pgtest.NewConn() 174 defer conn.Close(t) 175 conn.Query(list.LIST_MIGRATION_VERSION). 176 Reply("SELECT 0") 177 // Connect to mock 178 ctx := context.Background() 179 mock, err := utils.ConnectByConfig(ctx, dbConfig, conn.Intercept) 180 require.NoError(t, err) 181 defer mock.Close(ctx) 182 // Run test 183 err = assertRemoteInSync(ctx, mock, fsys) 184 // Check error 185 assert.ErrorIs(t, err, os.ErrPermission) 186 assert.Empty(t, apitest.ListUnmatchedRequests()) 187 }) 188 189 t.Run("throws error on mismatched length", func(t *testing.T) { 190 // Setup in-memory fs 191 fsys := afero.NewMemMapFs() 192 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 193 require.NoError(t, afero.WriteFile(fsys, path, []byte(""), 0644)) 194 // Setup mock postgres 195 conn := pgtest.NewConn() 196 defer conn.Close(t) 197 conn.Query(list.LIST_MIGRATION_VERSION). 198 Reply("SELECT 0") 199 // Connect to mock 200 ctx := context.Background() 201 mock, err := utils.ConnectByConfig(ctx, dbConfig, conn.Intercept) 202 require.NoError(t, err) 203 defer mock.Close(ctx) 204 // Run test 205 err = assertRemoteInSync(ctx, mock, fsys) 206 // Check error 207 assert.ErrorIs(t, err, errConflict) 208 assert.Empty(t, apitest.ListUnmatchedRequests()) 209 }) 210 211 t.Run("throws error on mismatched migration", func(t *testing.T) { 212 // Setup in-memory fs 213 fsys := afero.NewMemMapFs() 214 path := filepath.Join(utils.MigrationsDir, "0_test.sql") 215 require.NoError(t, afero.WriteFile(fsys, path, []byte(""), 0644)) 216 // Setup mock postgres 217 conn := pgtest.NewConn() 218 defer conn.Close(t) 219 conn.Query(list.LIST_MIGRATION_VERSION). 220 Reply("SELECT 1", []interface{}{"20220727064247"}) 221 // Connect to mock 222 ctx := context.Background() 223 mock, err := utils.ConnectByConfig(ctx, dbConfig, conn.Intercept) 224 require.NoError(t, err) 225 defer mock.Close(ctx) 226 // Run test 227 err = assertRemoteInSync(ctx, mock, fsys) 228 // Check error 229 assert.ErrorIs(t, err, errConflict) 230 assert.Empty(t, apitest.ListUnmatchedRequests()) 231 }) 232 233 t.Run("throws error on missing migration", func(t *testing.T) { 234 // Setup in-memory fs 235 fsys := afero.NewMemMapFs() 236 // Setup mock postgres 237 conn := pgtest.NewConn() 238 defer conn.Close(t) 239 conn.Query(list.LIST_MIGRATION_VERSION). 240 Reply("SELECT 0") 241 // Connect to mock 242 ctx := context.Background() 243 mock, err := utils.ConnectByConfig(ctx, dbConfig, conn.Intercept) 244 require.NoError(t, err) 245 defer mock.Close(ctx) 246 // Run test 247 err = assertRemoteInSync(ctx, mock, fsys) 248 // Check error 249 assert.ErrorIs(t, err, errMissing) 250 assert.Empty(t, apitest.ListUnmatchedRequests()) 251 }) 252 }