package errkind_test import ( "context" "errors" "fmt" "net" "net/url" "testing" "github.com/stretchr/testify/assert" "git.d-ma.be/mathias/tapir/internal/errkind" ) // Of reduces an error to a short kind that is safe to log, store and show: it never // carries the error's own text, because that text can hold a provider's response body, a // file path or a word from a private transcript (ADR-035 review F3, F5). type statusErr struct{ code int } func (e statusErr) Error() string { return fmt.Sprintf("gateway said: %d and echoed the transcript", e.code) } func (e statusErr) HTTPStatus() int { return e.code } type timeoutNet struct{} func (timeoutNet) Error() string { return "i/o timeout to 10.0.0.1" } func (timeoutNet) Timeout() bool { return true } func (timeoutNet) Temporary() bool { return true } func TestOf_ReducesAnErrorToAKindAndNeverItsText(t *testing.T) { tests := []struct { name string err error want string }{ {"nothing", nil, ""}, {"a deadline", context.DeadlineExceeded, "timeout"}, {"a wrapped deadline", fmt.Errorf("transcribe: %w", context.DeadlineExceeded), "timeout"}, {"a cancellation", fmt.Errorf("job: %w", context.Canceled), "canceled"}, {"an HTTP status", statusErr{code: 502}, "http_502"}, {"a wrapped HTTP status", fmt.Errorf("model: %w", statusErr{code: 400}), "http_400"}, {"a network timeout", timeoutNet{}, "timeout"}, {"a network failure", &net.OpError{Op: "dial", Err: errors.New("connection refused")}, "network"}, {"a url error wrapping a refusal", &url.Error{Op: "Post", URL: "http://gateway/secret-path", Err: errors.New("refused")}, "network"}, {"anything else", errors.New("moov atom not found in /spool/upload-555"), "error"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.want, errkind.Of(tt.err)) }) } } func TestOf_AKindCarriesNoWordFromTheError(t *testing.T) { err := fmt.Errorf("convert /spool/upload-555: %w", errors.New("Anders ansvarar för virket")) got := errkind.Of(err) assert.NotContains(t, got, "Anders") assert.NotContains(t, got, "spool") }