package engine
import (
"errors"
"testing"
)
func TestFailureAttribution(t *testing.T) {
base := errors.New("detail")
tests := []struct {
name string
result string
err error
wantClass string
wantReason string
}{
{name: "success", result: "success", wantClass: "none", wantReason: "success"},
{name: "timeout", result: "timeout", err: ErrTimedOut, wantClass: "user", wantReason: "timeout"},
{name: "cancelled", result: "cancelled", err: ErrWorkflowCanceled, wantClass: "user", wantReason: "cancelled"},
{
name: "user command",
result: "failure",
err: ClassifiedFailure(FailureClassUser, FailureReasonCommandFailed, base),
wantClass: "user",
wantReason: "command_failed",
},
{
name: "infrastructure",
result: "failure",
err: ClassifiedFailure(FailureClassInfrastructure, FailureReasonRuntimeFailed, base),
wantClass: "infrastructure",
wantReason: "runtime_failed",
},
{
name: "policy",
result: "failure",
err: ClassifiedFailure(FailureClassPolicy, FailureReasonQuotaDenied, base),
wantClass: "policy",
wantReason: "quota_denied",
},
{name: "capacity", result: "failure", err: ErrNoWorkflowSlots, wantClass: "infrastructure", wantReason: "capacity_unavailable"},
{name: "unclassified", result: "failure", err: base, wantClass: "infrastructure", wantReason: "runtime_failed"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotClass, gotReason := FailureAttribution(tt.result, tt.err)
if gotClass != tt.wantClass || gotReason != tt.wantReason {
t.Fatalf("FailureAttribution() = (%q, %q), want (%q, %q)", gotClass, gotReason, tt.wantClass, tt.wantReason)
}
})
}
}
func TestClassifiedFailurePreservesCauseAndFirstClassification(t *testing.T) {
cause := errors.New("cause")
first := ClassifiedFailure(FailureClassUser, FailureReasonCommandFailed, cause)
second := ClassifiedFailure(FailureClassInfrastructure, FailureReasonRuntimeFailed, first)
if second != first {
t.Fatal("nested classification replaced the original leaf classification")
}
if !errors.Is(second, cause) {
t.Fatal("classified failure does not preserve errors.Is")
}
}