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,110 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMigrationStatus(t *testing.T) {
|
||||
t.Run("migraciones en disco pero no en BD aparecen como pending", 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")
|
||||
|
||||
statuses, err := MigrationGetStatus(db, dir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(statuses) != 2 {
|
||||
t.Fatalf("expected 2 statuses, got %d", len(statuses))
|
||||
}
|
||||
for _, s := range statuses {
|
||||
if s.Applied {
|
||||
t.Errorf("version %d should be pending, got applied", s.Version)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("migraciones aplicadas aparecen con Applied=true y AppliedAt", 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)
|
||||
}
|
||||
|
||||
statuses, err := MigrationGetStatus(db, dir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(statuses) != 2 {
|
||||
t.Fatalf("expected 2 statuses, got %d", len(statuses))
|
||||
}
|
||||
for _, s := range statuses {
|
||||
if !s.Applied {
|
||||
t.Errorf("version %d should be applied, got pending", s.Version)
|
||||
}
|
||||
if s.AppliedAt.IsZero() {
|
||||
t.Errorf("version %d AppliedAt should not be zero", s.Version)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("migraciones aplicadas sin archivo en disco aparecen como orphaned", 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)
|
||||
}
|
||||
|
||||
// Remove file from disk but migration is applied in DB
|
||||
// Now check status with an empty dir
|
||||
emptyDir := t.TempDir()
|
||||
statuses, err := MigrationGetStatus(db, emptyDir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(statuses) != 1 {
|
||||
t.Fatalf("expected 1 status (orphaned), got %d", len(statuses))
|
||||
}
|
||||
s := statuses[0]
|
||||
if !s.Applied {
|
||||
t.Errorf("orphaned migration should have Applied=true")
|
||||
}
|
||||
if !containsStr(s.Name, "orphaned") {
|
||||
t.Errorf("orphaned migration name should contain 'orphaned', got %q", s.Name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("base de datos sin tabla _migrations retorna todas como pending", 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")
|
||||
|
||||
// Do NOT call MigrationUp — _migrations table doesn't exist
|
||||
statuses, err := MigrationGetStatus(db, dir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(statuses) != 1 {
|
||||
t.Fatalf("expected 1 status, got %d", len(statuses))
|
||||
}
|
||||
if statuses[0].Applied {
|
||||
t.Errorf("migration should be pending when _migrations does not exist")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user