diff --git a/.changeset/http-error-server-error.md b/.changeset/http-error-server-error.md new file mode 100644 index 000000000..c64424aa1 --- /dev/null +++ b/.changeset/http-error-server-error.md @@ -0,0 +1,48 @@ +--- +'apollo-angular': major +--- + +**BREAKING CHANGE**: HTTP errors now return Apollo Client's `ServerError` instead of Angular's `HttpErrorResponse` + +When Apollo Server returns non-2xx HTTP status codes (status >= 300), apollo-angular's HTTP links now return `ServerError` from `@apollo/client/errors` instead of Angular's `HttpErrorResponse`. This enables proper error detection in errorLinks using `ServerError.is(error)` and provides consistent error handling with Apollo Client's ecosystem. + +**Migration Guide:** + +Before: +```typescript +import { HttpErrorResponse } from '@angular/common/http'; + +link.request(operation).subscribe({ + error: (err) => { + if (err instanceof HttpErrorResponse) { + console.log(err.status); + console.log(err.error); + } + } +}); +``` + +After: +```typescript +import { ServerError } from '@apollo/client/errors'; + +link.request(operation).subscribe({ + error: (err) => { + if (ServerError.is(err)) { + console.log(err.statusCode); + console.log(err.bodyText); + console.log(err.response.headers); + } + } +}); +``` + +**Properties Changed:** +- `err.status` → `err.statusCode` +- `err.error` → `err.bodyText` (always string, JSON stringified for objects) +- `err.headers` (Angular HttpHeaders) → `err.response.headers` (native Headers) +- Access response via `err.response` which includes: `status`, `statusText`, `ok`, `url`, `type`, `redirected` + +**Note:** This only affects HTTP-level errors (status >= 300). Network errors and other error types remain unchanged. GraphQL errors in the response body are still processed normally through Apollo Client's error handling. + +Fixes #2394 diff --git a/packages/apollo-angular/http/src/http-batch-link.ts b/packages/apollo-angular/http/src/http-batch-link.ts index 1400399a6..1b929dd7a 100644 --- a/packages/apollo-angular/http/src/http-batch-link.ts +++ b/packages/apollo-angular/http/src/http-batch-link.ts @@ -1,9 +1,10 @@ import { print } from 'graphql'; import { Observable } from 'rxjs'; -import { HttpClient, HttpContext, HttpHeaders } from '@angular/common/http'; +import { HttpClient, HttpContext, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { ApolloLink } from '@apollo/client'; import { BatchLink } from '@apollo/client/link/batch'; +import { ServerError } from '@apollo/client/errors'; import type { HttpLink } from './http-link'; import { Body, Context, OperationPrinter, Request } from './types'; import { @@ -14,6 +15,43 @@ import { prioritize, } from './utils'; +function convertHttpErrorToApolloError(err: HttpErrorResponse): Error { + // Create a Response-like object that satisfies Apollo Client's expectations + const mockResponse = { + status: err.status, + statusText: err.statusText, + ok: err.ok, + url: err.url || '', + headers: new Headers(), + type: 'error' as ResponseType, + redirected: false, + } as Response; + + // Convert Angular's HttpHeaders to native Headers + err.headers.keys().forEach(key => { + const values = err.headers.getAll(key); + if (values) { + values.forEach(value => mockResponse.headers.append(key, value)); + } + }); + + // Get the body text + const bodyText = typeof err.error === 'string' + ? err.error + : JSON.stringify(err.error || {}); + + // Return ServerError for non-2xx status codes (following Apollo Client's logic) + if (err.status >= 300) { + return new ServerError( + `Response not successful: Received status code ${err.status}`, + { response: mockResponse, bodyText } + ); + } + + // For other HttpErrorResponse cases, return a generic error + return new Error(err.message); +} + export declare namespace HttpBatchLink { export type Options = { batchMax?: number; @@ -89,7 +127,10 @@ export class HttpBatchLinkHandler extends ApolloLink { throw new Error('File upload is not available when combined with Batching'); }).subscribe({ next: result => observer.next(result.body), - error: err => observer.error(err), + error: err => { + if (err instanceof HttpErrorResponse) observer.error(convertHttpErrorToApolloError(err)); + else observer.error(err); + }, complete: () => observer.complete(), }); diff --git a/packages/apollo-angular/http/src/http-link.ts b/packages/apollo-angular/http/src/http-link.ts index e4bf84a2b..0d4d48ffb 100644 --- a/packages/apollo-angular/http/src/http-link.ts +++ b/packages/apollo-angular/http/src/http-link.ts @@ -1,8 +1,9 @@ import { print } from 'graphql'; import { Observable } from 'rxjs'; -import { HttpClient, HttpContext } from '@angular/common/http'; +import { HttpClient, HttpContext, HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { ApolloLink } from '@apollo/client'; +import { ServerError } from '@apollo/client/errors'; import { pick } from './http-batch-link'; import { Body, @@ -15,6 +16,43 @@ import { } from './types'; import { createHeadersWithClientAwareness, fetch, mergeHeaders, mergeHttpContext } from './utils'; +function convertHttpErrorToApolloError(err: HttpErrorResponse): Error { + // Create a Response-like object that satisfies Apollo Client's expectations + const mockResponse = { + status: err.status, + statusText: err.statusText, + ok: err.ok, + url: err.url || '', + headers: new Headers(), + type: 'error' as ResponseType, + redirected: false, + } as Response; + + // Convert Angular's HttpHeaders to native Headers + err.headers.keys().forEach(key => { + const values = err.headers.getAll(key); + if (values) { + values.forEach(value => mockResponse.headers.append(key, value)); + } + }); + + // Get the body text + const bodyText = typeof err.error === 'string' + ? err.error + : JSON.stringify(err.error || {}); + + // Return ServerError for non-2xx status codes (following Apollo Client's logic) + if (err.status >= 300) { + return new ServerError( + `Response not successful: Received status code ${err.status}`, + { response: mockResponse, bodyText } + ); + } + + // For other HttpErrorResponse cases, return a generic error + return new Error(err.message); +} + export declare namespace HttpLink { export interface Options extends FetchOptions, HttpRequestOptions { operationPrinter?: OperationPrinter; @@ -94,7 +132,10 @@ export class HttpLinkHandler extends ApolloLink { operation.setContext({ response }); observer.next(response.body); }, - error: err => observer.error(err), + error: err => { + if (err instanceof HttpErrorResponse) observer.error(convertHttpErrorToApolloError(err)); + else observer.error(err); + }, complete: () => observer.complete(), }); diff --git a/packages/apollo-angular/http/tests/http-batch-link.spec.ts b/packages/apollo-angular/http/tests/http-batch-link.spec.ts index 5cdd3af93..e64f99d89 100644 --- a/packages/apollo-angular/http/tests/http-batch-link.spec.ts +++ b/packages/apollo-angular/http/tests/http-batch-link.spec.ts @@ -9,6 +9,7 @@ import { import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { ApolloLink, gql } from '@apollo/client'; +import { ServerError } from '@apollo/client/errors'; import { getOperationName } from '@apollo/client/utilities/internal'; import { HttpBatchLink } from '../src/http-batch-link'; import { executeWithDefaultContext as execute } from './utils'; @@ -818,4 +819,382 @@ describe('HttpBatchLink', () => { }); }, 50); })); + + describe('HTTP Error Handling', () => { + test('should convert HTTP 400 to ServerError', () => + new Promise(done => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const errorBody = { + errors: [{ message: 'Validation error', extensions: { code: 'GRAPHQL_VALIDATION_FAILED' } }], + }; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(ServerError.is(err)).toBe(true); + expect(err.statusCode).toBe(400); + expect(err.message).toBe('Response not successful: Received status code 400'); + expect(err.bodyText).toBe(JSON.stringify([errorBody])); + expect(err.response.ok).toBe(false); + done(); + }, + }); + + setTimeout(() => { + httpBackend.expectOne('graphql').flush([errorBody], { status: 400, statusText: 'Bad Request' }); + }, 50); + })); + + test('should convert HTTP 500 to ServerError', () => + new Promise(done => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const errorBody = 'Internal server error'; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(ServerError.is(err)).toBe(true); + expect(err.statusCode).toBe(500); + expect(err.message).toBe('Response not successful: Received status code 500'); + expect(err.bodyText).toBe(errorBody); + expect(err.response.ok).toBe(false); + done(); + }, + }); + + setTimeout(() => { + httpBackend.expectOne('graphql').flush(errorBody, { status: 500, statusText: 'Internal Server Error' }); + }, 50); + })); + + test('should include all response properties in ServerError', () => + new Promise(done => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const errorBody = [{ errors: [{ message: 'Not found' }] }]; + const customHeaders = new HttpHeaders({ 'X-Custom-Header': 'test-value', 'X-Request-ID': '12345' }); + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.response).toBeDefined(); + expect(err.response.status).toBe(404); + expect(err.response.statusText).toBe('Not Found'); + expect(err.response.ok).toBe(false); + expect(err.response.url).toBe('graphql'); + expect(err.response.type).toBe('error'); + expect(err.response.redirected).toBe(false); + + // Verify headers were converted from Angular HttpHeaders to native Headers + expect(err.response.headers).toBeDefined(); + expect(err.response.headers.get('x-custom-header')).toBe('test-value'); + expect(err.response.headers.get('x-request-id')).toBe('12345'); + done(); + }, + }); + + setTimeout(() => { + httpBackend.expectOne('graphql').flush(errorBody, { + status: 404, + statusText: 'Not Found', + headers: customHeaders + }); + }, 50); + })); + + test('should work in link chain for error handling', () => + new Promise(done => { + // Verify ServerError propagates through link chain + const passThroughLink = new ApolloLink((operation, forward) => { + return forward(operation); + }); + + const link = httpLink.create({ uri: 'graphql' }); + const combinedLink = ApolloLink.from([passThroughLink, link]); + + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + + execute(combinedLink, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + // Verify ServerError works through link chain + expect(err instanceof ServerError).toBe(true); + expect(ServerError.is(err)).toBe(true); + expect(err.statusCode).toBe(400); + expect(err.bodyText).toBeDefined(); + done(); + }, + }); + + setTimeout(() => { + httpBackend.expectOne('graphql').flush( + [{ errors: [{ message: 'Bad request' }] }], + { status: 400, statusText: 'Bad Request' } + ); + }, 50); + })); + + + test('should extract body text from string error', () => + new Promise(done => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const errorBody = 'Plain string error message'; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.bodyText).toBe(errorBody); + done(); + }, + }); + + setTimeout(() => { + httpBackend.expectOne('graphql').flush(errorBody, { status: 500, statusText: 'Server Error' }); + }, 50); + })); + + test('should extract body text from object error', () => + new Promise(done => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const errorBody = [{ errors: [{ message: 'GraphQL error', path: ['field'] }] }]; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.bodyText).toBe(JSON.stringify(errorBody)); + done(); + }, + }); + + setTimeout(() => { + httpBackend.expectOne('graphql').flush(errorBody, { status: 400, statusText: 'Bad Request' }); + }, 50); + })); + + test('should extract body text from null error', () => + new Promise(done => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.bodyText).toBe('{}'); + done(); + }, + }); + + setTimeout(() => { + httpBackend.expectOne('graphql').flush(null, { status: 500, statusText: 'Server Error' }); + }, 50); + })); + + test('should not create ServerError for status 299', () => + new Promise(done => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const data = { field: 'value' }; + + execute(link, op).subscribe({ + next: result => { + expect(result).toEqual({ data }); + done(); + }, + error: () => { + throw new Error('Should not error'); + }, + }); + + setTimeout(() => { + httpBackend.expectOne('graphql').flush([{ data }], { status: 299, statusText: 'Custom Success' }); + }, 50); + })); + + test('should create ServerError for status 300', () => + new Promise(done => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed for status 300'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.statusCode).toBe(300); + expect(err.message).toBe('Response not successful: Received status code 300'); + done(); + }, + }); + + setTimeout(() => { + httpBackend.expectOne('graphql').flush( + [{ errors: [{ message: 'Error' }] }], + { status: 300, statusText: 'Error' } + ); + }, 50); + })); + + test('should create ServerError for status 404', () => + new Promise(done => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed for status 404'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.statusCode).toBe(404); + expect(err.message).toBe('Response not successful: Received status code 404'); + done(); + }, + }); + + setTimeout(() => { + httpBackend.expectOne('graphql').flush( + [{ errors: [{ message: 'Error' }] }], + { status: 404, statusText: 'Not Found' } + ); + }, 50); + })); + + test('should create ServerError for status 503', () => + new Promise(done => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed for status 503'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.statusCode).toBe(503); + expect(err.message).toBe('Response not successful: Received status code 503'); + done(); + }, + }); + + setTimeout(() => { + httpBackend.expectOne('graphql').flush( + [{ errors: [{ message: 'Error' }] }], + { status: 503, statusText: 'Service Unavailable' } + ); + }, 50); + })); + }); }); diff --git a/packages/apollo-angular/http/tests/http-link.spec.ts b/packages/apollo-angular/http/tests/http-link.spec.ts index aa778ab10..5b3449a97 100644 --- a/packages/apollo-angular/http/tests/http-link.spec.ts +++ b/packages/apollo-angular/http/tests/http-link.spec.ts @@ -11,6 +11,7 @@ import { import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { ApolloLink, gql, InMemoryCache } from '@apollo/client'; +import { ServerError } from '@apollo/client/errors'; import { Apollo } from '../../src'; import { HttpLink } from '../src/http-link'; import { executeWithDefaultContext as execute } from './utils'; @@ -791,4 +792,337 @@ describe('HttpLink', () => { httpBackend.expectOne('graphql').flush({ data: {} }); })); + + describe('HTTP Error Handling', () => { + test('should convert HTTP 400 to ServerError', () => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const errorBody = { + errors: [{ message: 'Validation error', extensions: { code: 'GRAPHQL_VALIDATION_FAILED' } }], + }; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(ServerError.is(err)).toBe(true); + expect(err.statusCode).toBe(400); + expect(err.message).toBe('Response not successful: Received status code 400'); + expect(err.bodyText).toBe(JSON.stringify(errorBody)); + expect(err.response.ok).toBe(false); + }, + }); + + httpBackend.expectOne('graphql').flush(errorBody, { status: 400, statusText: 'Bad Request' }); + }); + + test('should convert HTTP 500 to ServerError', () => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const errorBody = 'Internal server error'; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(ServerError.is(err)).toBe(true); + expect(err.statusCode).toBe(500); + expect(err.message).toBe('Response not successful: Received status code 500'); + expect(err.bodyText).toBe(errorBody); + expect(err.response.ok).toBe(false); + }, + }); + + httpBackend.expectOne('graphql').flush(errorBody, { status: 500, statusText: 'Internal Server Error' }); + }); + + test('should include all response properties in ServerError', () => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const errorBody = { errors: [{ message: 'Not found' }] }; + const customHeaders = new HttpHeaders({ 'X-Custom-Header': 'test-value', 'X-Request-ID': '12345' }); + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.response).toBeDefined(); + expect(err.response.status).toBe(404); + expect(err.response.statusText).toBe('Not Found'); + expect(err.response.ok).toBe(false); + expect(err.response.url).toBe('graphql'); + expect(err.response.type).toBe('error'); + expect(err.response.redirected).toBe(false); + + // Verify headers were converted from Angular HttpHeaders to native Headers + expect(err.response.headers).toBeDefined(); + expect(err.response.headers.get('x-custom-header')).toBe('test-value'); + expect(err.response.headers.get('x-request-id')).toBe('12345'); + }, + }); + + httpBackend.expectOne('graphql').flush(errorBody, { + status: 404, + statusText: 'Not Found', + headers: customHeaders + }); + }); + + test('should work in link chain for error handling', () => { + // Verify ServerError propagates through link chain + const passThroughLink = new ApolloLink((operation, forward) => { + return forward(operation); + }); + + const link = httpLink.create({ uri: 'graphql' }); + const combinedLink = ApolloLink.from([passThroughLink, link]); + + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + + execute(combinedLink, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + // Verify ServerError works through link chain + expect(err instanceof ServerError).toBe(true); + expect(ServerError.is(err)).toBe(true); + expect(err.statusCode).toBe(400); + expect(err.bodyText).toBeDefined(); + }, + }); + + httpBackend.expectOne('graphql').flush( + { errors: [{ message: 'Bad request' }] }, + { status: 400, statusText: 'Bad Request' } + ); + }); + + test('should extract body text from string error', () => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const errorBody = 'Plain string error message'; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.bodyText).toBe(errorBody); + }, + }); + + httpBackend.expectOne('graphql').flush(errorBody, { status: 500, statusText: 'Server Error' }); + }); + + test('should extract body text from object error', () => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const errorBody = { errors: [{ message: 'GraphQL error', path: ['field'] }] }; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.bodyText).toBe(JSON.stringify(errorBody)); + }, + }); + + httpBackend.expectOne('graphql').flush(errorBody, { status: 400, statusText: 'Bad Request' }); + }); + + test('should extract body text from null error', () => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.bodyText).toBe('{}'); + }, + }); + + httpBackend.expectOne('graphql').flush(null, { status: 500, statusText: 'Server Error' }); + }); + + test('should not create ServerError for status 299', () => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + const data = { field: 'value' }; + + execute(link, op).subscribe({ + next: result => { + expect(result).toEqual({ data }); + }, + error: () => { + throw new Error('Should not error'); + }, + }); + + httpBackend.expectOne('graphql').flush({ data }, { status: 299, statusText: 'Custom Success' }); + }); + + test('should create ServerError for status 300', () => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed for status 300'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.statusCode).toBe(300); + expect(err.message).toBe('Response not successful: Received status code 300'); + }, + }); + + httpBackend.expectOne('graphql').flush( + { errors: [{ message: 'Error' }] }, + { status: 300, statusText: 'Error' } + ); + }); + + test('should create ServerError for status 404', () => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed for status 404'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.statusCode).toBe(404); + expect(err.message).toBe('Response not successful: Received status code 404'); + }, + }); + + httpBackend.expectOne('graphql').flush( + { errors: [{ message: 'Error' }] }, + { status: 404, statusText: 'Not Found' } + ); + }); + + test('should create ServerError for status 503', () => { + const link = httpLink.create({ uri: 'graphql' }); + const op = { + query: gql` + query test { + field + } + `, + operationName: 'test', + variables: {}, + }; + + execute(link, op).subscribe({ + next: () => { + throw new Error('Should not succeed for status 503'); + }, + error: err => { + expect(err instanceof ServerError).toBe(true); + expect(err.statusCode).toBe(503); + expect(err.message).toBe('Response not successful: Received status code 503'); + }, + }); + + httpBackend.expectOne('graphql').flush( + { errors: [{ message: 'Error' }] }, + { status: 503, statusText: 'Service Unavailable' } + ); + }); + }); });