Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
357 changes: 357 additions & 0 deletions src/__tests__/recursive-input.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
import { DocumentNode, GraphQLSchema, parse, validate } from "graphql";
import { makeExecutableSchema } from "graphql-tools";
import { compileQuery, isCompiledQuery } from "../execution";

describe("recursive input types", () => {
describe("simple recursive input", () => {
const schema = makeExecutableSchema({
typeDefs: `
type Query {
foo(input: FooInput): String
}
input FooInput {
foo: FooInput
}
`,
resolvers: {
Query: {
foo(_, args) {
// used as the acutal value in test matchers
return JSON.stringify(args);
}
}
}
});

test("should not fail for recursive input without variables", () => {
const query = parse(`
{
foo(input: {
foo: {
foo: {
foo: {
foo: {}
}
}
}
})
}
`);

const result = executeQuery(schema, query);

expect(result.errors).toBeUndefined();
expect(result.data.foo).toBe(
JSON.stringify({
input: {
foo: { foo: { foo: { foo: {} } } }
}
})
);
});

test("should not fail with variables using recursive input types", () => {
const document = parse(`
query ($f: FooInput) {
foo(input: $f)
}
`);
const variables = {
f: {
foo: { foo: { foo: {} } }
}
};

const result = executeQuery(schema, document, variables);
expect(result.errors).toBeUndefined();
expect(result.data.foo).toBe(
JSON.stringify({
input: { foo: { foo: { foo: {} } } }
})
);
});

// when the recursive variable appers at a nested level
test("should not fail with variables using recursive input types - 2", () => {
const document = parse(`
query ($f: FooInput) {
foo(input: {
foo: { foo: { foo: $f } }
})
}
`);
const variables = {
f: {
foo: { foo: { foo: {} } }
}
};

const result = executeQuery(schema, document, variables);
expect(result.errors).toBeUndefined();
expect(result.data.foo).toBe(
JSON.stringify({
input: { foo: { foo: { foo: { foo: { foo: { foo: {} } } } } } }
})
);
});

test("should work with multiple variables using the same recursive input type", () => {
const document = parse(`
query ($f: FooInput, $g: FooInput) {
a: foo(input: $f)
b: foo(input: $g)
}
`);
const variables = {
f: {
foo: { foo: { foo: {} } }
},
g: {
foo: {}
}
};

const result = executeQuery(schema, document, variables);
expect(result.errors).toBeUndefined();
expect(result.data.a).toBe(
JSON.stringify({
input: { foo: { foo: { foo: {} } } }
})
);
expect(result.data.b).toBe(
JSON.stringify({
input: { foo: {} }
})
);
});

test("should work with multiple variables using the same recursive input type - 2 (reverse order)", () => {
const document = parse(`
query ($f: FooInput, $g: FooInput) {
a: foo(input: $g)
b: foo(input: $f)
}
`);
const variables = {
g: {
foo: {}
},
f: {
foo: { foo: { foo: {} } }
}
};

const result = executeQuery(schema, document, variables);
expect(result.errors).toBeUndefined();
expect(result.data.b).toBe(
JSON.stringify({
input: { foo: { foo: { foo: {} } } }
})
);
expect(result.data.a).toBe(
JSON.stringify({
input: { foo: {} }
})
);
});

test("should capture and throw error when recursive value is passed", () => {
const document = parse(`
query ($f: FooInput) {
foo(input: $f)
}
`);
const variables: any = {
f: {
foo: { foo: { foo: {} } }
}
};
variables.f.foo = variables.f;

const result = executeQuery(schema, document, variables);
expect(result.errors[0].message).toBe(
"Circular reference detected in input variable '$f' at foo.foo"
);
});
});

describe("mutually recursive input types", () => {
const schema = makeExecutableSchema({
typeDefs: `
type Query {
products(filter: Filter): String
}
input Filter {
and: AndFilter
or: OrFilter
like: String
}
input AndFilter {
left: Filter
right: Filter
}
input OrFilter {
left: Filter
right: Filter
}
`,
resolvers: {
Query: {
products(_, args) {
// used as the acutal value in test matchers
return JSON.stringify(args);
}
}
}
});

test("should not fail for mutually recursive variables", () => {
const document = parse(`
query ($filter1: Filter) {
products(filter: $filter1)
}
`);

const variables = {
filter1: {
and: {
left: {
like: "windows"
},
right: {
or: {
left: {
like: "xp"
},
right: {
like: "vista"
}
}
}
}
}
};

const result = executeQuery(schema, document, variables);
expect(JSON.parse(result.data.products).filter).toEqual(
variables.filter1
);
});

test("should not fail for mutually recursive variables - multiple variables", () => {
const document = parse(`
query ($aFilter: Filter, $bFilter: Filter) {
a: products(filter: $aFilter)
b: products(filter: $bFilter)
}
`);

const variables = {
aFilter: {
and: {
left: {
like: "windows"
},
right: {
or: {
left: {
like: "xp"
},
right: {
like: "vista"
}
}
}
}
},
bFilter: {
like: "mac",
or: {
left: {
like: "10"
},
right: {
like: "11"
}
}
}
};

const result = executeQuery(schema, document, variables);
expect(JSON.parse(result.data.a).filter).toEqual(variables.aFilter);
expect(JSON.parse(result.data.b).filter).toEqual(variables.bFilter);
});

// when the mutually recursive input type appears at nested level
// instead of the top-level variable
test("should not fail for mutually recursive variables - 2", () => {
const document = parse(`
query ($macFilter: OrFilter) {
products(filter: {
like: "mac"
and: {
left: { like: "User" }
right: { like: "foo" }
}
or: $macFilter
})
}
`);

const variables = {
macFilter: {
left: { like: "Applications/Safari" },
right: { like: "Applications/Notes" }
}
};

const result = executeQuery(schema, document, variables);
expect(JSON.parse(result.data.products).filter.or).toEqual(
variables.macFilter
);
});

test("should throw for mutually recursive runtime values", () => {
const document = parse(`
query ($filter1: Filter) {
products(filter: $filter1)
}
`);

const variables: any = {
filter1: {
and: {
left: {}
},
or: {
left: {}
}
}
};
// create mututal recursion at
// and.left.or.left.and <-> or.left.and.left.or
variables.filter1.and.left.or = variables.filter1.or;
variables.filter1.or.left.and = variables.filter1.and;

const result = executeQuery(schema, document, variables);
expect(result.errors[0].message).toBe(
"Circular reference detected in input variable '$filter1' at and.left.or.left.and"
);
});
});
});

function executeQuery(
schema: GraphQLSchema,
document: DocumentNode,
variableValues?: any,
rootValue?: any,
contextValue?: any,
operationName?: string
) {
const prepared: any = compileQuery(schema, document as any, operationName);
if (!isCompiledQuery(prepared)) {
return prepared;
}
return prepared.query(rootValue, contextValue, variableValues || {});
}
Loading