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  }