use apollo_compiler::parser::Parser;
use apollo_compiler::ExecutableDocument;
use apollo_compiler::Schema;
use expect_test::expect;
fn build_fragment_chain(size: usize) -> String {
let mut query = r#"
query Introspection{
__schema {
types {
...typeFragment1
}
}
}
"#
.to_string();
for i in 1..size {
query.push_str(&format!(
"
fragment typeFragment{i} on __Type {{
ofType {{
...typeFragment{}
}}
}}",
i + 1
));
}
query.push_str(&format!(
"
fragment typeFragment{size} on __Type {{
ofType {{
name
}}
}}"
));
query
}
fn build_flat_fragment_chain(size: usize) -> String {
let mut query = r#"
query Introspection{
__schema {
types {
...typeFragment1
}
}
}
"#
.to_string();
for i in 1..size {
query.push_str(&format!(
"
fragment typeFragment{i} on __Type {{
...typeFragment{}
}}",
i + 1
));
}
query.push_str(&format!(
"
fragment typeFragment{size} on __Type {{
name
}}"
));
query
}
fn build_directive_chain(size: usize) -> String {
let mut schema = r#"
type Query {
field: Int! @directive(arg: true)
}
directive @directive(arg: Boolean @argDir1) on FIELD_DEFINITION
"#
.to_string();
for i in 1..size {
schema.push_str(&format!(
"
directive @argDir{i}(arg: Boolean @argDir{}) on ARGUMENT_DEFINITION
",
i + 1
));
}
schema.push_str(&format!(
"
directive @argDir{size}(arg: Boolean) on ARGUMENT_DEFINITION
"
));
schema
}
fn build_input_object_chain(size: usize) -> String {
let mut schema = r#"
type Query {
field(arg: VeryVeryDeep): Boolean
}
input VeryVeryDeep {
nest: VeryVeryDeep1!
}
"#
.to_string();
for i in 1..size {
schema.push_str(&format!(
"
input VeryVeryDeep{i} {{ nest: VeryVeryDeep{}! }}
",
i + 1
));
}
schema.push_str(&format!(
"
input VeryVeryDeep{size} {{ final: Boolean }}
"
));
schema
}
fn build_nested_selection(depth: usize) -> String {
format!(
"query {}{}{}",
"{ recur\n".repeat(depth),
"{ leaf(arg: true) leaf(arg: false) }",
"\n}".repeat(depth),
)
}
#[test]
fn long_nested_fragment_chains_do_not_overflow_stack() {
let query = build_fragment_chain(1_000);
let errors = Parser::new()
.parse_mixed_validate(
format!(
"type Query {{ a: Int }}
{query}"
),
"overflow.graphql",
)
.expect_err("must have recursion errors");
let expected = expect_test::expect![[r#"
Error: too much recursion
Error: too much recursion
Error: too much recursion
Error: `typeFragment1` contains too much nesting
╭─[ overflow.graphql:11:11 ]
│
11 │ fragment typeFragment1 on __Type {
│ ───────────┬──────────
│ ╰──────────── references a very long chain of fragments in its definition
────╯
"#]];
expected.assert_eq(&errors.to_string());
}
#[test]
fn long_flat_fragment_chains_do_not_overflow_stack() {
let query = build_flat_fragment_chain(10_000);
let errors = Parser::new()
.parse_mixed_validate(
format!(
"type Query {{ a: Int }}
{query}"
),
"overflow.graphql",
)
.expect_err("must have recursion errors");
let expected = expect_test::expect![[r#"
Error: too much recursion
Error: too much recursion
Error: `typeFragment1` contains too much nesting
╭─[ overflow.graphql:11:11 ]
│
11 │ fragment typeFragment1 on __Type {
│ ───────────┬──────────
│ ╰──────────── references a very long chain of fragments in its definition
────╯
"#]];
expected.assert_eq(&errors.to_string());
}
#[test]
fn long_flat_fragment_chains_do_not_overflow_stack_in_subscriptions() {
let size = 10_000;
let mut query = r#"
subscription {
...subscriptionFragment1
}
"#
.to_string();
for i in 1..size {
query.push_str(&format!(
"
fragment subscriptionFragment{i} on Subscription {{
...subscriptionFragment{}
}}",
i + 1
));
}
query.push_str(&format!(
"
fragment subscriptionFragment{size} on Subscription {{
a
}}"
));
let errors = Parser::new()
.parse_mixed_validate(
format!(
"
type Query {{ notUsed: Int }}
type Subscription {{ a: Int }}
{query}"
),
"overflow.graphql",
)
.expect_err("must have recursion errors");
let expected = expect_test::expect![[r#"
Error: too much recursion
Error: too much recursion
Error: too much recursion
Error: `subscriptionFragment1` contains too much nesting
╭─[ overflow.graphql:9:11 ]
│
9 │ fragment subscriptionFragment1 on Subscription {
│ ───────────────┬──────────────
│ ╰──────────────── references a very long chain of fragments in its definition
───╯
"#]];
expected.assert_eq(&errors.to_string());
}
#[test]
fn not_long_enough_fragment_chain_applies_correctly() {
let query = build_fragment_chain(99);
Parser::new()
.parse_mixed_validate(
format!(
"type Query {{ a: Int }}
{query}"
),
"no_overflow.graphql",
)
.expect("must not have recursion errors");
}
#[test]
fn long_directive_chains_do_not_overflow_stack() {
let schema = build_directive_chain(500);
let partial = apollo_compiler::Schema::parse_and_validate(schema, "directives.graphql")
.expect_err("must have recursion errors");
assert_eq!(partial.errors.len(), 469);
}
#[test]
fn not_long_enough_directive_chain_applies_correctly() {
let schema = build_directive_chain(31);
let _schema = apollo_compiler::Schema::parse_and_validate(schema, "directives.graphql")
.expect("must not have recursion errors");
}
#[test]
fn long_input_object_chains_do_not_overflow_stack() {
let schema = build_input_object_chain(500);
let partial = apollo_compiler::Schema::parse_and_validate(schema, "input_objects.graphql")
.expect_err("must have recursion errors");
assert_eq!(partial.errors.len(), 469);
}
#[test]
fn not_long_enough_input_object_chain_applies_correctly() {
let schema = build_input_object_chain(31);
let _schema = apollo_compiler::Schema::parse_and_validate(schema, "input_objects.graphql")
.expect("must not have recursion errors");
}
#[test]
fn deeply_nested_selection_bails_out() {
let schema = r#"
type Recur {
recur: Recur
leaf(arg: Boolean): Int
}
type Query {
recur: Recur
}
"#;
let query = build_nested_selection(400);
let errors = Parser::new()
.parse_mixed_validate(format!("{schema}\n{query}"), "selection.graphql")
.expect_err("must have validation error");
expect![[r#"
Error: too much recursion
"#]]
.assert_eq(&errors.to_string());
}
#[test]
fn handles_directive_with_nested_input_types() {
let schema = r#"
directive @custom(input: NestedInput) on OBJECT | INTERFACE
input NestedInput {
name: String
nested: NestedInput
}
type Query {
foo: String
}
"#;
let input_executable = r#"
query {
foo
}
"#;
let schema = Schema::parse_and_validate(schema, "schema.graphql").unwrap();
ExecutableDocument::parse_and_validate(&schema, input_executable, "query.graphql").unwrap();
}