From 8f84774296425752ccfbdd68f34c75356af3104c Mon Sep 17 00:00:00 2001 From: "igor.luckenkov" Date: Wed, 7 Dec 2022 21:18:42 +0000 Subject: [PATCH] ignore coverage and skip abortion test if node doesn't support AbortController --- src/execution/__tests__/executor-test.ts | 138 ++++++++++++----------- src/execution/execute.ts | 11 +- src/jsutils/AbortController.ts | 5 + 3 files changed, 85 insertions(+), 69 deletions(-) diff --git a/src/execution/__tests__/executor-test.ts b/src/execution/__tests__/executor-test.ts index ec953d0db1..0ea61bea0b 100644 --- a/src/execution/__tests__/executor-test.ts +++ b/src/execution/__tests__/executor-test.ts @@ -4,7 +4,10 @@ import { describe, it } from 'mocha'; import { expectJSON } from '../../__testUtils__/expectJSON.js'; import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick.js'; -import { AbortController } from '../../jsutils/AbortController.js'; +import { + AbortController, + hasAbortControllerSupport, +} from '../../jsutils/AbortController.js'; import { inspect } from '../../jsutils/inspect.js'; import { Kind } from '../../language/kinds.js'; @@ -1315,59 +1318,60 @@ describe('Execute: Handles basic execution tasks', () => { expect(possibleTypes).to.deep.equal([fooObject]); }); - it('stops execution and throws an error when signal is aborted', async () => { - /** - * This test has 3 resolvers nested in each other. - * Every resolve function waits 200ms before returning data. - * - * The test waits for the first resolver and half of the 2nd resolver execution time (200ms + 100ms) - * and then aborts the execution. - * - * 2nd resolver execution finishes, and we then expect to not execute the 3rd resolver - * and to get an error about aborted operation. - */ - - const WAIT_MS_BEFORE_RESOLVING = 200; - const ABORT_IN_MS_AFTER_STARTING_EXECUTION = - WAIT_MS_BEFORE_RESOLVING + WAIT_MS_BEFORE_RESOLVING / 2; - - const schema = new GraphQLSchema({ - query: new GraphQLObjectType({ - name: 'Query', - fields: { - resolvesIn500ms: { - type: new GraphQLObjectType({ - name: 'ResolvesIn500ms', - fields: { - resolvesIn400ms: { - type: new GraphQLObjectType({ - name: 'ResolvesIn400ms', - fields: { - shouldNotBeResolved: { - type: GraphQLString, - /* c8 ignore next 3 */ - resolve: () => { - throw new Error('This should not be executed!'); + /* c8 ignore start */ + if (hasAbortControllerSupport) { + it('stops execution and throws an error when signal is aborted', async () => { + /** + * This test has 3 resolvers nested in each other. + * Every resolve function waits 200ms before returning data. + * + * The test waits for the first resolver and half of the 2nd resolver execution time (200ms + 100ms) + * and then aborts the execution. + * + * 2nd resolver execution finishes, and we then expect to not execute the 3rd resolver + * and to get an error about aborted operation. + */ + + const WAIT_MS_BEFORE_RESOLVING = 200; + const ABORT_IN_MS_AFTER_STARTING_EXECUTION = + WAIT_MS_BEFORE_RESOLVING + WAIT_MS_BEFORE_RESOLVING / 2; + + const schema = new GraphQLSchema({ + query: new GraphQLObjectType({ + name: 'Query', + fields: { + resolvesIn500ms: { + type: new GraphQLObjectType({ + name: 'ResolvesIn500ms', + fields: { + resolvesIn400ms: { + type: new GraphQLObjectType({ + name: 'ResolvesIn400ms', + fields: { + shouldNotBeResolved: { + type: GraphQLString, + resolve: () => { + throw new Error('This should not be executed!'); + }, }, }, - }, - }), - resolve: () => - new Promise((resolve) => { - setTimeout(() => resolve({}), WAIT_MS_BEFORE_RESOLVING); }), + resolve: () => + new Promise((resolve) => { + setTimeout(() => resolve({}), WAIT_MS_BEFORE_RESOLVING); + }), + }, }, - }, - }), - resolve: () => - new Promise((resolve) => { - setTimeout(() => resolve({}), WAIT_MS_BEFORE_RESOLVING); }), + resolve: () => + new Promise((resolve) => { + setTimeout(() => resolve({}), WAIT_MS_BEFORE_RESOLVING); + }), + }, }, - }, - }), - }); - const document = parse(` + }), + }); + const document = parse(` query { resolvesIn500ms { resolvesIn400ms { @@ -1377,22 +1381,24 @@ describe('Execute: Handles basic execution tasks', () => { } `); - const abortController = new AbortController(); - const executionPromise = execute({ - schema, - document, - signal: abortController.signal, - }); - - setTimeout( - () => abortController.abort(), - ABORT_IN_MS_AFTER_STARTING_EXECUTION, - ); - - const result = await executionPromise; - expect(result.errors?.[0].message).to.eq('Execution aborted.'); - expect(result.data).to.eql({ - resolvesIn500ms: { resolvesIn400ms: null }, - }); - }); + const abortController = new AbortController(); + const executionPromise = execute({ + schema, + document, + signal: abortController.signal, + }); + + setTimeout( + () => abortController.abort(), + ABORT_IN_MS_AFTER_STARTING_EXECUTION, + ); + + const result = await executionPromise; + expect(result.errors?.[0].message).to.eq('Execution aborted.'); + expect(result.data).to.eql({ + resolvesIn500ms: { resolvesIn400ms: null }, + }); + }); + } + /* c8 ignore stop */ }); diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 9c8340cff9..2e67be875f 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -348,6 +348,7 @@ export function experimentalExecuteIncrementally( return executeImpl(exeContext); } +/* c8 ignore start */ function subscribeToAbortSignal(exeContext: ExecutionContext): () => void { const { abortion } = exeContext; if (!abortion) { @@ -362,6 +363,7 @@ function subscribeToAbortSignal(exeContext: ExecutionContext): () => void { abortion.executionAbortController.abort(); }; } +/* c8 ignore stop */ function executeImpl( exeContext: ExecutionContext, @@ -546,6 +548,7 @@ export function buildExecutionContext( }; } +/* c8 ignore start */ function getContextAbortionEntities( passedInAbortSignal: Maybe, ): ExecutionContext['abortion'] { @@ -561,6 +564,7 @@ function getContextAbortionEntities( executionAbortSignal: executionAbortController.signal, }; } +/* c8 ignore stop */ function buildPerEventExecutionContext( exeContext: ExecutionContext, @@ -892,12 +896,11 @@ function completeValue( result: unknown, asyncPayloadRecord?: AsyncPayloadRecord, ): PromiseOrValue { - // Ignoring test coverage for abortion check since Node 14 doesn't support AbortSignal - // and this condition is never true. - /* c8 ignore next 3 */ + /* c8 ignore start */ if (exeContext.abortion?.executionAbortSignal.aborted) { throw new GraphQLError('Execution aborted.'); } + /* c8 ignore stop */ // If result is an Error, throw a located error. if (result instanceof Error) { @@ -1636,11 +1639,13 @@ export function subscribe( > { // Until we have execution cancelling support in Subscriptions, // throw an error if client provides abort signal. + /* c8 ignore start */ if (args.signal) { return { errors: [new GraphQLError('Subscriptions do not support abort signals.')], }; } + /* c8 ignore stop */ // If a valid execution context cannot be created due to incorrect arguments, // a "Response" with only errors is returned. diff --git a/src/jsutils/AbortController.ts b/src/jsutils/AbortController.ts index 40ed4e48e7..96293a3023 100644 --- a/src/jsutils/AbortController.ts +++ b/src/jsutils/AbortController.ts @@ -28,4 +28,9 @@ export const AbortController: new () => IAbortController = this._signal.aborted = true; } }; + +export const hasAbortControllerSupport = + // eslint-disable-next-line no-undef + Boolean(global.AbortController); + /* c8 ignore stop */