-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors_test.go
More file actions
184 lines (165 loc) · 4.8 KB
/
errors_test.go
File metadata and controls
184 lines (165 loc) · 4.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package rdapapi
import (
"errors"
"fmt"
"testing"
)
func TestAPIErrorImplementsError(t *testing.T) {
err := &APIError{
StatusCode: 400,
Code: "invalid_input",
Message: "bad request",
RetryAfter: 0,
}
var _ error = err // compile-time check
if got := err.Error(); got != "bad request" {
t.Errorf("Error() = %q, want %q", got, "bad request")
}
}
func TestAPIErrorUnwrapReturnsNil(t *testing.T) {
err := &APIError{Message: "test"}
if got := err.Unwrap(); got != nil {
t.Errorf("Unwrap() = %v, want nil", got)
}
}
func TestNewErrorTypedErrors(t *testing.T) {
tests := []struct {
status int
code string
message string
retryAfter int
wantType string
}{
{400, "invalid_input", "bad domain", 0, "ValidationError"},
{401, "unauthenticated", "invalid key", 0, "AuthenticationError"},
{403, "subscription_required", "no plan", 0, "SubscriptionRequiredError"},
{404, "not_found", "not found", 0, "NotFoundError"},
{429, "rate_limited", "too many", 30, "RateLimitError"},
{502, "upstream_error", "upstream fail", 0, "UpstreamError"},
{503, "temporarily_unavailable", "temporarily unavailable", 300, "TemporarilyUnavailableError"},
}
for _, tt := range tests {
t.Run(tt.wantType, func(t *testing.T) {
err := newError(tt.status, tt.code, tt.message, tt.retryAfter)
// Check Error() returns the message.
if got := err.Error(); got != tt.message {
t.Errorf("Error() = %q, want %q", got, tt.message)
}
// Check errors.As works for each typed error.
switch tt.status {
case 400:
var target *ValidationError
if !errors.As(err, &target) {
t.Fatal("errors.As failed for ValidationError")
}
checkBase(t, target.APIError, tt)
case 401:
var target *AuthenticationError
if !errors.As(err, &target) {
t.Fatal("errors.As failed for AuthenticationError")
}
checkBase(t, target.APIError, tt)
case 403:
var target *SubscriptionRequiredError
if !errors.As(err, &target) {
t.Fatal("errors.As failed for SubscriptionRequiredError")
}
checkBase(t, target.APIError, tt)
case 404:
var target *NotFoundError
if !errors.As(err, &target) {
t.Fatal("errors.As failed for NotFoundError")
}
checkBase(t, target.APIError, tt)
case 429:
var target *RateLimitError
if !errors.As(err, &target) {
t.Fatal("errors.As failed for RateLimitError")
}
checkBase(t, target.APIError, tt)
if target.RetryAfter != 30 {
t.Errorf("RetryAfter = %d, want 30", target.RetryAfter)
}
case 502:
var target *UpstreamError
if !errors.As(err, &target) {
t.Fatal("errors.As failed for UpstreamError")
}
checkBase(t, target.APIError, tt)
case 503:
var target *TemporarilyUnavailableError
if !errors.As(err, &target) {
t.Fatal("errors.As failed for TemporarilyUnavailableError")
}
checkBase(t, target.APIError, tt)
if target.RetryAfter != 300 {
t.Errorf("RetryAfter = %d, want 300", target.RetryAfter)
}
}
// Typed errors embed *APIError, accessible via the field.
// errors.As matches the concrete typed error, not the embedded base.
})
}
}
func checkBase(t *testing.T, base *APIError, tt struct {
status int
code string
message string
retryAfter int
wantType string
},
) {
t.Helper()
if base.StatusCode != tt.status {
t.Errorf("StatusCode = %d, want %d", base.StatusCode, tt.status)
}
if base.Code != tt.code {
t.Errorf("Code = %q, want %q", base.Code, tt.code)
}
if base.Message != tt.message {
t.Errorf("Message = %q, want %q", base.Message, tt.message)
}
}
func TestNewErrorUnknownStatus(t *testing.T) {
err := newError(500, "server_error", "internal", 0)
// Should be a base APIError, not a typed one.
var apiErr *APIError
if !errors.As(err, &apiErr) {
t.Fatal("expected APIError for unknown status")
}
if apiErr.StatusCode != 500 {
t.Errorf("StatusCode = %d, want 500", apiErr.StatusCode)
}
if apiErr.Message != "internal" {
t.Errorf("Message = %q, want %q", apiErr.Message, "internal")
}
// Should not match any typed error.
var notFound *NotFoundError
if errors.As(err, ¬Found) {
t.Error("unexpected NotFoundError for 500")
}
}
func TestTypedErrorsDoNotMatchEachOther(t *testing.T) {
err := newError(404, "not_found", "not found", 0)
var v *ValidationError
if errors.As(err, &v) {
t.Error("NotFoundError should not match ValidationError")
}
var r *RateLimitError
if errors.As(err, &r) {
t.Error("NotFoundError should not match RateLimitError")
}
}
func TestAPIErrorFormatting(t *testing.T) {
err := &APIError{
StatusCode: 429,
Code: "rate_limited",
Message: "slow down",
RetryAfter: 60,
}
// fmt.Sprintf should use Error() method.
got := fmt.Sprintf("error: %s", err)
if got != "error: slow down" {
t.Errorf("formatted = %q, want %q", got, "error: slow down")
}
}