import { describe, it } from 'mocha';
import type { GraphQLSchema } from '../../type/schema.js';
import { buildSchema } from '../../utilities/buildASTSchema.js';
import { KnownDirectivesRule } from '../rules/KnownDirectivesRule.js';
import {
expectSDLValidationErrors,
expectValidationErrorsWithSchema,
} from './harness.js';
function expectErrors(queryStr: string) {
return expectValidationErrorsWithSchema(
schemaWithDirectives,
KnownDirectivesRule,
queryStr,
);
}
function expectValid(queryStr: string) {
expectErrors(queryStr).toDeepEqual([]);
}
function expectSDLErrors(sdlStr: string, schema?: GraphQLSchema) {
return expectSDLValidationErrors(schema, KnownDirectivesRule, sdlStr);
}
function expectValidSDL(sdlStr: string, schema?: GraphQLSchema) {
expectSDLErrors(sdlStr, schema).toDeepEqual([]);
}
const schemaWithDirectives = buildSchema(`
type Query {
dummy: String
}
directive @onQuery on QUERY
directive @onMutation on MUTATION
directive @onSubscription on SUBSCRIPTION
directive @onField on FIELD
directive @onFragmentDefinition on FRAGMENT_DEFINITION
directive @onFragmentSpread on FRAGMENT_SPREAD
directive @onInlineFragment on INLINE_FRAGMENT
directive @onVariableDefinition on VARIABLE_DEFINITION
`);
const schemaWithSDLDirectives = buildSchema(`
directive @onSchema on SCHEMA
directive @onScalar on SCALAR
directive @onObject on OBJECT
directive @onFieldDefinition on FIELD_DEFINITION
directive @onArgumentDefinition on ARGUMENT_DEFINITION
directive @onInterface on INTERFACE
directive @onUnion on UNION
directive @onEnum on ENUM
directive @onEnumValue on ENUM_VALUE
directive @onInputObject on INPUT_OBJECT
directive @onInputFieldDefinition on INPUT_FIELD_DEFINITION
`);
describe('Validate: Known directives', () => {
it('with no directives', () => {
expectValid(`
query Foo {
name
...Frag
}
fragment Frag on Dog {
name
}
`);
});
it('with standard directives', () => {
expectValid(`
{
human @skip(if: false) {
name
pets {
... on Dog @include(if: true) {
name
}
}
}
}
`);
});
it('with unknown directive', () => {
expectErrors(`
{
human @unknown(directive: "value") {
name
}
}
`).toDeepEqual([
{
message: 'Unknown directive "@unknown".',
locations: [{ line: 3, column: 15 }],
},
]);
});
it('with many unknown directives', () => {
expectErrors(`
{
__typename @unknown
human @unknown {
name
pets @unknown {
name
}
}
}
`).toDeepEqual([
{
message: 'Unknown directive "@unknown".',
locations: [{ line: 3, column: 20 }],
},
{
message: 'Unknown directive "@unknown".',
locations: [{ line: 4, column: 15 }],
},
{
message: 'Unknown directive "@unknown".',
locations: [{ line: 6, column: 16 }],
},
]);
});
it('with well placed directives', () => {
expectValid(`
query ($var: Boolean @onVariableDefinition) @onQuery {
human @onField {
...Frag @onFragmentSpread
... @onInlineFragment {
name @onField
}
}
}
mutation @onMutation {
someField @onField
}
subscription @onSubscription {
someField @onField
}
fragment Frag on Human @onFragmentDefinition {
name @onField
}
`);
});
it('with misplaced directives', () => {
expectErrors(`
query ($var: Boolean @onQuery) @onMutation {
human @onQuery {
...Frag @onQuery
... @onQuery {
name @onQuery
}
}
}
mutation @onQuery {
someField @onQuery
}
subscription @onQuery {
someField @onQuery
}
fragment Frag on Human @onQuery {
name @onQuery
}
`).toDeepEqual([
{
message: 'Directive "@onQuery" may not be used on VARIABLE_DEFINITION.',
locations: [{ line: 2, column: 28 }],
},
{
message: 'Directive "@onMutation" may not be used on QUERY.',
locations: [{ line: 2, column: 38 }],
},
{
message: 'Directive "@onQuery" may not be used on FIELD.',
locations: [{ line: 3, column: 15 }],
},
{
message: 'Directive "@onQuery" may not be used on FRAGMENT_SPREAD.',
locations: [{ line: 4, column: 19 }],
},
{
message: 'Directive "@onQuery" may not be used on INLINE_FRAGMENT.',
locations: [{ line: 5, column: 15 }],
},
{
message: 'Directive "@onQuery" may not be used on FIELD.',
locations: [{ line: 6, column: 18 }],
},
{
message: 'Directive "@onQuery" may not be used on MUTATION.',
locations: [{ line: 11, column: 16 }],
},
{
message: 'Directive "@onQuery" may not be used on FIELD.',
locations: [{ column: 19, line: 12 }],
},
{
message: 'Directive "@onQuery" may not be used on SUBSCRIPTION.',
locations: [{ column: 20, line: 15 }],
},
{
message: 'Directive "@onQuery" may not be used on FIELD.',
locations: [{ column: 19, line: 16 }],
},
{
message: 'Directive "@onQuery" may not be used on FRAGMENT_DEFINITION.',
locations: [{ column: 30, line: 19 }],
},
{
message: 'Directive "@onQuery" may not be used on FIELD.',
locations: [{ column: 14, line: 20 }],
},
]);
});
describe('within SDL', () => {
it('with directive defined inside SDL', () => {
expectValidSDL(`
type Query {
foo: String @test
}
directive @test on FIELD_DEFINITION
`);
});
it('with standard directive', () => {
expectValidSDL(`
type Query {
foo: String @deprecated
}
`);
});
it('with overridden standard directive', () => {
expectValidSDL(`
schema @deprecated {
query: Query
}
directive @deprecated on SCHEMA
`);
});
it('with directive defined in schema extension', () => {
const schema = buildSchema(`
type Query {
foo: String
}
`);
expectValidSDL(
`
directive @test on OBJECT
extend type Query @test
`,
schema,
);
});
it('with directive used in schema extension', () => {
const schema = buildSchema(`
directive @test on OBJECT
type Query {
foo: String
}
`);
expectValidSDL(
`
extend type Query @test
`,
schema,
);
});
it('with unknown directive in schema extension', () => {
const schema = buildSchema(`
type Query {
foo: String
}
`);
expectSDLErrors(
`
extend type Query @unknown
`,
schema,
).toDeepEqual([
{
message: 'Unknown directive "@unknown".',
locations: [{ line: 2, column: 29 }],
},
]);
});
it('with well placed directives', () => {
expectValidSDL(
`
type MyObj implements MyInterface @onObject {
myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition
}
extend type MyObj @onObject
scalar MyScalar @onScalar
extend scalar MyScalar @onScalar
interface MyInterface @onInterface {
myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition
}
extend interface MyInterface @onInterface
union MyUnion @onUnion = MyObj | Other
extend union MyUnion @onUnion
enum MyEnum @onEnum {
MY_VALUE @onEnumValue
}
extend enum MyEnum @onEnum
input MyInput @onInputObject {
myField: Int @onInputFieldDefinition
}
extend input MyInput @onInputObject
schema @onSchema {
query: MyQuery
}
extend schema @onSchema
`,
schemaWithSDLDirectives,
);
});
it('with misplaced directives', () => {
expectSDLErrors(
`
type MyObj implements MyInterface @onInterface {
myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition
}
scalar MyScalar @onEnum
interface MyInterface @onObject {
myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition
}
union MyUnion @onEnumValue = MyObj | Other
enum MyEnum @onScalar {
MY_VALUE @onUnion
}
input MyInput @onEnum {
myField: Int @onArgumentDefinition
}
schema @onObject {
query: MyQuery
}
extend schema @onObject
`,
schemaWithSDLDirectives,
).toDeepEqual([
{
message: 'Directive "@onInterface" may not be used on OBJECT.',
locations: [{ line: 2, column: 45 }],
},
{
message:
'Directive "@onInputFieldDefinition" may not be used on ARGUMENT_DEFINITION.',
locations: [{ line: 3, column: 32 }],
},
{
message:
'Directive "@onInputFieldDefinition" may not be used on FIELD_DEFINITION.',
locations: [{ line: 3, column: 65 }],
},
{
message: 'Directive "@onEnum" may not be used on SCALAR.',
locations: [{ line: 6, column: 27 }],
},
{
message: 'Directive "@onObject" may not be used on INTERFACE.',
locations: [{ line: 8, column: 33 }],
},
{
message:
'Directive "@onInputFieldDefinition" may not be used on ARGUMENT_DEFINITION.',
locations: [{ line: 9, column: 32 }],
},
{
message:
'Directive "@onInputFieldDefinition" may not be used on FIELD_DEFINITION.',
locations: [{ line: 9, column: 65 }],
},
{
message: 'Directive "@onEnumValue" may not be used on UNION.',
locations: [{ line: 12, column: 25 }],
},
{
message: 'Directive "@onScalar" may not be used on ENUM.',
locations: [{ line: 14, column: 23 }],
},
{
message: 'Directive "@onUnion" may not be used on ENUM_VALUE.',
locations: [{ line: 15, column: 22 }],
},
{
message: 'Directive "@onEnum" may not be used on INPUT_OBJECT.',
locations: [{ line: 18, column: 25 }],
},
{
message:
'Directive "@onArgumentDefinition" may not be used on INPUT_FIELD_DEFINITION.',
locations: [{ line: 19, column: 26 }],
},
{
message: 'Directive "@onObject" may not be used on SCHEMA.',
locations: [{ line: 22, column: 18 }],
},
{
message: 'Directive "@onObject" may not be used on SCHEMA.',
locations: [{ line: 26, column: 25 }],
},
]);
});
});
});