feat: tipos Migration/MigrationStatus y funciones puras migration_parse + migration_validate
Fase 1 del issue 0015. Tipos Go en functions/infra/migration.go con metadata en types/infra/. Funciones puras: MigrationParse (parsea filename NNN_name.sql + bloques -- +up/-- +down) y MigrationValidate (verifica secuencia, huecos, duplicados, bloques vacios). 16 tests, todos pasan. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func makeMig(version int, name, upSQL string) Migration {
|
||||
return Migration{Version: version, Name: name, UpSQL: upSQL, DownSQL: "DROP TABLE IF EXISTS t;"}
|
||||
}
|
||||
|
||||
func TestMigrationValidate(t *testing.T) {
|
||||
t.Run("secuencia valida retorna sin errores", func(t *testing.T) {
|
||||
migrations := []Migration{
|
||||
makeMig(1, "create_users", "CREATE TABLE users (id TEXT PRIMARY KEY);"),
|
||||
makeMig(2, "add_email", "ALTER TABLE users ADD COLUMN email TEXT;"),
|
||||
makeMig(3, "create_roles", "CREATE TABLE roles (id TEXT PRIMARY KEY);"),
|
||||
}
|
||||
errs := MigrationValidate(migrations)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("expected no errors, got: %v", errs)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("secuencia vacia retorna sin errores", func(t *testing.T) {
|
||||
errs := MigrationValidate([]Migration{})
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("expected no errors for empty slice, got: %v", errs)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("version duplicada reporta error", func(t *testing.T) {
|
||||
migrations := []Migration{
|
||||
makeMig(1, "create_users", "CREATE TABLE users (id TEXT PRIMARY KEY);"),
|
||||
makeMig(1, "create_users_dup", "CREATE TABLE users2 (id TEXT PRIMARY KEY);"),
|
||||
makeMig(2, "add_email", "ALTER TABLE users ADD COLUMN email TEXT;"),
|
||||
}
|
||||
errs := MigrationValidate(migrations)
|
||||
if len(errs) == 0 {
|
||||
t.Fatal("expected error for duplicate version, got none")
|
||||
}
|
||||
found := false
|
||||
for _, e := range errs {
|
||||
if containsStr(e, "duplicate") {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected 'duplicate' in errors, got: %v", errs)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("hueco en versiones reporta version faltante", func(t *testing.T) {
|
||||
migrations := []Migration{
|
||||
makeMig(1, "create_users", "CREATE TABLE users (id TEXT PRIMARY KEY);"),
|
||||
makeMig(3, "create_roles", "CREATE TABLE roles (id TEXT PRIMARY KEY);"),
|
||||
}
|
||||
errs := MigrationValidate(migrations)
|
||||
if len(errs) == 0 {
|
||||
t.Fatal("expected error for gap in versions, got none")
|
||||
}
|
||||
found := false
|
||||
for _, e := range errs {
|
||||
if containsStr(e, "gap") || containsStr(e, "missing 2") {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected gap error in errors, got: %v", errs)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("up_sql vacio reporta error", func(t *testing.T) {
|
||||
migrations := []Migration{
|
||||
{Version: 1, Name: "create_users", UpSQL: "", DownSQL: "DROP TABLE users;"},
|
||||
}
|
||||
errs := MigrationValidate(migrations)
|
||||
if len(errs) == 0 {
|
||||
t.Fatal("expected error for empty up_sql, got none")
|
||||
}
|
||||
found := false
|
||||
for _, e := range errs {
|
||||
if containsStr(e, "empty up_sql") {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected 'empty up_sql' in errors, got: %v", errs)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nombre vacio reporta error", func(t *testing.T) {
|
||||
migrations := []Migration{
|
||||
{Version: 1, Name: "", UpSQL: "CREATE TABLE users (id TEXT PRIMARY KEY);"},
|
||||
}
|
||||
errs := MigrationValidate(migrations)
|
||||
if len(errs) == 0 {
|
||||
t.Fatal("expected error for empty name, got none")
|
||||
}
|
||||
found := false
|
||||
for _, e := range errs {
|
||||
if containsStr(e, "empty name") {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected 'empty name' in errors, got: %v", errs)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("versiones que no empiezan en 1 reportan error", func(t *testing.T) {
|
||||
migrations := []Migration{
|
||||
makeMig(2, "create_users", "CREATE TABLE users (id TEXT PRIMARY KEY);"),
|
||||
makeMig(3, "add_email", "ALTER TABLE users ADD COLUMN email TEXT;"),
|
||||
}
|
||||
errs := MigrationValidate(migrations)
|
||||
if len(errs) == 0 {
|
||||
t.Fatal("expected error for versions not starting at 1, got none")
|
||||
}
|
||||
found := false
|
||||
for _, e := range errs {
|
||||
if containsStr(e, "start at 1") {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected 'start at 1' error, got: %v", errs)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple errores se reportan todos", func(t *testing.T) {
|
||||
migrations := []Migration{
|
||||
{Version: 2, Name: "", UpSQL: ""},
|
||||
{Version: 4, Name: "something", UpSQL: "CREATE TABLE x (id TEXT);"},
|
||||
}
|
||||
errs := MigrationValidate(migrations)
|
||||
// Expect: not starting at 1, gap between 2 and 4, empty name for v2, empty up_sql for v2
|
||||
if len(errs) < 3 {
|
||||
t.Errorf("expected at least 3 errors, got %d: %v", len(errs), errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func containsStr(s, sub string) bool {
|
||||
return len(s) >= len(sub) && (s == sub || len(sub) == 0 ||
|
||||
func() bool {
|
||||
for i := 0; i <= len(s)-len(sub); i++ {
|
||||
if s[i:i+len(sub)] == sub {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}())
|
||||
}
|
||||
Reference in New Issue
Block a user