Skip to content

Commit

Permalink
WIP: Move schema validation into separate step (type constructors)
Browse files Browse the repository at this point in the history
This is the second step of moving work from type constructors to the schema validation function.
  • Loading branch information
leebyron committed Dec 13, 2017
1 parent 7e147a8 commit 290f721
Show file tree
Hide file tree
Showing 9 changed files with 1,324 additions and 1,143 deletions.
308 changes: 289 additions & 19 deletions src/type/__tests__/definition-test.js
Expand Up @@ -270,7 +270,7 @@ describe('Type System: Example', () => {
expect(schema.getTypeMap().NestedInputObject).to.equal(NestedInputObject);
});

it("includes interfaces' subtypes in the type map", () => {
it('includes interface possible types in the type map', () => {
const SomeInterface = new GraphQLInterfaceType({
name: 'SomeInterface',
fields: {
Expand Down Expand Up @@ -374,26 +374,13 @@ describe('Type System: Example', () => {
});
});

it('prohibits putting non-Object types in unions', () => {
const badUnionTypes = [
GraphQLInt,
GraphQLNonNull(GraphQLInt),
GraphQLList(GraphQLInt),
InterfaceType,
UnionType,
EnumType,
InputObjectType,
];
badUnionTypes.forEach(x => {
expect(() =>
new GraphQLUnionType({ name: 'BadUnion', types: [x] }).getTypes(),
).to.throw(
`BadUnion may only contain Object types, it cannot contain: ${x}.`,
);
});
it('prohibits nesting NonNull inside NonNull', () => {
expect(() => GraphQLNonNull(GraphQLNonNull(GraphQLInt))).to.throw(
'Expected Int! to be a GraphQL nullable type.',
);
});

it("allows a thunk for Union's types", () => {
it('allows a thunk for Union member types', () => {
const union = new GraphQLUnionType({
name: 'ThunkUnion',
types: () => [ObjectType],
Expand Down Expand Up @@ -470,6 +457,289 @@ describe('Type System: Example', () => {
});
});

describe('Field config must be object', () => {
it('accepts an Object type with a field function', () => {
const objType = new GraphQLObjectType({
name: 'SomeObject',
fields() {
return {
f: { type: GraphQLString },
};
},
});
expect(objType.getFields().f.type).to.equal(GraphQLString);
});

it('rejects an Object type field with undefined config', () => {
const objType = new GraphQLObjectType({
name: 'SomeObject',
fields: {
f: undefined,
},
});
expect(() => objType.getFields()).to.throw(
'SomeObject.f field config must be an object',
);
});

it('rejects an Object type with incorrectly typed fields', () => {
const objType = new GraphQLObjectType({
name: 'SomeObject',
fields: [{ field: GraphQLString }],
});
expect(() => objType.getFields()).to.throw(
'SomeObject fields must be an object with field names as keys or a ' +
'function which returns such an object.',
);
});

it('rejects an Object type with a field function that returns incorrect type', () => {
const objType = new GraphQLObjectType({
name: 'SomeObject',
fields() {
return [{ field: GraphQLString }];
},
});
expect(() => objType.getFields()).to.throw(
'SomeObject fields must be an object with field names as keys or a ' +
'function which returns such an object.',
);
});
});

describe('Field arg config must be object', () => {
it('accepts an Object type with field args', () => {
const objType = new GraphQLObjectType({
name: 'SomeObject',
fields: {
goodField: {
type: GraphQLString,
args: {
goodArg: { type: GraphQLString },
},
},
},
});
expect(() => objType.getFields()).not.to.throw();
});

it('rejects an Object type with incorrectly typed field args', () => {
const objType = new GraphQLObjectType({
name: 'SomeObject',
fields: {
badField: {
type: GraphQLString,
args: [{ badArg: GraphQLString }],
},
},
});
expect(() => objType.getFields()).to.throw(
'SomeObject.badField args must be an object with argument names as keys.',
);
});
});

describe('Object interfaces must be array', () => {
it('accepts an Object type with array interfaces', () => {
const objType = new GraphQLObjectType({
name: 'SomeObject',
interfaces: [InterfaceType],
fields: { f: { type: GraphQLString } },
});
expect(objType.getInterfaces()[0]).to.equal(InterfaceType);
});

it('accepts an Object type with interfaces as a function returning an array', () => {
const objType = new GraphQLObjectType({
name: 'SomeObject',
interfaces: () => [InterfaceType],
fields: { f: { type: GraphQLString } },
});
expect(objType.getInterfaces()[0]).to.equal(InterfaceType);
});

it('rejects an Object type with incorrectly typed interfaces', () => {
const objType = new GraphQLObjectType({
name: 'SomeObject',
interfaces: {},
fields: { f: { type: GraphQLString } },
});
expect(() => objType.getInterfaces()).to.throw(
'SomeObject interfaces must be an Array or a function which returns an Array.',
);
});

it('rejects an Object type with interfaces as a function returning an incorrect type', () => {
const objType = new GraphQLObjectType({
name: 'SomeObject',
interfaces() {
return {};
},
fields: { f: { type: GraphQLString } },
});
expect(() => objType.getInterfaces()).to.throw(
'SomeObject interfaces must be an Array or a function which returns an Array.',
);
});
});

describe('Type System: Input Objects must have fields', () => {
it('accepts an Input Object type with fields', () => {
const inputObjType = new GraphQLInputObjectType({
name: 'SomeInputObject',
fields: {
f: { type: GraphQLString },
},
});
expect(inputObjType.getFields().f.type).to.equal(GraphQLString);
});

it('accepts an Input Object type with a field function', () => {
const inputObjType = new GraphQLInputObjectType({
name: 'SomeInputObject',
fields() {
return {
f: { type: GraphQLString },
};
},
});
expect(inputObjType.getFields().f.type).to.equal(GraphQLString);
});

it('rejects an Input Object type with incorrect fields', () => {
const inputObjType = new GraphQLInputObjectType({
name: 'SomeInputObject',
fields: [],
});
expect(() => inputObjType.getFields()).to.throw(
'SomeInputObject fields must be an object with field names as keys or a ' +
'function which returns such an object.',
);
});

it('rejects an Input Object type with fields function that returns incorrect type', () => {
const inputObjType = new GraphQLInputObjectType({
name: 'SomeInputObject',
fields() {
return [];
},
});
expect(() => inputObjType.getFields()).to.throw(
'SomeInputObject fields must be an object with field names as keys or a ' +
'function which returns such an object.',
);
});
});

describe('Type System: Input Object fields must not have resolvers', () => {
it('rejects an Input Object type with resolvers', () => {
const inputObjType = new GraphQLInputObjectType({
name: 'SomeInputObject',
fields: {
f: {
type: GraphQLString,
resolve: () => {
return 0;
},
},
},
});
expect(() => inputObjType.getFields()).to.throw(
'SomeInputObject.f field type has a resolve property, ' +
'but Input Types cannot define resolvers.',
);
});

it('rejects an Input Object type with resolver constant', () => {
const inputObjType = new GraphQLInputObjectType({
name: 'SomeInputObject',
fields: {
f: {
type: GraphQLString,
resolve: {},
},
},
});
expect(() => inputObjType.getFields()).to.throw(
'SomeInputObject.f field type has a resolve property, ' +
'but Input Types cannot define resolvers.',
);
});
});

describe('Type System: Enum types must be well defined', () => {
it('accepts a well defined Enum type with empty value definition', () => {
const enumType = new GraphQLEnumType({
name: 'SomeEnum',
values: {
FOO: {},
BAR: {},
},
});
expect(enumType.getValue('FOO').value).to.equal('FOO');
expect(enumType.getValue('BAR').value).to.equal('BAR');
});

it('accepts a well defined Enum type with internal value definition', () => {
const enumType = new GraphQLEnumType({
name: 'SomeEnum',
values: {
FOO: { value: 10 },
BAR: { value: 20 },
},
});
expect(enumType.getValue('FOO').value).to.equal(10);
expect(enumType.getValue('BAR').value).to.equal(20);
});

it('rejects an Enum type with incorrectly typed values', () => {
const enumType = new GraphQLEnumType({
name: 'SomeEnum',
values: [{ FOO: 10 }],
});
expect(() => enumType.getValue()).to.throw(
'SomeEnum values must be an object with value names as keys.',
);
});

it('rejects an Enum type with missing value definition', () => {
const enumType = new GraphQLEnumType({
name: 'SomeEnum',
values: { FOO: null },
});
expect(() => enumType.getValues()).to.throw(
'SomeEnum.FOO must refer to an object with a "value" key representing ' +
'an internal value but got: null.',
);
});

it('rejects an Enum type with incorrectly typed value definition', () => {
const enumType = new GraphQLEnumType({
name: 'SomeEnum',
values: { FOO: 10 },
});
expect(() => enumType.getValues()).to.throw(
'SomeEnum.FOO must refer to an object with a "value" key representing ' +
'an internal value but got: 10.',
);
});

it('does not allow isDeprecated without deprecationReason on enum', () => {
const enumType = new GraphQLEnumType({
name: 'SomeEnum',
values: {
FOO: {
isDeprecated: true,
},
},
});
expect(() => enumType.getValues()).to.throw(
'SomeEnum.FOO should provide "deprecationReason" instead ' +
'of "isDeprecated".',
);
});
});

describe('Type System: List must accept only types', () => {
const types = [
GraphQLString,
Expand Down

0 comments on commit 290f721

Please sign in to comment.