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 := openMigrationTestDB(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) } }) }