package infra import ( "database/sql" "os" "path/filepath" "testing" _ "github.com/mattn/go-sqlite3" ) func openMigrationTestDB(t *testing.T) *sql.DB { t.Helper() db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatalf("cannot open test DB: %v", err) } t.Cleanup(func() { db.Close() }) return db } func writeMigrationFile(t *testing.T, dir, filename, content string) { t.Helper() if err := os.WriteFile(filepath.Join(dir, filename), []byte(content), 0o644); err != nil { t.Fatalf("cannot write migration file %s: %v", filename, err) } } func TestMigrationUp(t *testing.T) { t.Run("base de datos vacia aplica todas las migraciones", func(t *testing.T) { db := openMigrationTestDB(t) dir := t.TempDir() writeMigrationFile(t, dir, "001_create_users.sql", "-- +up\nCREATE TABLE users (id TEXT PRIMARY KEY, name TEXT NOT NULL);\n-- +down\nDROP TABLE IF EXISTS users;\n") writeMigrationFile(t, dir, "002_create_roles.sql", "-- +up\nCREATE TABLE roles (id TEXT PRIMARY KEY);\n-- +down\nDROP TABLE IF EXISTS roles;\n") applied, err := MigrationUp(db, dir) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(applied) != 2 { t.Errorf("applied count: got %d, want 2", len(applied)) } if applied[0].Version != 1 || applied[1].Version != 2 { t.Errorf("applied versions: got %v", []int{applied[0].Version, applied[1].Version}) } // Verify tables were created var count int if err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count); err != nil { t.Errorf("users table not created: %v", err) } if err := db.QueryRow("SELECT COUNT(*) FROM roles").Scan(&count); err != nil { t.Errorf("roles table not created: %v", err) } }) t.Run("migraciones ya aplicadas se omiten", func(t *testing.T) { db := openMigrationTestDB(t) dir := t.TempDir() writeMigrationFile(t, dir, "001_create_users.sql", "-- +up\nCREATE TABLE users (id TEXT PRIMARY KEY);\n-- +down\nDROP TABLE IF EXISTS users;\n") writeMigrationFile(t, dir, "002_create_roles.sql", "-- +up\nCREATE TABLE roles (id TEXT PRIMARY KEY);\n-- +down\nDROP TABLE IF EXISTS roles;\n") // Apply all _, err := MigrationUp(db, dir) if err != nil { t.Fatalf("first up failed: %v", err) } // Apply again — should apply nothing applied, err := MigrationUp(db, dir) if err != nil { t.Fatalf("second up failed: %v", err) } if len(applied) != 0 { t.Errorf("expected 0 applied on second run, got %d", len(applied)) } }) t.Run("migracion con SQL invalido retorna error y deja las anteriores aplicadas", func(t *testing.T) { db := openMigrationTestDB(t) dir := t.TempDir() writeMigrationFile(t, dir, "001_create_users.sql", "-- +up\nCREATE TABLE users (id TEXT PRIMARY KEY);\n-- +down\nDROP TABLE IF EXISTS users;\n") writeMigrationFile(t, dir, "002_bad_sql.sql", "-- +up\nTHIS IS NOT VALID SQL!!!;\n-- +down\n\n") applied, err := MigrationUp(db, dir) if err == nil { t.Fatal("expected error for invalid SQL, got nil") } // Version 1 should be applied if len(applied) != 1 || applied[0].Version != 1 { t.Errorf("expected [1] applied before failure, got versions: %v", applied) } // users table should still exist (migration 1 committed) var count int if err2 := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count); err2 != nil { t.Errorf("users table not present after partial apply: %v", err2) } }) t.Run("directorio sin archivos sql no aplica nada", func(t *testing.T) { db := openMigrationTestDB(t) dir := t.TempDir() applied, err := MigrationUp(db, dir) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(applied) != 0 { t.Errorf("expected 0 applied for empty dir, got %d", len(applied)) } }) }