fix: address audit execution failures and unsafe type conversions

This commit is contained in:
2026-10-12 19:53:19 +02:00
parent bfe9852826
commit 2fa48335d3
29 changed files with 1630 additions and 421 deletions
+16 -16
View File
@@ -502,30 +502,30 @@ func (w *Writer) executeDatabaseSQL(db *models.Database, connString string) erro
// Connect to database
dbConn, err := sql.Open("mssql", connString)
if err != nil {
return fmt.Errorf("failed to connect to database: %w", err)
return writers.FailExecution(w.options, "SQL Server", writers.StatementOperations(statements), fmt.Errorf("failed to connect to database: %w", err))
}
defer dbConn.Close()
ctx := context.Background()
if err = dbConn.PingContext(ctx); err != nil {
return fmt.Errorf("failed to ping database: %w", err)
return writers.FailExecution(w.options, "SQL Server", writers.StatementOperations(statements), fmt.Errorf("failed to ping database: %w", err))
}
// Execute statements
executedCount := 0
for i, stmt := range statements {
fmt.Fprintf(os.Stderr, "Executing statement %d/%d...\n", i+1, len(statements))
if _, execErr := dbConn.ExecContext(ctx, stmt); execErr != nil {
fmt.Fprintf(os.Stderr, "⚠ Warning: Statement failed: %v\n", execErr)
continue
}
executedCount++
var tx *sql.Tx
backend := writers.ExecutionBackend{
Exec: func(ctx context.Context, _ int, stmt string) error {
if tx != nil {
_, err := tx.ExecContext(ctx, stmt)
return err
}
_, err := dbConn.ExecContext(ctx, stmt)
return err
},
Begin: func(ctx context.Context) error { var err error; tx, err = dbConn.BeginTx(ctx, nil); return err },
Commit: func(context.Context) error { return tx.Commit() },
Rollback: func(context.Context) error { return tx.Rollback() },
}
fmt.Fprintf(os.Stderr, "✓ Successfully executed %d statements\n", executedCount)
return nil
return writers.RunExecution(ctx, w.options, &writers.ExecutionReport{Dialect: "SQL Server"}, writers.StatementOperations(statements), backend)
}
// generateStatements renders the same script WriteDatabase would produce and
+69
View File
@@ -0,0 +1,69 @@
package mssql
import (
"database/sql"
"errors"
"fmt"
"os"
"testing"
"time"
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/writers"
)
func TestLive_ExecutionFailureModes(t *testing.T) {
connection := os.Getenv("RELSPEC_TEST_MSSQL_CONN")
if connection == "" {
t.Skip("RELSPEC_TEST_MSSQL_CONN not set")
}
conn, err := sql.Open("mssql", connection)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
for _, mode := range []string{"stop", "continue", "atomic"} {
t.Run(mode, func(t *testing.T) {
prefix := fmt.Sprintf("audit_%d_", time.Now().UnixNano())
db := models.InitDatabase("audit")
schema := models.InitSchema("dbo")
for _, name := range []string{"first", "bad", "last"} {
table := models.InitTable(prefix+name, "dbo")
col := models.InitColumn("id", table.Name, "dbo")
col.Type = "integer"
if name == "bad" {
duplicate := models.InitColumn("id", table.Name, "dbo")
duplicate.Type = "integer"
table.Columns["duplicate"] = duplicate
}
table.Columns[col.Name] = col
schema.Tables = append(schema.Tables, table)
}
db.Schemas = append(db.Schemas, schema)
t.Cleanup(func() {
for _, name := range []string{"first", "bad", "last"} {
_, _ = conn.Exec("DROP TABLE IF EXISTS dbo." + prefix + name)
}
})
opts := &writers.WriterOptions{ContinueOnError: mode == "continue", AtomicExecution: mode == "atomic", Metadata: map[string]interface{}{"connection_string": connection}}
err := NewWriter(opts).WriteDatabase(db)
var failure *writers.ExecutionFailure
if !errors.As(err, &failure) || failure.Report.FailedStatements == 0 {
t.Fatalf("expected structured error: %v", err)
}
for _, name := range []string{"first", "last"} {
var count int
if err := conn.QueryRow("SELECT count(*) FROM sys.tables WHERE name=?", prefix+name).Scan(&count); err != nil {
t.Fatal(err)
}
want := mode != "atomic" && (name == "first" || mode == "continue")
if (count == 1) != want {
t.Fatalf("%s exists=%v want=%v", name, count == 1, want)
}
}
if mode == "atomic" && (failure.Report.Outcome != "rolled_back" || failure.Report.RolledBackStatements == 0 || failure.Report.ExecutedStatements != 0) {
t.Fatalf("incorrect rollback report: %+v", failure.Report)
}
})
}
}