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 }