import { expect } from 'chai';
import { describe, it } from 'mocha';
import { expectJSON } from '../../__testUtils__/expectJSON.js';
import type { PromiseOrValue } from '../../jsutils/PromiseOrValue.js';
import { parse } from '../../language/parser.js';
import { GraphQLNonNull, GraphQLObjectType } from '../../type/definition.js';
import { GraphQLString } from '../../type/scalars.js';
import { GraphQLSchema } from '../../type/schema.js';
import { buildSchema } from '../../utilities/buildASTSchema.js';
import type { ExecutionResult } from '../execute.js';
import { execute, executeSync } from '../execute.js';
const syncError = new Error('sync');
const syncNonNullError = new Error('syncNonNull');
const promiseError = new Error('promise');
const promiseNonNullError = new Error('promiseNonNull');
const throwingData = {
sync() {
throw syncError;
},
syncNonNull() {
throw syncNonNullError;
},
promise() {
return new Promise(() => {
throw promiseError;
});
},
promiseNonNull() {
return new Promise(() => {
throw promiseNonNullError;
});
},
syncNest() {
return throwingData;
},
syncNonNullNest() {
return throwingData;
},
promiseNest() {
return new Promise((resolve) => {
resolve(throwingData);
});
},
promiseNonNullNest() {
return new Promise((resolve) => {
resolve(throwingData);
});
},
};
const nullingData = {
sync() {
return null;
},
syncNonNull() {
return null;
},
promise() {
return new Promise((resolve) => {
resolve(null);
});
},
promiseNonNull() {
return new Promise((resolve) => {
resolve(null);
});
},
syncNest() {
return nullingData;
},
syncNonNullNest() {
return nullingData;
},
promiseNest() {
return new Promise((resolve) => {
resolve(nullingData);
});
},
promiseNonNullNest() {
return new Promise((resolve) => {
resolve(nullingData);
});
},
};
const schema = buildSchema(`
type DataType {
sync: String
syncNonNull: String!
promise: String
promiseNonNull: String!
syncNest: DataType
syncNonNullNest: DataType!
promiseNest: DataType
promiseNonNullNest: DataType!
}
schema {
query: DataType
}
`);
function executeQuery(
query: string,
rootValue: unknown,
): PromiseOrValue<ExecutionResult> {
return execute({ schema, document: parse(query), rootValue });
}
function patch(str: string): string {
return str
.replace(/\bsync\b/g, 'promise')
.replace(/\bsyncNonNull\b/g, 'promiseNonNull');
}
function patchData(data: ExecutionResult): ExecutionResult {
return JSON.parse(patch(JSON.stringify(data)));
}
async function executeSyncAndAsync(query: string, rootValue: unknown) {
const syncResult = executeSync({ schema, document: parse(query), rootValue });
const asyncResult = await execute({
schema,
document: parse(patch(query)),
rootValue,
});
expectJSON(asyncResult).toDeepEqual(patchData(syncResult));
return syncResult;
}
describe('Execute: handles non-nullable types', () => {
describe('nulls a nullable field', () => {
const query = `
{
sync
}
`;
it('that returns null', async () => {
const result = await executeSyncAndAsync(query, nullingData);
expect(result).to.deep.equal({
data: { sync: null },
});
});
it('that throws', async () => {
const result = await executeSyncAndAsync(query, throwingData);
expectJSON(result).toDeepEqual({
data: { sync: null },
errors: [
{
message: syncError.message,
path: ['sync'],
locations: [{ line: 3, column: 9 }],
},
],
});
});
});
describe('nulls a returned object that contains a non-nullable field', () => {
const query = `
{
syncNest {
syncNonNull,
}
}
`;
it('that returns null', async () => {
const result = await executeSyncAndAsync(query, nullingData);
expectJSON(result).toDeepEqual({
data: { syncNest: null },
errors: [
{
message:
'Cannot return null for non-nullable field DataType.syncNonNull.',
path: ['syncNest', 'syncNonNull'],
locations: [{ line: 4, column: 11 }],
},
],
});
});
it('that throws', async () => {
const result = await executeSyncAndAsync(query, throwingData);
expectJSON(result).toDeepEqual({
data: { syncNest: null },
errors: [
{
message: syncNonNullError.message,
path: ['syncNest', 'syncNonNull'],
locations: [{ line: 4, column: 11 }],
},
],
});
});
});
describe('nulls a complex tree of nullable fields, each', () => {
const query = `
{
syncNest {
sync
promise
syncNest { sync promise }
promiseNest { sync promise }
}
promiseNest {
sync
promise
syncNest { sync promise }
promiseNest { sync promise }
}
}
`;
const data = {
syncNest: {
sync: null,
promise: null,
syncNest: { sync: null, promise: null },
promiseNest: { sync: null, promise: null },
},
promiseNest: {
sync: null,
promise: null,
syncNest: { sync: null, promise: null },
promiseNest: { sync: null, promise: null },
},
};
it('that returns null', async () => {
const result = await executeQuery(query, nullingData);
expect(result).to.deep.equal({ data });
});
it('that throws', async () => {
const result = await executeQuery(query, throwingData);
expectJSON(result).toDeepEqual({
data,
errors: [
{
message: syncError.message,
path: ['syncNest', 'sync'],
locations: [{ line: 4, column: 11 }],
},
{
message: syncError.message,
path: ['syncNest', 'syncNest', 'sync'],
locations: [{ line: 6, column: 22 }],
},
{
message: promiseError.message,
path: ['syncNest', 'promise'],
locations: [{ line: 5, column: 11 }],
},
{
message: promiseError.message,
path: ['syncNest', 'syncNest', 'promise'],
locations: [{ line: 6, column: 27 }],
},
{
message: syncError.message,
path: ['syncNest', 'promiseNest', 'sync'],
locations: [{ line: 7, column: 25 }],
},
{
message: syncError.message,
path: ['promiseNest', 'sync'],
locations: [{ line: 10, column: 11 }],
},
{
message: syncError.message,
path: ['promiseNest', 'syncNest', 'sync'],
locations: [{ line: 12, column: 22 }],
},
{
message: promiseError.message,
path: ['syncNest', 'promiseNest', 'promise'],
locations: [{ line: 7, column: 30 }],
},
{
message: promiseError.message,
path: ['promiseNest', 'promise'],
locations: [{ line: 11, column: 11 }],
},
{
message: promiseError.message,
path: ['promiseNest', 'syncNest', 'promise'],
locations: [{ line: 12, column: 27 }],
},
{
message: syncError.message,
path: ['promiseNest', 'promiseNest', 'sync'],
locations: [{ line: 13, column: 25 }],
},
{
message: promiseError.message,
path: ['promiseNest', 'promiseNest', 'promise'],
locations: [{ line: 13, column: 30 }],
},
],
});
});
});
describe('nulls the first nullable object after a field in a long chain of non-null fields', () => {
const query = `
{
syncNest {
syncNonNullNest {
promiseNonNullNest {
syncNonNullNest {
promiseNonNullNest {
syncNonNull
}
}
}
}
}
promiseNest {
syncNonNullNest {
promiseNonNullNest {
syncNonNullNest {
promiseNonNullNest {
syncNonNull
}
}
}
}
}
anotherNest: syncNest {
syncNonNullNest {
promiseNonNullNest {
syncNonNullNest {
promiseNonNullNest {
promiseNonNull
}
}
}
}
}
anotherPromiseNest: promiseNest {
syncNonNullNest {
promiseNonNullNest {
syncNonNullNest {
promiseNonNullNest {
promiseNonNull
}
}
}
}
}
}
`;
const data = {
syncNest: null,
promiseNest: null,
anotherNest: null,
anotherPromiseNest: null,
};
it('that returns null', async () => {
const result = await executeQuery(query, nullingData);
expectJSON(result).toDeepEqual({
data,
errors: [
{
message:
'Cannot return null for non-nullable field DataType.syncNonNull.',
path: [
'syncNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNull',
],
locations: [{ line: 8, column: 19 }],
},
{
message:
'Cannot return null for non-nullable field DataType.syncNonNull.',
path: [
'promiseNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNull',
],
locations: [{ line: 19, column: 19 }],
},
{
message:
'Cannot return null for non-nullable field DataType.promiseNonNull.',
path: [
'anotherNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNullNest',
'promiseNonNullNest',
'promiseNonNull',
],
locations: [{ line: 30, column: 19 }],
},
{
message:
'Cannot return null for non-nullable field DataType.promiseNonNull.',
path: [
'anotherPromiseNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNullNest',
'promiseNonNullNest',
'promiseNonNull',
],
locations: [{ line: 41, column: 19 }],
},
],
});
});
it('that throws', async () => {
const result = await executeQuery(query, throwingData);
expectJSON(result).toDeepEqual({
data,
errors: [
{
message: syncNonNullError.message,
path: [
'syncNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNull',
],
locations: [{ line: 8, column: 19 }],
},
{
message: syncNonNullError.message,
path: [
'promiseNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNull',
],
locations: [{ line: 19, column: 19 }],
},
{
message: promiseNonNullError.message,
path: [
'anotherNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNullNest',
'promiseNonNullNest',
'promiseNonNull',
],
locations: [{ line: 30, column: 19 }],
},
{
message: promiseNonNullError.message,
path: [
'anotherPromiseNest',
'syncNonNullNest',
'promiseNonNullNest',
'syncNonNullNest',
'promiseNonNullNest',
'promiseNonNull',
],
locations: [{ line: 41, column: 19 }],
},
],
});
});
});
describe('nulls the top level if non-nullable field', () => {
const query = `
{
syncNonNull
}
`;
it('that returns null', async () => {
const result = await executeSyncAndAsync(query, nullingData);
expectJSON(result).toDeepEqual({
data: null,
errors: [
{
message:
'Cannot return null for non-nullable field DataType.syncNonNull.',
path: ['syncNonNull'],
locations: [{ line: 3, column: 9 }],
},
],
});
});
it('that throws', async () => {
const result = await executeSyncAndAsync(query, throwingData);
expectJSON(result).toDeepEqual({
data: null,
errors: [
{
message: syncNonNullError.message,
path: ['syncNonNull'],
locations: [{ line: 3, column: 9 }],
},
],
});
});
});
describe('Handles non-null argument', () => {
const schemaWithNonNullArg = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
withNonNullArg: {
type: GraphQLString,
args: {
cannotBeNull: {
type: new GraphQLNonNull(GraphQLString),
},
},
resolve: (_, args) => 'Passed: ' + String(args.cannotBeNull),
},
},
}),
});
it('succeeds when passed non-null literal value', () => {
const result = executeSync({
schema: schemaWithNonNullArg,
document: parse(`
query {
withNonNullArg (cannotBeNull: "literal value")
}
`),
});
expect(result).to.deep.equal({
data: {
withNonNullArg: 'Passed: literal value',
},
});
});
it('succeeds when passed non-null variable value', () => {
const result = executeSync({
schema: schemaWithNonNullArg,
document: parse(`
query ($testVar: String!) {
withNonNullArg (cannotBeNull: $testVar)
}
`),
variableValues: {
testVar: 'variable value',
},
});
expect(result).to.deep.equal({
data: {
withNonNullArg: 'Passed: variable value',
},
});
});
it('succeeds when missing variable has default value', () => {
const result = executeSync({
schema: schemaWithNonNullArg,
document: parse(`
query ($testVar: String = "default value") {
withNonNullArg (cannotBeNull: $testVar)
}
`),
variableValues: {
},
});
expect(result).to.deep.equal({
data: {
withNonNullArg: 'Passed: default value',
},
});
});
it('field error when missing non-null arg', () => {
const result = executeSync({
schema: schemaWithNonNullArg,
document: parse(`
query {
withNonNullArg
}
`),
});
expectJSON(result).toDeepEqual({
data: {
withNonNullArg: null,
},
errors: [
{
message:
'Argument "cannotBeNull" of required type "String!" was not provided.',
locations: [{ line: 3, column: 13 }],
path: ['withNonNullArg'],
},
],
});
});
it('field error when non-null arg provided null', () => {
const result = executeSync({
schema: schemaWithNonNullArg,
document: parse(`
query {
withNonNullArg(cannotBeNull: null)
}
`),
});
expectJSON(result).toDeepEqual({
data: {
withNonNullArg: null,
},
errors: [
{
message:
'Argument "cannotBeNull" of non-null type "String!" must not be null.',
locations: [{ line: 3, column: 42 }],
path: ['withNonNullArg'],
},
],
});
});
it('field error when non-null arg not provided variable value', () => {
const result = executeSync({
schema: schemaWithNonNullArg,
document: parse(`
query ($testVar: String) {
withNonNullArg(cannotBeNull: $testVar)
}
`),
variableValues: {
},
});
expectJSON(result).toDeepEqual({
data: {
withNonNullArg: null,
},
errors: [
{
message:
'Argument "cannotBeNull" of required type "String!" was provided the variable "$testVar" which was not provided a runtime value.',
locations: [{ line: 3, column: 42 }],
path: ['withNonNullArg'],
},
],
});
});
it('field error when non-null arg provided variable with explicit null value', () => {
const result = executeSync({
schema: schemaWithNonNullArg,
document: parse(`
query ($testVar: String = "default value") {
withNonNullArg (cannotBeNull: $testVar)
}
`),
variableValues: {
testVar: null,
},
});
expectJSON(result).toDeepEqual({
data: {
withNonNullArg: null,
},
errors: [
{
message:
'Argument "cannotBeNull" of non-null type "String!" must not be null.',
locations: [{ line: 3, column: 43 }],
path: ['withNonNullArg'],
},
],
});
});
});
});