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  }