Files
relspecgo/pkg/writers/execution_test.go
T

97 lines
4.1 KiB
Go

package writers
import (
"context"
"errors"
"path/filepath"
"strings"
"testing"
)
func TestExecutionOutcomes(t *testing.T) {
for _, atomic := range []bool{false, true} {
for _, continuation := range []bool{false, true} {
t.Run(strings.Join([]string{map[bool]string{false: "independent", true: "atomic"}[atomic], map[bool]string{false: "stop", true: "continue"}[continuation]}, "/"), func(t *testing.T) {
attempted, rollbacks := 0, 0
cause := errors.New("injected failure")
backend := ExecutionBackend{
Exec: func(_ context.Context, i int, _ string) error {
attempted++
if i == 1 {
return cause
}
return nil
},
Begin: func(context.Context) error { return nil }, Commit: func(context.Context) error { t.Fatal("must not commit"); return nil },
Rollback: func(context.Context) error { rollbacks++; return nil },
}
opts := &WriterOptions{ContinueOnError: continuation, AtomicExecution: atomic}
report := &ExecutionReport{Dialect: "test"}
err := RunExecution(context.Background(), opts, report, StatementOperations([]string{"one", "bad", "three"}), backend)
if atomic && continuation {
if err == nil || attempted != 0 || report.SkippedStatements != 3 {
t.Fatalf("invalid options must not execute: %+v %v", report, err)
}
return
}
var failure *ExecutionFailure
if !errors.As(err, &failure) || !errors.Is(err, cause) {
t.Fatalf("must preserve typed report and cause: %v", err)
}
if report.FailedStatements != 1 || report.TotalStatements != 3 || report.Errors[0].StatementNumber != 2 {
t.Fatalf("failure report: %+v", report)
}
if atomic {
if rollbacks != 1 || report.ExecutedStatements != 0 || report.RolledBackStatements != 1 || report.Operations[0].Status != "rolled_back" || report.Outcome != "rolled_back" {
t.Fatalf("rollback report: %+v", report)
}
} else if continuation {
if attempted != 3 || report.ExecutedStatements != 2 || report.SkippedStatements != 0 {
t.Fatalf("continuation report: %+v", report)
}
} else if attempted != 2 || report.ExecutedStatements != 1 || report.SkippedStatements != 1 {
t.Fatalf("stop report: %+v", report)
}
if opts.Metadata["execution_report"] != report {
t.Fatal("typed results missing")
}
})
}
}
}
func TestExecutionUncertainFinalization(t *testing.T) {
for _, failStatement := range []bool{false, true} {
t.Run(map[bool]string{false: "commit", true: "rollback"}[failStatement], func(t *testing.T) {
finalErr := errors.New("connection lost during finalization")
backend := ExecutionBackend{
Begin: func(context.Context) error { return nil },
Exec: func(_ context.Context, i int, _ string) error {
if failStatement && i == 1 {
return errors.New("statement failed")
}
return nil
},
Commit: func(context.Context) error { return finalErr }, Rollback: func(context.Context) error { return finalErr },
}
report := &ExecutionReport{}
err := RunExecution(context.Background(), &WriterOptions{AtomicExecution: true}, report, StatementOperations([]string{"one", "two"}), backend)
if !errors.Is(err, finalErr) || report.Outcome != "unknown" || report.Operations[0].Status != "unknown" || report.RolledBackStatements != 0 || report.ExecutedStatements != 0 {
t.Fatalf("uncertain outcome misreported: %+v %v", report, err)
}
})
}
}
func TestExecutionReportFailureAndFatalPreflight(t *testing.T) {
opts := &WriterOptions{ContinueOnError: true, Metadata: map[string]interface{}{"report_path": filepath.Join(t.TempDir(), "missing", "report.json")}}
report := &ExecutionReport{}
calls := 0
ops := []ExecutionOperation{{Statement: "preflight", StopOnFailure: true}, {Statement: "must not run"}}
err := RunExecution(context.Background(), opts, report, ops, ExecutionBackend{Exec: func(context.Context, int, string) error { calls++; return errors.New("invalid data") }})
var failure *ExecutionFailure
if !errors.As(err, &failure) || !strings.Contains(err.Error(), "requested execution report") || calls != 1 || report.SkippedStatements != 1 {
t.Fatalf("must retain both failures and stop: %+v %v", report, err)
}
}