diff --git a/internal/errkind/errkind_test.go b/internal/errkind/errkind_test.go new file mode 100644 index 0000000..cba5ce5 --- /dev/null +++ b/internal/errkind/errkind_test.go @@ -0,0 +1,64 @@ +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") +}