mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
An MMO server without multiplayer defeats the purpose. PostgreSQL is the right choice and Docker Compose already solves the setup pain. This reverts the common/db wrapper, SQLite schema, config Driver field, modernc.org/sqlite dependency, and all repo type changes while keeping the dashboard, wizard, and CI improvements from the previous commit.
280 lines
6.6 KiB
Go
280 lines
6.6 KiB
Go
package migrations
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
_ "github.com/lib/pq"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func testDB(t *testing.T) *sqlx.DB {
|
|
t.Helper()
|
|
|
|
host := getEnv("TEST_DB_HOST", "localhost")
|
|
port := getEnv("TEST_DB_PORT", "5433")
|
|
user := getEnv("TEST_DB_USER", "test")
|
|
password := getEnv("TEST_DB_PASSWORD", "test")
|
|
dbName := getEnv("TEST_DB_NAME", "erupe_test")
|
|
|
|
connStr := fmt.Sprintf(
|
|
"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
|
host, port, user, password, dbName,
|
|
)
|
|
|
|
db, err := sqlx.Open("postgres", connStr)
|
|
if err != nil {
|
|
t.Skipf("Test database not available: %v", err)
|
|
return nil
|
|
}
|
|
|
|
if err := db.Ping(); err != nil {
|
|
_ = db.Close()
|
|
t.Skipf("Test database not available: %v", err)
|
|
return nil
|
|
}
|
|
|
|
// Clean slate
|
|
_, err = db.Exec("DROP SCHEMA public CASCADE; CREATE SCHEMA public;")
|
|
if err != nil {
|
|
t.Fatalf("Failed to clean database: %v", err)
|
|
}
|
|
|
|
return db
|
|
}
|
|
|
|
func getEnv(key, defaultValue string) string {
|
|
if value := os.Getenv(key); value != "" {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func TestMigrateEmptyDB(t *testing.T) {
|
|
db := testDB(t)
|
|
defer func() { _ = db.Close() }()
|
|
|
|
logger, _ := zap.NewDevelopment()
|
|
|
|
applied, err := Migrate(db, logger)
|
|
if err != nil {
|
|
t.Fatalf("Migrate failed: %v", err)
|
|
}
|
|
if applied != 5 {
|
|
t.Errorf("expected 5 migrations applied, got %d", applied)
|
|
}
|
|
|
|
ver, err := Version(db)
|
|
if err != nil {
|
|
t.Fatalf("Version failed: %v", err)
|
|
}
|
|
if ver != 5 {
|
|
t.Errorf("expected version 5, got %d", ver)
|
|
}
|
|
}
|
|
|
|
func TestMigrateAlreadyMigrated(t *testing.T) {
|
|
db := testDB(t)
|
|
defer func() { _ = db.Close() }()
|
|
|
|
logger, _ := zap.NewDevelopment()
|
|
|
|
// First run
|
|
_, err := Migrate(db, logger)
|
|
if err != nil {
|
|
t.Fatalf("First Migrate failed: %v", err)
|
|
}
|
|
|
|
// Second run should apply 0
|
|
applied, err := Migrate(db, logger)
|
|
if err != nil {
|
|
t.Fatalf("Second Migrate failed: %v", err)
|
|
}
|
|
if applied != 0 {
|
|
t.Errorf("expected 0 migrations on second run, got %d", applied)
|
|
}
|
|
}
|
|
|
|
func TestMigrateExistingDBWithoutSchemaVersion(t *testing.T) {
|
|
db := testDB(t)
|
|
defer func() { _ = db.Close() }()
|
|
|
|
logger, _ := zap.NewDevelopment()
|
|
|
|
// Simulate an existing database that has the full 0001 schema applied
|
|
// but no schema_version tracking yet (pre-migration-system installs).
|
|
// First, run all migrations normally to get the real schema...
|
|
_, err := Migrate(db, logger)
|
|
if err != nil {
|
|
t.Fatalf("Initial Migrate failed: %v", err)
|
|
}
|
|
// ...then drop schema_version to simulate the pre-tracking state.
|
|
_, err = db.Exec("DROP TABLE schema_version")
|
|
if err != nil {
|
|
t.Fatalf("Failed to drop schema_version: %v", err)
|
|
}
|
|
|
|
// Migrate should detect existing DB and auto-mark baseline,
|
|
// then apply remaining migrations (0002-0005).
|
|
applied, err := Migrate(db, logger)
|
|
if err != nil {
|
|
t.Fatalf("Migrate failed: %v", err)
|
|
}
|
|
// Baseline (0001) is auto-marked, then 0002-0005 are applied = 4
|
|
if applied != 4 {
|
|
t.Errorf("expected 4 migrations applied (baseline auto-marked, 0002-0005 applied), got %d", applied)
|
|
}
|
|
|
|
ver, err := Version(db)
|
|
if err != nil {
|
|
t.Fatalf("Version failed: %v", err)
|
|
}
|
|
if ver != 5 {
|
|
t.Errorf("expected version 5, got %d", ver)
|
|
}
|
|
}
|
|
|
|
func TestVersionEmptyDB(t *testing.T) {
|
|
db := testDB(t)
|
|
defer func() { _ = db.Close() }()
|
|
|
|
ver, err := Version(db)
|
|
if err != nil {
|
|
t.Fatalf("Version failed: %v", err)
|
|
}
|
|
if ver != 0 {
|
|
t.Errorf("expected version 0 on empty DB, got %d", ver)
|
|
}
|
|
}
|
|
|
|
func TestApplySeedData(t *testing.T) {
|
|
db := testDB(t)
|
|
defer func() { _ = db.Close() }()
|
|
|
|
logger, _ := zap.NewDevelopment()
|
|
|
|
// Apply schema first
|
|
_, err := Migrate(db, logger)
|
|
if err != nil {
|
|
t.Fatalf("Migrate failed: %v", err)
|
|
}
|
|
|
|
count, err := ApplySeedData(db, logger)
|
|
if err != nil {
|
|
t.Fatalf("ApplySeedData failed: %v", err)
|
|
}
|
|
if count == 0 {
|
|
t.Error("expected at least 1 seed file applied, got 0")
|
|
}
|
|
}
|
|
|
|
func TestParseVersion(t *testing.T) {
|
|
tests := []struct {
|
|
filename string
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{"0001_init.sql", 1, false},
|
|
{"0002_add_users.sql", 2, false},
|
|
{"0100_big_change.sql", 100, false},
|
|
{"bad.sql", 0, true},
|
|
}
|
|
for _, tt := range tests {
|
|
got, err := parseVersion(tt.filename)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("parseVersion(%q) error = %v, wantErr %v", tt.filename, err, tt.wantErr)
|
|
continue
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("parseVersion(%q) = %d, want %d", tt.filename, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReadMigrations(t *testing.T) {
|
|
migrations, err := readMigrations()
|
|
if err != nil {
|
|
t.Fatalf("readMigrations failed: %v", err)
|
|
}
|
|
if len(migrations) == 0 {
|
|
t.Fatal("expected at least 1 migration, got 0")
|
|
}
|
|
if migrations[0].version != 1 {
|
|
t.Errorf("first migration version = %d, want 1", migrations[0].version)
|
|
}
|
|
if migrations[0].filename != "0001_init.sql" {
|
|
t.Errorf("first migration filename = %q, want 0001_init.sql", migrations[0].filename)
|
|
}
|
|
}
|
|
|
|
func TestReadMigrations_Sorted(t *testing.T) {
|
|
migrations, err := readMigrations()
|
|
if err != nil {
|
|
t.Fatalf("readMigrations failed: %v", err)
|
|
}
|
|
for i := 1; i < len(migrations); i++ {
|
|
if migrations[i].version <= migrations[i-1].version {
|
|
t.Errorf("migrations not sorted: version %d at index %d follows version %d at index %d",
|
|
migrations[i].version, i, migrations[i-1].version, i-1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReadMigrations_AllHaveSQL(t *testing.T) {
|
|
migrations, err := readMigrations()
|
|
if err != nil {
|
|
t.Fatalf("readMigrations failed: %v", err)
|
|
}
|
|
for _, m := range migrations {
|
|
if m.sql == "" {
|
|
t.Errorf("migration %s has empty SQL", m.filename)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReadMigrations_BaselineIsLargest(t *testing.T) {
|
|
migrations, err := readMigrations()
|
|
if err != nil {
|
|
t.Fatalf("readMigrations failed: %v", err)
|
|
}
|
|
if len(migrations) < 2 {
|
|
t.Skip("not enough migrations to compare sizes")
|
|
}
|
|
// The baseline (0001_init.sql) should be the largest migration.
|
|
baselineLen := len(migrations[0].sql)
|
|
for _, m := range migrations[1:] {
|
|
if len(m.sql) > baselineLen {
|
|
t.Errorf("migration %s (%d bytes) is larger than baseline (%d bytes)",
|
|
m.filename, len(m.sql), baselineLen)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseVersion_Comprehensive(t *testing.T) {
|
|
tests := []struct {
|
|
filename string
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{"0001_init.sql", 1, false},
|
|
{"0002_add_users.sql", 2, false},
|
|
{"0100_big_change.sql", 100, false},
|
|
{"9999_final.sql", 9999, false},
|
|
{"bad.sql", 0, true},
|
|
{"noseparator", 0, true},
|
|
{"abc_description.sql", 0, true},
|
|
}
|
|
for _, tt := range tests {
|
|
got, err := parseVersion(tt.filename)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("parseVersion(%q) error = %v, wantErr %v", tt.filename, err, tt.wantErr)
|
|
continue
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("parseVersion(%q) = %d, want %d", tt.filename, got, tt.want)
|
|
}
|
|
}
|
|
}
|