MCPcopy Create free account
hub / github.com/TestSprite/testsprite-cli / parseEnvelopeBody

Function parseEnvelopeBody

src/lib/errors.ts:355–444  ·  view source on GitHub ↗
(raw: unknown, httpStatus?: number, apiUrl?: string)

Source from the content-addressed store, hash-verified

353}
354
355function parseEnvelopeBody(raw: unknown, httpStatus?: number, apiUrl?: string): ErrorEnvelopeBody {
356 if (typeof raw !== 'object' || raw === null) {
357 const fallbackCode = codeFromHttpStatus(httpStatus);
358 return {
359 code: fallbackCode,
360 message:
361 fallbackCode === 'INTERNAL'
362 ? 'Server returned a malformed error response.'
363 : `Server returned ${httpStatus} without an error envelope.`,
364 nextAction:
365 fallbackCode === 'INTERNAL'
366 ? 'Server error. Retry once; if it persists, report the `requestId` to support@testsprite.com.'
367 : '',
368 requestId: 'unknown',
369 details: {},
370 };
371 }
372 const obj = raw as Record<string, unknown>;
373
374 // Raw Nest/Express 404 shape: `{ message, error: "Not Found", statusCode }`.
375 // The current parser was matching `obj.error` (a string) and falling to
376 // `Server error.` while losing the actual `message` from the body
377 // (e.g. `Cannot POST /api/cli/v1/tests/{id}/runs`). Detect this shape
378 // explicitly so the user sees something actionable like
379 // "endpoint not deployed" instead of the generic CLI failure text.
380 if (typeof obj.error === 'string' && typeof obj.message === 'string') {
381 const message = obj.message;
382 const cannotMethod = /^Cannot (GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS) /.exec(message);
383 const isRouteMissing404 = (httpStatus === 404 || obj.statusCode === 404) && !!cannotMethod;
384 return {
385 code: codeFromHttpStatus(httpStatus),
386 message: isRouteMissing404
387 ? `${message} — endpoint not available on the current backend deployment.`
388 : message,
389 nextAction: isRouteMissing404
390 ? 'Verify the CLI is targeting the right environment (check `testsprite auth status` for `env`), and confirm the backend has the corresponding M3.3 piece deployed.'
391 : '',
392 requestId: 'unknown',
393 details: { statusCode: obj.statusCode },
394 };
395 }
396
397 const errorObj = (obj.error ?? obj) as Record<string, unknown>;
398 const rawCode = isErrorCode(errorObj.code) ? errorObj.code : codeFromHttpStatus(httpStatus);
399 const message = typeof errorObj.message === 'string' ? errorObj.message : 'Server error.';
400 const details =
401 typeof errorObj.details === 'object' && errorObj.details !== null
402 ? (errorObj.details as Record<string, unknown>)
403 : {};
404 const nextAction = typeof errorObj.nextAction === 'string' ? errorObj.nextAction : '';
405 const requestId = typeof errorObj.requestId === 'string' ? errorObj.requestId : 'unknown';
406
407 // Client-side re-map: RATE_LIMITED with credits signal -> INSUFFICIENT_CREDITS.
408 // This makes the code non-retriable and sets exit 12 before the retry
409 // decision in http.ts sees it, so BOTH retry and exit-code paths are correct.
410 // A nextAction pointing to the billing page is synthesized when the backend
411 // didn't supply one (older backend versions).
412 if (isInsufficientCredits(rawCode, message, details)) {

Callers 1

fromEnvelopeMethod · 0.85

Calls 4

codeFromHttpStatusFunction · 0.85
isErrorCodeFunction · 0.85
isInsufficientCreditsFunction · 0.85
resolvePortalBaseFunction · 0.85

Tested by

no test coverage detected