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_1189_variable_skip_is_preserved_in_subgraph_operation() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
query ($v: Boolean!) {
product @skip(if: $v) {
price
}
}
"#,
);
let query_plan = build_query_plan_with_defaults(
"fixture/tests/simple-include-skip.supergraph.graphql",
document,
)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Fetch(service: "a") {
query ($v:Boolean!) {
product @skip(if: $v) {
price
}
}
},
},
"#);
Ok(())
}
#[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(())
}
#[test]
fn issue_1539_requires_after_entity_hop() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
{
user {
orders {
name
}
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/1539.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Sequence {
Fetch(service: "users") {
{
user {
__typename
id
}
}
},
Flatten(path: "user") {
Fetch(service: "orders") {
{
... on User {
__typename
id
}
} =>
{
... on User {
orders {
__typename
id
sku
}
}
}
},
},
Flatten(path: "user.orders.@") {
Fetch(service: "catalog") {
{
... on Order {
__typename
sku
id
}
} =>
{
... on Order {
name
}
}
},
},
},
},
"#);
Ok(())
}
#[test]
fn issue_1539_requires_after_entity_hop_with_explicit_requirement() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
{
user {
orders {
sku
name
}
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/1539.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Sequence {
Fetch(service: "users") {
{
user {
__typename
id
}
}
},
Flatten(path: "user") {
Fetch(service: "orders") {
{
... on User {
__typename
id
}
} =>
{
... on User {
orders {
__typename
sku
id
}
}
}
},
},
Flatten(path: "user.orders.@") {
Fetch(service: "catalog") {
{
... on Order {
__typename
sku
id
}
} =>
{
... on Order {
name
}
}
},
},
},
},
"#);
Ok(())
}
#[test]
fn issue_1539_requires_on_root_parent() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
{
order {
name
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/1539.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Sequence {
Fetch(service: "orders") {
{
order {
__typename
id
sku
}
}
},
Flatten(path: "order") {
Fetch(service: "catalog") {
{
... on Order {
__typename
sku
id
}
} =>
{
... on Order {
name
}
}
},
},
},
},
"#);
Ok(())
}
#[test]
fn issue_1539_requires_on_self_referential_entity() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
{
order {
related {
name
}
}
}
"#,
);
let query_plan = build_query_plan_with_defaults(
"fixture/issues/1539-self-referential.supergraph.graphql",
document,
)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Sequence {
Fetch(service: "users") {
{
order {
__typename
id
}
}
},
Flatten(path: "order") {
Fetch(service: "orders") {
{
... on Order {
__typename
id
}
} =>
{
... on Order {
related {
__typename
id
sku
}
}
}
},
},
Flatten(path: "order.related") {
Fetch(service: "catalog") {
{
... on Order {
__typename
sku
id
}
} =>
{
... on Order {
name
}
}
},
},
},
},
"#);
Ok(())
}
#[test]
fn issue_1308_requires_through_entity_interface_after_entity_hop() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
query {
cage {
listings {
rank
}
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/1308.supergraph.graphql", document)?;
let query_plan = query_plan.to_string();
assert!(query_plan.contains("tricks"));
assert!(query_plan.contains("whiskers"));
assert!(query_plan.contains("rank"));
Ok(())
}
#[test]
fn issue_1308_conditional_rank_keeps_concrete_requires_types() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
query ($includeRank: Boolean!) {
cage {
listings {
rank @include(if: $includeRank)
}
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/1308.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Sequence {
Fetch(service: "catalog") {
{
cage {
__typename
id
}
}
},
Flatten(path: "cage") {
Fetch(service: "search") {
{
... on Cage {
__typename
id
}
} =>
($includeRank:Boolean!) {
... on Cage {
listings {
__typename
id
... on Listing @include(if: $includeRank) {
pet {
__typename
id
}
}
}
}
}
},
},
Include(if: $includeRank) {
Sequence {
Flatten(path: "cage.listings.@.pet") {
Fetch(service: "catalog") {
{
... on Animal {
__typename
id
}
} =>
{
... on Animal {
__typename
... on Cat {
whiskers
}
... on Dog {
tricks
}
}
}
},
},
Flatten(path: "cage.listings.@") {
Fetch(service: "ranking") {
{
... on Listing {
__typename
pet {
__typename
... on Dog {
tricks
}
... on Cat {
whiskers
}
}
id
}
} =>
{
... on Listing {
rank
}
}
},
},
},
},
},
},
"#);
Ok(())
}
#[test]
fn issue_1309_requires_through_entity_interface() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
query {
listings {
rank
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/1309.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Sequence {
Fetch(service: "search") {
{
listings {
__typename
id
pet {
__typename
id
}
}
}
},
Flatten(path: "listings.@.pet") {
Fetch(service: "catalog") {
{
... on Animal {
__typename
id
}
} =>
{
... on Animal {
__typename
... on Cat {
whiskers
}
... on Dog {
tricks
}
}
}
},
},
Flatten(path: "listings.@") {
Fetch(service: "ranking") {
{
... on Listing {
__typename
pet {
__typename
... on Dog {
tricks
}
... on Cat {
whiskers
}
}
id
}
} =>
{
... on Listing {
rank
}
}
},
},
},
},
"#);
Ok(())
}
#[test]
fn issue_1310_nested_requires_with_multiple_providers() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
query {
pet {
checkup {
grade
}
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/1310.supergraph.graphql", document)?;
let query_plan = query_plan.to_string();
for expected in ["age", "weight", "fee", "grade"] {
assert!(
query_plan.contains(expected),
"missing {expected}: {query_plan}"
);
}
Ok(())
}
#[test]
fn issue_1311_different_arguments_under_type_conditions() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
query {
storefront {
departments {
... on Aquatics {
photo {
thumbnail(width: 100)
}
}
... on Reptiles {
photo {
thumbnail(width: 200)
}
}
}
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/1311.supergraph.graphql", document)?;
let query_plan = query_plan.to_string();
assert!(query_plan.contains("thumbnail(width: 100)"));
assert!(query_plan.contains("thumbnail(width: 200)"));
Ok(())
}
#[test]
fn issue_1647_include_on_field_after_conditional_entity_call() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
query ($withDetails: Boolean!, $id: ID! = "", $term: String! = "") {
record(id: $id) @include(if: $withDetails) {
... on User {
id
email
invoices
}
}
listed: catalog {
search(term: $term, kind: "item", source: "store") @include(if: $withDetails) {
... on Item {
id
label
}
}
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/1647.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Sequence {
Fetch(service: "accounts") {
query ($id:ID!="",$term:String!="",$withDetails:Boolean!) {
record(id: $id) @include(if: $withDetails) {
__typename
... on User {
__typename
id
email
}
}
listed: catalog {
search(kind: "item", source: "store", term: $term) @include(if: $withDetails) {
__typename
... on Item {
id
label
}
}
}
}
},
Include(if: $withDetails) {
Flatten(path: "record|[User]") {
Fetch(service: "billing") {
{
... on User {
__typename
id
}
} =>
{
... on User {
invoices
}
}
},
},
},
},
},
"#);
Ok(())
}
#[test]
fn issue_1647_include_on_fragment_after_conditional_entity_call() -> Result<(), Box<dyn Error>> {
init_logger();
let document = parse_operation(
r#"
query ($withDetails: Boolean!, $id: ID! = "", $term: String! = "") {
record(id: $id) @include(if: $withDetails) {
... on User {
id
email
invoices
}
}
... on Query @include(if: $withDetails) {
listed: catalog {
search(term: $term, kind: "item", source: "store") {
... on Item {
id
label
}
}
}
}
}
"#,
);
let query_plan =
build_query_plan_with_defaults("fixture/issues/1647.supergraph.graphql", document)?;
insta::assert_snapshot!(format!("{}", query_plan), @r#"
QueryPlan {
Sequence {
Fetch(service: "accounts") {
query ($id:ID!="",$term:String!="",$withDetails:Boolean!) {
record(id: $id) @include(if: $withDetails) {
__typename
... on User {
__typename
id
email
}
}
... on Query @include(if: $withDetails) {
listed: catalog {
search(kind: "item", source: "store", term: $term) {
__typename
... on Item {
id
label
}
}
}
}
}
},
Include(if: $withDetails) {
Flatten(path: "record|[User]") {
Fetch(service: "billing") {
{
... on User {
__typename
id
}
} =>
{
... on User {
invoices
}
}
},
},
},
},
},
"#);
Ok(())
}