use crate::query_planner::{
planner::QueryPlannerOptions,
tests::testkit::{build_query_plan, build_query_plan_with_defaults, init_logger},
utils::parsing::parse_operation,
};
use std::error::Error;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
#[test]
fn issue_281_test() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
{
viewer {
review {
... on AnonymousReview {
__typename
product {
b
}
}
... on UserReview {
__typename
product {
c
d
}
}
}
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/281.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Sequence {
Fetch(service: "a") {
{
viewer {
review {
__typename
... on AnonymousReview {
__typename
product {
...a
}
}
... on UserReview {
__typename
product {
...a
}
}
}
}
}
fragment a on Product {
__typename
id
}
},
BatchFetch(service: "b") {
{
_e0 {
paths: [
"viewer.review|[UserReview].product"
]
{
... on Product {
__typename
id
}
}
}
_e1 {
paths: [
"viewer.review|[AnonymousReview].product"
]
{
... on Product {
__typename
id
}
}
}
}
{
_e0: _entities(representations: $__batch_reps_0) {
... on Product {
pid
}
}
_e1: _entities(representations: $__batch_reps_1) {
... on Product {
b
}
}
}
},
Parallel {
Flatten(path: "viewer.review|[UserReview].product") {
Fetch(service: "c") {
{
... on Product {
__typename
pid
}
} =>
{
... on Product {
c
}
}
},
},
Flatten(path: "viewer.review|[UserReview].product") {
Fetch(service: "d") {
{
... on Product {
__typename
pid
}
} =>
{
... on Product {
d
}
}
},
},
},
},
},
"#);
Ok(())
}
#[test]
fn issue_190_test() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
query(
$included: Boolean!
) {
recommender @include(if: $included) {
id
results {
...Recommendable_Product
__typename
}
__typename
}
}
fragment Recommendable_Product on Product {
id
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/190.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Fetch(service: "recommender") {
query ($included:Boolean!) {
recommender @include(if: $included) {
id
results {
__typename
... on Product {
id
}
}
__typename
}
}
},
},
"#);
let document = parse_operation(
r#"
query(
$included: Boolean!
) {
recommender @include(if: $included) {
id
results {
...Recommendable_Product
}
}
}
fragment Recommendable_Product on Product {
id
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/190.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Fetch(service: "recommender") {
query ($included:Boolean!) {
recommender @include(if: $included) {
id
results {
__typename
... on Product {
id
}
}
}
}
},
},
"#);
let document = parse_operation(
r#"
query(
$included: Boolean!
) {
recommender @include(if: $included) {
id
results {
... on Product {
id
}
}
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/190.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Fetch(service: "recommender") {
query ($included:Boolean!) {
recommender @include(if: $included) {
id
results {
__typename
... on Product {
id
}
}
}
}
},
},
"#);
Ok(())
}
#[test]
fn issue_939_test() -> Result<(), Box<dyn Error>> {
init_logger();
let named_fragment_document = parse_operation(
r#"
query SingleNode($id: ID!) {
node(id: $id) {
... on MyNode {
content {
... on ITextContent {
fragments {
contentNode {
content {
...ITextContentPreview
}
}
}
}
}
}
}
}
fragment ITextContentPreview on ITextContent {
id
}
"#,
);
let named_fragment_plan = build_query_plan_with_defaults(
"fixture/issues/939.supergraph.graphql",
named_fragment_document,
)?;
let inline_fragment_document = parse_operation(
r#"
query SingleNode($id: ID!) {
node(id: $id) {
... on MyNode {
content {
... on ITextContent {
fragments {
contentNode {
content {
... on ITextContent {
id
}
}
}
}
}
}
}
}
}
"#,
);
let inline_fragment_plan = build_query_plan_with_defaults(
"fixture/issues/939.supergraph.graphql",
inline_fragment_document,
)?;
assert_eq!(
format!("{}", named_fragment_plan),
format!("{}", inline_fragment_plan)
);
insta::assert_snapshot!(format!("{}", inline_fragment_plan), @r#"
QueryPlan {
Fetch(service: "content") {
query ($id:ID!) {
node(id: $id) {
__typename
... on MyNode {
content {
__typename
... on TextContent {
fragments {
...a
}
}
... on TextGroupContent {
fragments {
...a
}
}
}
}
}
}
fragment a on TextContentFragment {
contentNode {
content {
__typename
... on TextContent {
id
}
... on TextGroupContent {
id
}
}
}
}
},
},
"#);
Ok(())
}
#[test]
fn experimental_abstract_type_folding_folds_object_fragments_into_interface(
) -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
query SingleNode($id: ID!) {
node(id: $id) {
... on MyNode {
content {
... on TextContent {
id
}
... on TextGroupContent {
id
}
}
}
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/939.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Fetch(service: "content") {
query ($id:ID!) {
node(id: $id) {
__typename
... on MyNode {
content {
__typename
... on TextContent {
id
}
... on TextGroupContent {
id
}
}
}
}
}
},
},
"#);
let document = parse_operation(
r#"
query SingleNode($id: ID!) {
node(id: $id) {
... on MyNode {
content {
... on TextContent {
id
}
... on TextGroupContent {
id
}
}
}
}
}
"#,
);
let query_plan = build_query_plan(
"fixture/issues/939.supergraph.graphql",
document,
Default::default(),
QueryPlannerOptions {
experimental_abstract_type_folding: true,
},
)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Fetch(service: "content") {
query ($id:ID!) {
node(id: $id) {
__typename
... on MyNode {
content {
__typename
... on ITextContent {
id
}
}
}
}
}
},
},
"#);
Ok(())
}
#[test]
fn issue_965_test() -> Result<(), Box<dyn Error>> {
init_logger();
let abstract_named_fragment_document = parse_operation(
r#"
query {
account(id: "a1") {
...Test
}
}
fragment Test on Node {
... on Node {
id
}
... on Account {
username
}
}
"#,
);
let abstract_named_fragment_plan = build_query_plan_with_defaults(
"fixture/tests/corrupted-supergraph-node-id.supergraph.graphql",
abstract_named_fragment_document,
)?;
let concrete_named_fragment_document = parse_operation(
r#"
query {
account(id: "a1") {
...Test
}
}
fragment Test on Account {
... on Node {
id
}
username
}
"#,
);
let concrete_named_fragment_plan = build_query_plan_with_defaults(
"fixture/tests/corrupted-supergraph-node-id.supergraph.graphql",
concrete_named_fragment_document,
)?;
insta::assert_snapshot!(format!("{}", concrete_named_fragment_plan), @r#"
QueryPlan {
Fetch(service: "a") {
{
account(id: "a1") {
id
username
}
}
},
},
"#);
insta::assert_snapshot!(format!("{}", abstract_named_fragment_plan), @r#"
QueryPlan {
Fetch(service: "a") {
{
account(id: "a1") {
id
username
}
}
},
},
"#);
Ok(())
}
#[test]
fn issue_965_mixed_nested_fragments_with_directives_test() -> Result<(), Box<dyn Error>> {
init_logger();
let abstract_named_fragment_document = parse_operation(
r#"
query($outer: Boolean!, $inner: Boolean!, $skip: Boolean!) {
account(id: "a1") {
...Test
}
}
fragment Test on Node {
... on Node @include(if: $outer) {
...Inner @include(if: $inner)
}
... on Account @skip(if: $skip) {
username
}
}
fragment Inner on Node {
... {
id
}
}
"#,
);
let abstract_named_fragment_plan = build_query_plan_with_defaults(
"fixture/tests/corrupted-supergraph-node-id.supergraph.graphql",
abstract_named_fragment_document,
)?;
let concrete_named_fragment_document = parse_operation(
r#"
query($outer: Boolean!, $inner: Boolean!, $skip: Boolean!) {
account(id: "a1") {
...Test
}
}
fragment Test on Account {
... on Account @include(if: $outer) {
...Inner @include(if: $inner)
}
... on Account @skip(if: $skip) {
username
}
}
fragment Inner on Account {
id
}
"#,
);
let concrete_named_fragment_plan = build_query_plan_with_defaults(
"fixture/tests/corrupted-supergraph-node-id.supergraph.graphql",
concrete_named_fragment_document,
)?;
insta::assert_snapshot!(format!("{}", concrete_named_fragment_plan), @r#"
QueryPlan {
Fetch(service: "a") {
query ($inner:Boolean!,$outer:Boolean!,$skip:Boolean!) {
account(id: "a1") {
... on Account @include(if: $outer) {
... on Account @include(if: $inner) {
id
}
}
... on Account @skip(if: $skip) {
username
}
}
}
},
},
"#);
insta::assert_snapshot!(format!("{}", abstract_named_fragment_plan), @r#"
QueryPlan {
Fetch(service: "a") {
query ($inner:Boolean!,$outer:Boolean!,$skip:Boolean!) {
account(id: "a1") {
... on Account @include(if: $outer) {
... on Account @include(if: $inner) {
id
}
}
... on Account @skip(if: $skip) {
username
}
}
}
},
},
"#);
Ok(())
}
#[test]
fn issue_interface_object_typename() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
{
me {
__typename
}
}
"#,
);
let query_plan = build_query_plan_with_defaults(
"fixture/issues/infinite-typename-interfaceobject.graphql",
document,
)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Fetch(service: "s3") {
{
me {
__typename
}
}
},
},
"#);
Ok(())
}
#[test]
fn recursion_bomb_test() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
{
list {
items {
value
}
}
}
"#,
);
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let ok = build_query_plan_with_defaults(
"fixture/issues/recursion-bomb.supergraph.graphql",
document,
)
.is_ok();
let _ = tx.send(ok);
});
let ok = rx
.recv_timeout(Duration::from_secs(2))
.expect("query planner timed out");
assert!(ok);
Ok(())
}
#[test]
fn requires_circular_keys_test() -> Result<(), Box<dyn Error>> {
init_logger();
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let queries = [
r#"
{
list {
items {
value
valueRequiresObjectFragment
valueRequiresInterfaceFragment
valueRequiresInlineFragment
valueNestedField
}
}
}
"#,
r#"
{
list {
taggedItems {
... on Item {
valueInterfaceKey
}
}
}
}
"#,
];
let result = queries.iter().try_for_each(|query| {
let document = parse_operation(query);
build_query_plan_with_defaults(
"fixture/issues/requires-circular-keys.supergraph.graphql",
document,
)
.map(|_| ())
.map_err(|error| error.to_string())
});
let _ = tx.send(result);
});
let result = rx
.recv_timeout(Duration::from_secs(2))
.expect("query planner timed out");
assert!(result.is_ok(), "{}", result.unwrap_err());
Ok(())
}
#[test]
fn requires_self_dependency_false_positive() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
{
books {
edition {
book {
catalogId
}
}
isRecommended
}
}
"#,
);
let plan = build_query_plan_with_defaults(
"fixture/issues/requires-self-dependency-false-positive.supergraph.graphql",
document,
)?;
insta::assert_snapshot!(format!("{}", plan), @r#"
QueryPlan {
Sequence {
Fetch(service: "library") {
{
books {
__typename
edition {
book {
catalogId
}
isbn
}
}
}
},
Flatten(path: "books.@") {
Fetch(service: "recommender") {
{
... on BookListing {
__typename
edition {
book {
catalogId
}
isbn
}
}
} =>
{
... on BookListing {
isRecommended
}
}
},
},
},
},
"#);
Ok(())
}