github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/migrate/migrate_test.go (about)

     1  package migrate
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/require"
     9  )
    10  
    11  var (
    12  	noNonatomicMigration MigrationFunc[fakeConnPool]
    13  	noTxMigration        TxMigrationFunc[fakeTx]
    14  )
    15  
    16  type fakeDriver struct {
    17  	currentVersion string
    18  }
    19  
    20  func (fd *fakeDriver) Version(ctx context.Context) (string, error) {
    21  	return fd.currentVersion, ctx.Err()
    22  }
    23  
    24  func (fd *fakeDriver) WriteVersion(ctx context.Context, _ fakeTx, to, _ string) error {
    25  	if ctx.Err() == nil {
    26  		fd.currentVersion = to
    27  	}
    28  	return ctx.Err()
    29  }
    30  
    31  func (*fakeDriver) Conn() fakeConnPool {
    32  	return fakeConnPool{}
    33  }
    34  
    35  func (*fakeDriver) RunTx(ctx context.Context, _ TxMigrationFunc[fakeTx]) error {
    36  	return ctx.Err()
    37  }
    38  
    39  func (*fakeDriver) Close(ctx context.Context) error {
    40  	return ctx.Err()
    41  }
    42  
    43  type fakeConnPool struct{}
    44  
    45  type fakeTx struct{}
    46  
    47  func TestContextError(t *testing.T) {
    48  	req := require.New(t)
    49  	ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Millisecond))
    50  	m := NewManager[Driver[fakeConnPool, fakeTx], fakeConnPool, fakeTx]()
    51  
    52  	err := m.Register("1", "", func(ctx context.Context, conn fakeConnPool) error {
    53  		cancelFunc()
    54  		return nil
    55  	}, noTxMigration)
    56  	req.NoError(err)
    57  
    58  	err = m.Register("2", "1", func(ctx context.Context, conn fakeConnPool) error {
    59  		panic("the second migration should never be executed")
    60  	}, noTxMigration)
    61  	req.NoError(err)
    62  
    63  	err = m.Run(ctx, &fakeDriver{}, Head, false)
    64  	req.ErrorIs(err, context.Canceled)
    65  }
    66  
    67  type revisionRangeTest struct {
    68  	start, end        string
    69  	expectError       bool
    70  	expectedRevisions []string
    71  }
    72  
    73  func TestRevisionWalking(t *testing.T) {
    74  	testCases := []struct {
    75  		migrations map[string]migration[fakeConnPool, fakeTx]
    76  		ranges     []revisionRangeTest
    77  	}{
    78  		{noMigrations, []revisionRangeTest{
    79  			{"", "", false, []string{}},
    80  			{"", "123", true, []string{}},
    81  		}},
    82  		{simpleMigrations, []revisionRangeTest{
    83  			{"", "123", false, []string{"123"}},
    84  			{"123", "123", false, []string{}},
    85  			{"", "", false, []string{}},
    86  			{"", "456", true, []string{}},
    87  			{"123", "456", true, []string{}},
    88  			{"456", "456", false, []string{}},
    89  		}},
    90  		{singleHeadedChain, []revisionRangeTest{
    91  			{"", "123", false, []string{"123"}},
    92  			{"", "789", false, []string{"123", "456", "789"}},
    93  			{"123", "789", false, []string{"456", "789"}},
    94  			{"123", "456", false, []string{"456"}},
    95  			{"123", "10", true, []string{}},
    96  			{"", "10", true, []string{}},
    97  		}},
    98  		{multiHeadedChain, []revisionRangeTest{
    99  			{"", "123", false, []string{"123"}},
   100  			{"", "789a", false, []string{"123", "456", "789a"}},
   101  			{"", "789b", false, []string{"123", "456", "789b"}},
   102  			{"456", "789b", false, []string{"789b"}},
   103  		}},
   104  		{missingEarlyMigrations, []revisionRangeTest{
   105  			{"", "123", true, []string{}},
   106  			{"", "10", true, []string{}},
   107  			{"123", "10", false, []string{"456", "789", "10"}},
   108  			{"456", "10", false, []string{"789", "10"}},
   109  		}},
   110  	}
   111  
   112  	require := require.New(t)
   113  
   114  	for _, tc := range testCases {
   115  		for _, versionRange := range tc.ranges {
   116  			computed, err := collectMigrationsInRange(
   117  				versionRange.start,
   118  				versionRange.end,
   119  				tc.migrations,
   120  			)
   121  
   122  			require.Equal(versionRange.expectError, err != nil, err)
   123  
   124  			migrationNames := make([]string, 0, len(computed))
   125  			for _, mgr := range computed {
   126  				migrationNames = append(migrationNames, mgr.version)
   127  			}
   128  			require.Equal(versionRange.expectedRevisions, migrationNames)
   129  		}
   130  	}
   131  }
   132  
   133  func TestComputeHeadRevision(t *testing.T) {
   134  	testCases := []struct {
   135  		migrations   map[string]migration[fakeConnPool, fakeTx]
   136  		headRevision string
   137  		expectError  bool
   138  	}{
   139  		{noMigrations, "", true},
   140  		{simpleMigrations, "123", false},
   141  		{singleHeadedChain, "789", false},
   142  		{multiHeadedChain, "", true},
   143  		{missingEarlyMigrations, "10", false},
   144  	}
   145  
   146  	require := require.New(t)
   147  	for _, tc := range testCases {
   148  		m := Manager[Driver[fakeConnPool, fakeTx], fakeConnPool, fakeTx]{migrations: tc.migrations}
   149  		head, err := m.HeadRevision()
   150  		require.Equal(tc.expectError, err != nil, err)
   151  		require.Equal(tc.headRevision, head)
   152  	}
   153  }
   154  
   155  func TestIsHeadCompatible(t *testing.T) {
   156  	testCases := []struct {
   157  		migrations       map[string]migration[fakeConnPool, fakeTx]
   158  		currentMigration string
   159  		expectedResult   bool
   160  		expectError      bool
   161  	}{
   162  		{noMigrations, "", false, true},
   163  		{simpleMigrations, "123", true, false},
   164  		{singleHeadedChain, "789", true, false},
   165  		{singleHeadedChain, "456", true, false},
   166  		{singleHeadedChain, "123", false, false},
   167  		{multiHeadedChain, "", false, true},
   168  		{missingEarlyMigrations, "10", true, false},
   169  		{missingEarlyMigrations, "789", true, false},
   170  		{missingEarlyMigrations, "456", false, false},
   171  	}
   172  
   173  	req := require.New(t)
   174  	for _, tc := range testCases {
   175  		m := Manager[Driver[fakeConnPool, fakeTx], fakeConnPool, fakeTx]{migrations: tc.migrations}
   176  		compatible, err := m.IsHeadCompatible(tc.currentMigration)
   177  		req.Equal(compatible, tc.expectedResult)
   178  		req.Equal(tc.expectError, err != nil, err)
   179  	}
   180  }
   181  
   182  func TestManagerEnsureVersionIsWritten(t *testing.T) {
   183  	req := require.New(t)
   184  	m := NewManager[Driver[fakeConnPool, fakeTx], fakeConnPool, fakeTx]()
   185  	err := m.Register("0", "", noNonatomicMigration, noTxMigration)
   186  	req.NoError(err)
   187  	drv := &fakeDriver{}
   188  	err = m.Run(context.Background(), drv, "0", LiveRun)
   189  	req.Error(err)
   190  
   191  	writtenVer, err := drv.Version(context.Background())
   192  	req.NoError(err)
   193  	req.Equal("", writtenVer)
   194  }
   195  
   196  var noMigrations = map[string]migration[fakeConnPool, fakeTx]{}
   197  
   198  var simpleMigrations = map[string]migration[fakeConnPool, fakeTx]{
   199  	"123": {"123", "", noNonatomicMigration, noTxMigration},
   200  }
   201  
   202  var singleHeadedChain = map[string]migration[fakeConnPool, fakeTx]{
   203  	"123": {"123", "", noNonatomicMigration, noTxMigration},
   204  	"456": {"456", "123", noNonatomicMigration, noTxMigration},
   205  	"789": {"789", "456", noNonatomicMigration, noTxMigration},
   206  }
   207  
   208  var multiHeadedChain = map[string]migration[fakeConnPool, fakeTx]{
   209  	"123":  {"123", "", noNonatomicMigration, noTxMigration},
   210  	"456":  {"456", "123", noNonatomicMigration, noTxMigration},
   211  	"789a": {"789a", "456", noNonatomicMigration, noTxMigration},
   212  	"789b": {"789b", "456", noNonatomicMigration, noTxMigration},
   213  }
   214  
   215  var missingEarlyMigrations = map[string]migration[fakeConnPool, fakeTx]{
   216  	"456": {"456", "123", noNonatomicMigration, noTxMigration},
   217  	"789": {"789", "456", noNonatomicMigration, noTxMigration},
   218  	"10":  {"10", "789", noNonatomicMigration, noTxMigration},
   219  }