store/schema_version_test.go
Rama main · réplica de lectura del repositorio original.
package store
import (
"io/fs"
"maps"
"slices"
"testing"
"testing/fstest"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"golang.org/x/mod/semver"
)
func TestCompareSchemaVersions(t *testing.T) {
for _, tc := range []struct {
a, b string
want int
}{
{"0.31.7", "0.31.8", -1},
{"0.31.8", "0.31.8", 0},
{"26.9.1", "0.31.8", 1},
{"26.9.10", "26.9.2", 1},
{"26.10.1", "26.9.99", 1},
{"27.1.1", "26.12.99", 1},
} {
order, err := compareSchemaVersions(tc.a, tc.b)
require.NoError(t, err)
require.Equal(t, tc.want, order)
}
for _, invalid := range []string{"", "dev", "26.09.1", "26.9", "26.0.1", "26.13.1", "26.9.01", "26.9.1-rc.1", "26.9.1+build", "0.031.8", "26.9.999999999999999999999999"} {
_, err := compareSchemaVersions(invalid, baselineSchemaVersion)
require.Error(t, err, invalid)
_, err = compareSchemaVersions(baselineSchemaVersion, invalid)
require.Error(t, err, invalid)
}
}
func migrationFixture(paths ...string) fstest.MapFS {
fsys := fstest.MapFS{}
for _, path := range paths {
fsys[path] = &fstest.MapFile{Data: []byte("SELECT 1;")}
}
return fsys
}
func TestCalendarMigrationOrdering(t *testing.T) {
files, err := listMigrationFilesFrom(migrationFixture(
"migration/sqlite/LATEST.sql",
"migration/sqlite/27.01/00__next_year.sql",
"migration/sqlite/26.09/100__many_changes.sql",
"migration/sqlite/26.10/00__next_month.sql",
"migration/sqlite/26.09/09__tenth.sql",
"migration/sqlite/26.09/00__first.sql",
), "migration/sqlite/")
require.NoError(t, err)
var versions []string
for _, file := range files {
versions = append(versions, file.version)
require.Equal(t, mustParseSchemaVersion(file.version), file.parsed)
// v0.31.0 uses this comparator for its downgrade guard.
require.True(t, semver.IsValid("v"+file.version))
require.Positive(t, semver.Compare("v"+file.version, "v"+baselineSchemaVersion))
}
require.Equal(t, []string{"26.9.1", "26.9.10", "26.9.101", "26.10.1", "27.1.1"}, versions)
v, err := getSchemaVersionOfMigrateScript("migration/sqlite/26.09/00__LATEST.sql")
require.NoError(t, err)
require.Equal(t, "26.9.1", v)
for _, path := range []string{
"migration/sqlite/26.13/00__bad_month.sql",
"migration/sqlite/26.9/00__unpadded.sql",
"migration/sqlite/26.09/-1__negative.sql",
"migration/sqlite/26.09/00.sql",
"migration/sqlite/26.09/00__.sql",
"migration/sqlite/26.09/LATEST.sql",
} {
_, err := listMigrationFilesFrom(migrationFixture(path), "migration/sqlite/")
require.Error(t, err, path)
}
}
func TestDuplicateMigrationSequenceIsRejected(t *testing.T) {
_, err := listMigrationFilesFrom(migrationFixture(
"migration/sqlite/26.09/00__first.sql",
"migration/sqlite/26.09/01__add_index.sql",
"migration/sqlite/26.09/01__rename_column.sql",
), "migration/sqlite/")
require.ErrorContains(t, err, "both resolve to schema version 26.9.2")
}
func TestCalendarMigrationSelection(t *testing.T) {
for _, tc := range []struct {
file, current, target string
want bool
}{
{"26.9.1", "0.31.8", "26.10.1", true},
{"26.9.1", "26.9.1", "26.10.1", false},
{"26.9.2", "26.9.1", "26.9.2", true},
{"26.9.3", "26.9.1", "26.9.2", false},
{"27.1.1", "26.12.1", "27.1.1", true},
} {
apply := shouldApplyMigration(mustParseSchemaVersion(tc.file), mustParseSchemaVersion(tc.current), mustParseSchemaVersion(tc.target))
require.Equal(t, tc.want, apply, "%+v", tc)
}
}
// migrationVersionsByDriver lists each driver's migration versions in order.
func migrationVersionsByDriver(t *testing.T, fsys fs.FS) map[string][]string {
t.Helper()
entries, err := fs.ReadDir(fsys, "migration")
require.NoError(t, err)
versions := map[string][]string{}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
files, err := listMigrationFilesFrom(fsys, "migration/"+entry.Name()+"/")
require.NoError(t, err, entry.Name())
driverVersions := []string{}
for _, file := range files {
driverVersions = append(driverVersions, file.version)
}
versions[entry.Name()] = driverVersions
}
return versions
}
// migrationVersionMismatch names the first driver whose migration versions
// differ from another driver's, or returns nil when every driver agrees.
func migrationVersionMismatch(versions map[string][]string) error {
drivers := slices.Sorted(maps.Keys(versions))
for _, driver := range drivers[1:] {
if !slices.Equal(versions[drivers[0]], versions[driver]) {
return errors.Errorf("driver %s ships migration versions %v but %s ships %v",
driver, versions[driver], drivers[0], versions[drivers[0]])
}
}
return nil
}
// TestMigrationVersionsMatchAcrossDrivers guards the schema marker: a driver
// only records versions whose SQL it executed, so a migration must exist for
// every driver before any driver can advance past it.
func TestMigrationVersionsMatchAcrossDrivers(t *testing.T) {
versions := migrationVersionsByDriver(t, migrationFS)
require.Len(t, versions, 3)
require.NoError(t, migrationVersionMismatch(versions))
// The check itself catches a migration that lands for one driver only.
partial := migrationVersionsByDriver(t, migrationFixture(
"migration/mysql/LATEST.sql",
"migration/postgres/LATEST.sql",
"migration/sqlite/LATEST.sql",
"migration/sqlite/26.09/00__sqlite_only.sql",
))
require.Equal(t, []string{"26.9.1"}, partial["sqlite"])
require.Empty(t, partial["mysql"])
require.ErrorContains(t, migrationVersionMismatch(partial), "driver sqlite ships migration versions [26.9.1] but mysql ships []")
}