Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 264 additions & 8 deletions src/type/__tests__/validation-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ describe('Type System: Input Objects must have fields', () => {
expectJSON(validateSchema(schema)).toDeepEqual([
{
message:
'Invalid circular reference. The Input Object SomeInputObject references itself in the non-null field SomeInputObject.nonNullSelf.',
'Input Object SomeInputObject references itself via the required fields: SomeInputObject.nonNullSelf.',
locations: [{ line: 7, column: 9 }],
},
]);
Expand Down Expand Up @@ -952,13 +952,31 @@ describe('Type System: Input Objects must have fields', () => {
expectJSON(validateSchema(schema)).toDeepEqual([
{
message:
'Invalid circular reference. The Input Object SomeInputObject references itself via the non-null fields: SomeInputObject.startLoop, AnotherInputObject.nextInLoop, YetAnotherInputObject.closeLoop.',
'Input Object SomeInputObject references itself via the required fields: SomeInputObject.startLoop, AnotherInputObject.nextInLoop, YetAnotherInputObject.closeLoop.',
locations: [
{ line: 7, column: 9 },
{ line: 11, column: 9 },
{ line: 15, column: 9 },
],
},
{
message:
'Input Object AnotherInputObject references itself via the required fields: AnotherInputObject.nextInLoop, YetAnotherInputObject.closeLoop, SomeInputObject.startLoop.',
locations: [
{ line: 11, column: 9 },
{ line: 15, column: 9 },
{ line: 7, column: 9 },
],
},
{
message:
'Input Object YetAnotherInputObject references itself via the required fields: YetAnotherInputObject.closeLoop, SomeInputObject.startLoop, AnotherInputObject.nextInLoop.',
locations: [
{ line: 15, column: 9 },
{ line: 7, column: 9 },
{ line: 11, column: 9 },
],
},
]);
});

Expand Down Expand Up @@ -986,24 +1004,28 @@ describe('Type System: Input Objects must have fields', () => {
expectJSON(validateSchema(schema)).toDeepEqual([
{
message:
'Invalid circular reference. The Input Object SomeInputObject references itself via the non-null fields: SomeInputObject.startLoop, AnotherInputObject.closeLoop.',
'Input Object SomeInputObject references itself via the required fields: SomeInputObject.startLoop, AnotherInputObject.closeLoop.',
locations: [
{ line: 7, column: 9 },
{ line: 11, column: 9 },
],
},
{
message:
'Invalid circular reference. The Input Object AnotherInputObject references itself via the non-null fields: AnotherInputObject.startSecondLoop, YetAnotherInputObject.closeSecondLoop.',
'Input Object AnotherInputObject references itself via the required fields: AnotherInputObject.closeLoop, SomeInputObject.startLoop.',
locations: [
{ line: 12, column: 9 },
{ line: 16, column: 9 },
{ line: 11, column: 9 },
{ line: 7, column: 9 },
],
},
{
message:
'Invalid circular reference. The Input Object YetAnotherInputObject references itself in the non-null field YetAnotherInputObject.nonNullSelf.',
locations: [{ line: 17, column: 9 }],
'Input Object YetAnotherInputObject references itself via the required fields: YetAnotherInputObject.closeSecondLoop, AnotherInputObject.closeLoop, SomeInputObject.startLoop.',
locations: [
{ line: 16, column: 9 },
{ line: 11, column: 9 },
{ line: 7, column: 9 },
],
},
]);
});
Expand Down Expand Up @@ -2409,6 +2431,240 @@ describe('Type System: OneOf Input Object fields must be nullable', () => {
});
});

