feat: funciones impuras migration_create, migration_up, migration_down, migration_status
Fase 2 del issue 0015. MigrationCreate (crea archivo .sql template con version auto-calculada), MigrationUp (aplica migraciones pendientes en transacciones individuales), MigrationDown (revierte ultimas N via down_sql de _migrations), MigrationGetStatus (cruza disco con BD, detecta orphaned). Tests de integracion: ciclo completo create->up->status->down->status. 26 tests, todos pasan. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMigrationDown(t *testing.T) {
|
||||
t.Run("revertir ultima migracion elimina registro y ejecuta down_sql", func(t *testing.T) {
|
||||
db := openTestDB(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")
|
||||
|
||||
if _, err := MigrationUp(db, dir); err != nil {
|
||||
t.Fatalf("up failed: %v", err)
|
||||
}
|
||||
|
||||
reverted, err := MigrationDown(db, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("down failed: %v", err)
|
||||
}
|
||||
if len(reverted) != 1 {
|
||||
t.Errorf("expected 1 reverted, got %d", len(reverted))
|
||||
}
|
||||
if reverted[0].Version != 2 {
|
||||
t.Errorf("expected version 2 reverted, got %d", reverted[0].Version)
|
||||
}
|
||||
|
||||
// roles table should be gone
|
||||
if err := db.QueryRow("SELECT COUNT(*) FROM roles").Err(); err == nil {
|
||||
t.Error("roles table should not exist after down")
|
||||
}
|
||||
|
||||
// users table should still exist
|
||||
var count int
|
||||
if err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count); err != nil {
|
||||
t.Errorf("users table should still exist: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("revertir n migraciones revierte en orden descendente", func(t *testing.T) {
|
||||
db := openTestDB(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")
|
||||
writeMigrationFile(t, dir, "003_create_logs.sql",
|
||||
"-- +up\nCREATE TABLE logs (id INTEGER PRIMARY KEY);\n-- +down\nDROP TABLE IF EXISTS logs;\n")
|
||||
|
||||
if _, err := MigrationUp(db, dir); err != nil {
|
||||
t.Fatalf("up failed: %v", err)
|
||||
}
|
||||
|
||||
reverted, err := MigrationDown(db, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("down 2 failed: %v", err)
|
||||
}
|
||||
if len(reverted) != 2 {
|
||||
t.Errorf("expected 2 reverted, got %d", len(reverted))
|
||||
}
|
||||
// Should be reverted in descending order: 3, 2
|
||||
if reverted[0].Version != 3 || reverted[1].Version != 2 {
|
||||
t.Errorf("reverted order wrong: got %d, %d", reverted[0].Version, reverted[1].Version)
|
||||
}
|
||||
|
||||
// users table should still exist (migration 1 not reverted)
|
||||
var count int
|
||||
if err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count); err != nil {
|
||||
t.Errorf("users table should still exist: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("n cero no revierte nada", func(t *testing.T) {
|
||||
db := openTestDB(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")
|
||||
|
||||
if _, err := MigrationUp(db, dir); err != nil {
|
||||
t.Fatalf("up failed: %v", err)
|
||||
}
|
||||
|
||||
reverted, err := MigrationDown(db, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(reverted) != 0 {
|
||||
t.Errorf("expected 0 reverted for n=0, got %d", len(reverted))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("base de datos sin migraciones retorna slice vacio", func(t *testing.T) {
|
||||
db := openTestDB(t)
|
||||
dir := t.TempDir()
|
||||
|
||||
// Apply to create _migrations table
|
||||
if _, err := MigrationUp(db, dir); err != nil {
|
||||
t.Fatalf("up failed: %v", err)
|
||||
}
|
||||
|
||||
reverted, err := MigrationDown(db, 5)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(reverted) != 0 {
|
||||
t.Errorf("expected 0 reverted from empty DB, got %d", len(reverted))
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user