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  }