describe('Type System: Input Objects must not have unbreakable cycles', () => {
it('accepts a OneOf Input Object with a scalar field', () => {
const schema = buildSchema(`
type Query {
test(arg: A): Int
}

input A @oneOf {
a: Int
}
`);
expectJSON(validateSchema(schema)).toDeepEqual([]);
});

it('accepts a OneOf Input Object with a recursive list field', () => {
const schema = buildSchema(`
type Query {
test(arg: A): Int
}

input A @oneOf {
a: [A!]
}
`);
expectJSON(validateSchema(schema)).toDeepEqual([]);
});

it('accepts a OneOf Input Object referencing a non-OneOf input object', () => {
const schema = buildSchema(`
type Query {
test(arg: A): Int
}

input A @oneOf {
b: B
}

input B {
x: Int
}
`);
expectJSON(validateSchema(schema)).toDeepEqual([]);
});

it('accepts a OneOf/OneOf cycle with a scalar escape', () => {
const schema = buildSchema(`
type Query {
test(arg: A): Int
}

input A @oneOf {
b: B
escape: Int
}

input B @oneOf {
a: A
}
`);
expectJSON(validateSchema(schema)).toDeepEqual([]);
});

it('accepts a OneOf/non-OneOf cycle with a nullable escape', () => {
const schema = buildSchema(`
type Query {
test(arg: A): Int
}

input A @oneOf {
b: B
}

input B {
a: A
}
`);
expectJSON(validateSchema(schema)).toDeepEqual([]);
});

it('rejects a self-referencing OneOf type with no escapes', () => {
const schema = buildSchema(`
type Query {
test(arg: A): Int
}

input A @oneOf {
self: A
}
`);
expectJSON(validateSchema(schema)).toDeepEqual([
{
message:
'Input Object A references itself via the required fields: A.self.',
locations: [{ line: 7, column: 9 }],
},
]);
});

it('rejects a mixed OneOf/non-OneOf cycle with no escapes', () => {
const schema = buildSchema(`
type Query {
test(arg: A): Int
}

input A @oneOf {
b: B
}

input B {
a: A!
}
`);
expectJSON(validateSchema(schema)).toDeepEqual([
{
message:
'Input Object A references itself via the required fields: A.b, B.a.',
locations: [
{ line: 7, column: 9 },
{ line: 11, column: 9 },
],
},
{
message:
'Input Object B references itself via the required fields: B.a, A.b.',
locations: [
{ line: 11, column: 9 },
{ line: 7, column: 9 },
],
},
]);
});

it('accepts a OneOf/non-OneOf with scalar escape', () => {
const schema = buildSchema(`
type Query {
test(arg: A): Int
}

input A @oneOf {
b: B
escape: Int
}

input B {
a: A!
}
`);
expectJSON(validateSchema(schema)).toDeepEqual([]);
});

it('accepts a non-OneOf/non-OneOf cycle with a nullable escape', () => {
const schema = buildSchema(`
type Query {
test(arg: A): Int
}

input A {
b: B!
}

input B {
a: A
}
`);
expectJSON(validateSchema(schema)).toDeepEqual([]);
});

it('accepts a non-OneOf/non-OneOf cycle with a list escape', () => {
const schema = buildSchema(`
type Query {
test(arg: A): Int
}

input A {
b: [B!]!
}

input B {
a: A!
}
`);
expectJSON(validateSchema(schema)).toDeepEqual([]);
});

it('rejects a larger mixed OneOf/non-OneOf cycle with no escapes', () => {
const schema = buildSchema(`
type Query {
test(arg: A): Int
}

input A @oneOf {
b: B
}

input B {
c: C!
}

input C @oneOf {
a: A
}
`);
expectJSON(validateSchema(schema)).toDeepEqual([
{
message:
'Input Object A references itself via the required fields: A.b, B.c, C.a.',
locations: [
{ line: 7, column: 9 },
{ line: 11, column: 9 },
{ line: 15, column: 9 },
],
},
{
message:
'Input Object B references itself via the required fields: B.c, C.a, A.b.',
locations: [
{ line: 11, column: 9 },
{ line: 15, column: 9 },
{ line: 7, column: 9 },
],
},
{
message:
'Input Object C references itself via the required fields: C.a, A.b, B.c.',
locations: [
{ line: 15, column: 9 },
{ line: 7, column: 9 },
{ line: 11, column: 9 },
],
},
]);
});
});

describe('Objects must adhere to Interface they implement', () => {
it('accepts an Object which implements an Interface', () => {
const schema = buildSchema(`
Expand Down
Loading
Loading