Ctrl K
ComunidadGuías…

store/schema_version_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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 []")
}