Skip to content

Commit

Permalink
cancel execution despite pending resolvers
Browse files Browse the repository at this point in the history
  • Loading branch information
yaacovCR committed Oct 29, 2024
1 parent c16d429 commit 99edfea
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 53 deletions.
175 changes: 149 additions & 26 deletions src/execution/__tests__/abort-signal-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import { parse } from '../../language/parser.js';

import { buildSchema } from '../../utilities/buildASTSchema.js';

import { execute, experimentalExecuteIncrementally } from '../execute.js';
import {
execute,
experimentalExecuteIncrementally,
subscribe,
} from '../execute.js';
import type {
InitialIncrementalExecutionResult,
SubsequentIncrementalExecutionResult,
Expand Down Expand Up @@ -52,12 +56,17 @@ const schema = buildSchema(`
type Query {
todo: Todo
nonNullableTodo: Todo!
}
type Mutation {
foo: String
bar: String
}
type Subscription {
foo: String
}
`);

describe('Execute: Cancellation', () => {
Expand Down Expand Up @@ -300,6 +309,97 @@ describe('Execute: Cancellation', () => {
});
});

it('should stop the execution when aborted despite a hanging resolver', async () => {
const abortController = new AbortController();
const document = parse(`
query {
todo {
id
author {
id
}
}
}
`);

const resultPromise = execute({
document,
schema,
abortSignal: abortController.signal,
rootValue: {
todo: () =>
new Promise(() => {
/* will never resolve */
}),
},
});

abortController.abort();

const result = await resultPromise;

expect(result.errors?.[0].originalError?.name).to.equal('AbortError');

expectJSON(result).toDeepEqual({
data: {
todo: null,
},
errors: [
{
message: 'This operation was aborted',
path: ['todo'],
locations: [{ line: 3, column: 9 }],
},
],
});
});

it('should stop the execution when aborted with proper null bubbling', async () => {
const abortController = new AbortController();
const document = parse(`
query {
nonNullableTodo {
id
author {
id
}
}
}
`);

const resultPromise = execute({
document,
schema,
abortSignal: abortController.signal,
rootValue: {
nonNullableTodo: async () =>
Promise.resolve({
id: '1',
text: 'Hello, World!',
/* c8 ignore next */
author: () => expect.fail('Should not be called'),
}),
},
});

abortController.abort();

const result = await resultPromise;

expect(result.errors?.[0].originalError?.name).to.equal('AbortError');

expectJSON(result).toDeepEqual({
data: null,
errors: [
{
message: 'This operation was aborted',
path: ['nonNullableTodo'],
locations: [{ line: 3, column: 9 }],
},
],
});
});

it('should stop deferred execution when aborted', async () => {
const abortController = new AbortController();
const document = parse(`
Expand Down Expand Up @@ -353,14 +453,12 @@ describe('Execute: Cancellation', () => {
const abortController = new AbortController();
const document = parse(`
query {
todo {
id
... on Todo @defer {
... on Query @defer {
todo {
id
text
author {
... on Author @defer {
id
}
id
}
}
}
Expand All @@ -382,41 +480,27 @@ describe('Execute: Cancellation', () => {
abortController.signal,
);

await resolveOnNextTick();
await resolveOnNextTick();
await resolveOnNextTick();

abortController.abort();

const result = await resultPromise;

expectJSON(result).toDeepEqual([
{
data: {
todo: {
id: '1',
},
},
pending: [{ id: '0', path: ['todo'] }],
data: {},
pending: [{ id: '0', path: [] }],
hasNext: true,
},
{
incremental: [
{
data: {
text: 'hello world',
author: null,
todo: null,
},
errors: [
{
locations: [
{
column: 13,
line: 7,
},
],
message: 'This operation was aborted',
path: ['todo', 'author'],
path: ['todo'],
locations: [{ line: 4, column: 11 }],
},
],
id: '0',
Expand Down Expand Up @@ -448,6 +532,10 @@ describe('Execute: Cancellation', () => {
},
});

await resolveOnNextTick();
await resolveOnNextTick();
await resolveOnNextTick();

abortController.abort();

const result = await resultPromise;
Expand Down Expand Up @@ -498,4 +586,39 @@ describe('Execute: Cancellation', () => {
],
});
});

it('should stop the execution when aborted during subscription', async () => {
const abortController = new AbortController();
const document = parse(`
subscription {
foo
}
`);

const resultPromise = subscribe({
document,
schema,
abortSignal: abortController.signal,
rootValue: {
foo: async () =>
new Promise(() => {
/* will never resolve */
}),
},
});

abortController.abort();

const result = await resultPromise;

expectJSON(result).toDeepEqual({
errors: [
{
message: 'This operation was aborted',
path: ['foo'],
locations: [{ line: 3, column: 9 }],
},
],
});
});
});
5 changes: 5 additions & 0 deletions src/execution/__tests__/stream-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1661,6 +1661,11 @@ describe('Execute: stream directive', () => {
items: [{ name: 'Luke' }],
id: '1',
},
],
hasNext: true,
},
{
incremental: [
{
data: { scalarField: null },
id: '0',
Expand Down
Loading

0 comments on commit 99edfea

Please sign in to comment.