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,114 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestMigrationIntegration covers the full create -> up -> status -> down -> status cycle.
|
||||
func TestMigrationIntegration(t *testing.T) {
|
||||
t.Run("ciclo completo create up status down status", func(t *testing.T) {
|
||||
db := openTestDB(t)
|
||||
dir := t.TempDir()
|
||||
|
||||
// Step 1: Create migration files using MigrationCreate
|
||||
path1, err := MigrationCreate(dir, "create_users")
|
||||
if err != nil {
|
||||
t.Fatalf("create 001 failed: %v", err)
|
||||
}
|
||||
path2, err := MigrationCreate(dir, "create_roles")
|
||||
if err != nil {
|
||||
t.Fatalf("create 002 failed: %v", err)
|
||||
}
|
||||
|
||||
// Fill in actual SQL
|
||||
sql1 := "-- 001_create_users.sql\n\n-- +up\nCREATE TABLE users (id TEXT PRIMARY KEY, name TEXT NOT NULL);\n\n-- +down\nDROP TABLE IF EXISTS users;\n"
|
||||
if err := os.WriteFile(path1, []byte(sql1), 0o644); err != nil {
|
||||
t.Fatalf("write sql1: %v", err)
|
||||
}
|
||||
sql2 := "-- 002_create_roles.sql\n\n-- +up\nCREATE TABLE roles (id TEXT PRIMARY KEY, label TEXT NOT NULL);\n\n-- +down\nDROP TABLE IF EXISTS roles;\n"
|
||||
if err := os.WriteFile(path2, []byte(sql2), 0o644); err != nil {
|
||||
t.Fatalf("write sql2: %v", err)
|
||||
}
|
||||
|
||||
// Step 2: Parse and validate
|
||||
content1, _ := os.ReadFile(filepath.Join(dir, "001_create_users.sql"))
|
||||
content2, _ := os.ReadFile(filepath.Join(dir, "002_create_roles.sql"))
|
||||
m1, err := MigrationParse("001_create_users.sql", string(content1))
|
||||
if err != nil {
|
||||
t.Fatalf("parse 001: %v", err)
|
||||
}
|
||||
m2, err := MigrationParse("002_create_roles.sql", string(content2))
|
||||
if err != nil {
|
||||
t.Fatalf("parse 002: %v", err)
|
||||
}
|
||||
validationErrs := MigrationValidate([]Migration{m1, m2})
|
||||
if len(validationErrs) > 0 {
|
||||
t.Fatalf("validate failed: %v", validationErrs)
|
||||
}
|
||||
|
||||
// Step 3: Apply up
|
||||
applied, err := MigrationUp(db, dir)
|
||||
if err != nil {
|
||||
t.Fatalf("up failed: %v", err)
|
||||
}
|
||||
if len(applied) != 2 {
|
||||
t.Errorf("up: expected 2 applied, got %d", len(applied))
|
||||
}
|
||||
|
||||
// Step 4: Check status — all applied
|
||||
statuses, err := MigrationGetStatus(db, dir)
|
||||
if err != nil {
|
||||
t.Fatalf("status after up failed: %v", err)
|
||||
}
|
||||
if len(statuses) != 2 {
|
||||
t.Errorf("status: expected 2, got %d", len(statuses))
|
||||
}
|
||||
for _, s := range statuses {
|
||||
if !s.Applied {
|
||||
t.Errorf("version %d should be applied", s.Version)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Revert the last migration
|
||||
reverted, err := MigrationDown(db, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("down failed: %v", err)
|
||||
}
|
||||
if len(reverted) != 1 || reverted[0].Version != 2 {
|
||||
t.Errorf("down: expected version 2, got %v", reverted)
|
||||
}
|
||||
|
||||
// Step 6: Check status — one applied, one pending
|
||||
statuses2, err := MigrationGetStatus(db, dir)
|
||||
if err != nil {
|
||||
t.Fatalf("status after down failed: %v", err)
|
||||
}
|
||||
if len(statuses2) != 2 {
|
||||
t.Errorf("status2: expected 2, got %d", len(statuses2))
|
||||
}
|
||||
// Version 1 should be applied, version 2 pending
|
||||
for _, s := range statuses2 {
|
||||
switch s.Version {
|
||||
case 1:
|
||||
if !s.Applied {
|
||||
t.Errorf("version 1 should still be applied")
|
||||
}
|
||||
case 2:
|
||||
if s.Applied {
|
||||
t.Errorf("version 2 should be pending after down")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7: Re-apply — should apply version 2 again
|
||||
applied2, err := MigrationUp(db, dir)
|
||||
if err != nil {
|
||||
t.Fatalf("second up failed: %v", err)
|
||||
}
|
||||
if len(applied2) != 1 || applied2[0].Version != 2 {
|
||||
t.Errorf("second up: expected version 2, got %v", applied2)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user