MCPcopy Create free account
hub / github.com/PerpetualSoftware/pad / TestClassifyHTTPStatus

Function TestClassifyHTTPStatus

internal/mcp/errors_test.go:158–216  ·  view source on GitHub ↗

TestClassifyHTTPStatus covers the HTTP status → ErrorCode mapping. Each documented status maps to the expected code; unmapped statuses fall through to server_error.

(t *testing.T)

Source from the content-addressed store, hash-verified

156// Each documented status maps to the expected code; unmapped statuses
157// fall through to server_error.
158func TestClassifyHTTPStatus(t *testing.T) {
159 cases := []struct {
160 name string
161 status int
162 body string
163 wantCode ErrorCode
164 }{
165 {"401_unauthorized", http.StatusUnauthorized, "token invalid", ErrAuthRequired},
166 {"403_forbidden", http.StatusForbidden, "role insufficient", ErrPermissionDenied},
167 {"404_item", http.StatusNotFound, "item TASK-99 not found", ErrItemNotFound},
168 {"404_workspace", http.StatusNotFound, "workspace foo not visible", ErrUnknownWorkspace},
169 {"409_conflict", http.StatusConflict, "version mismatch", ErrConflict},
170 {"422_validation", http.StatusUnprocessableEntity, "title required", ErrValidationFailed},
171 {"400_validation", http.StatusBadRequest, "bad input", ErrValidationFailed},
172 // TASK-1078: 5xx now maps to upstream_error (distinct from
173 // server_error, which is reserved for dispatcher internal
174 // failures + un-mapped 4xx). Pre-fix every 5xx collapsed to
175 // ErrServerError; the new code lets agents distinguish
176 // "backend hiccup, retry" from "dispatcher bug, escalate."
177 {"500_upstream", http.StatusInternalServerError, "boom", ErrUpstreamError},
178 {"503_upstream", http.StatusServiceUnavailable, "down", ErrUpstreamError},
179 // BUG-1430: 429 is now a first-class ErrRateLimited code,
180 // distinct from the generic ErrServerError "other 4xx" bucket
181 // it landed in pre-fix. Agents implementing backoff key off
182 // the code without parsing free-form text.
183 {"429_rate_limited", http.StatusTooManyRequests, "rate_limited", ErrRateLimited},
184 {"418_other", http.StatusTeapot, "weird", ErrServerError},
185 }
186 for _, tc := range cases {
187 t.Run(tc.name, func(t *testing.T) {
188 res := classifyHTTPStatus(context.Background(),
189 "pad item list", tc.status, []byte(tc.body), nil)
190 if !res.IsError {
191 t.Errorf("expected IsError")
192 }
193 env := decodeEnvelope(t, res)
194 if env.Error.Code != tc.wantCode {
195 t.Errorf("Code = %q, want %q", env.Error.Code, tc.wantCode)
196 }
197 // Body fragment preservation contract changed in TASK-1077:
198 // pre-fix the raw body was always copied into Hint, which
199 // would leak unstructured upstream output (Codex review #387
200 // round 1). Post-fix only structured-envelope-shaped bodies
201 // have their inner message lifted; unstructured bodies are
202 // dropped to avoid leaking tokens / debug dumps.
203 //
204 // Each test case above passes a bare string body — those
205 // hit the safe-fallback path and don't appear in the
206 // envelope. The unknown_workspace branch can also legitimately
207 // emit an empty Hint (no upstream message AND no extractable
208 // slug AND no lister wired). This test asserts the code
209 // mapping; body preservation specifically is tested in
210 // TestExtractUpstreamMessage with structured input, and hint
211 // shape per code is tested in
212 // TestClassifyHTTPStatus_HintsAreActionable.
213 _ = env.Error.Hint
214 })
215 }

Callers

nothing calls this directly

Calls 3

classifyHTTPStatusFunction · 0.85
decodeEnvelopeFunction · 0.85
RunMethod · 0.80

Tested by

no test coverage detected