diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index 8439dd33b..ab031c20f 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -108,6 +108,7 @@ jobs: env: APOLLO_ROVER_DEV_COMPOSITION_VERSION: ${{ matrix.composition-version }} APOLLO_ROVER_DEV_ROUTER_VERSION: ${{ matrix.router-version }} + CARGO_MANIFEST_DIR: ${{ github.workspace }} run: | E2E_BINARY=$(find ./${{ matrix.testing_target.binary_under_test }}/deps -type f ! -name "*.*" -and -name "e2e-*") echo "Found '$E2E_BINARY'" @@ -118,6 +119,7 @@ jobs: APOLLO_ROVER_DEV_COMPOSITION_VERSION: ${{ matrix.composition-version }} APOLLO_ROVER_DEV_ROUTER_VERSION: ${{ matrix.router-version }} CARGO_BIN_EXE_rover: ${{ github.workspace }}\${{ matrix.testing_target.binary_under_test }}\rover.exe + CARGO_MANIFEST_DIR: ${{ github.workspace }} run: | $E2E_BINARY=Get-ChildItem -Path .\${{ matrix.testing_target.binary_under_test }}\deps -File | Where-Object { $_.Name -like 'e2e-*.exe' } | ForEach-Object { $_.FullName } Write-Output "Found '$E2E_BINARY'" diff --git a/Cargo.lock b/Cargo.lock index e43f8e9ee..df453c3c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -748,7 +748,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", - "regex-automata", + "regex-automata 0.4.7", "serde", ] @@ -1637,6 +1637,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "dircpy" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "015cf520d424257fb8fbeccda4ee8d921b02907ae612484f036fa1a432b9036e" +dependencies = [ + "jwalk", + "log", + "walkdir", +] + [[package]] name = "directories-next" version = "2.0.0" @@ -2234,8 +2245,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -2771,7 +2782,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata", + "regex-automata 0.4.7", "same-file", "walkdir", "winapi-util", @@ -3031,7 +3042,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax", + "regex-syntax 0.8.4", "string_cache", "term", "tiny-keccak", @@ -3045,7 +3056,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata", + "regex-automata 0.4.7", ] [[package]] @@ -3291,6 +3302,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matches" version = "0.1.10" @@ -3981,6 +4001,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "portpicker" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" +dependencies = [ + "rand", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -4222,8 +4251,17 @@ checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -4234,9 +4272,15 @@ checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.4", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.4" @@ -4407,6 +4451,7 @@ dependencies = [ "crossbeam-channel", "ctrlc", "dialoguer", + "dircpy", "duct", "flate2", "git2", @@ -4421,6 +4466,7 @@ dependencies = [ "notify", "opener", "os_info", + "portpicker", "predicates", "prettytable-rs", "rayon", @@ -4447,6 +4493,7 @@ dependencies = [ "tokio", "toml", "tracing", + "tracing-test", "url", "uuid", "which 6.0.1", @@ -5757,15 +5804,40 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ + "matchers", "nu-ansi-term", + "once_cell", "parking_lot", + "regex", "sharded-slab", "smallvec", "thread_local", + "tracing", "tracing-core", "tracing-log", ] +[[package]] +name = "tracing-test" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" +dependencies = [ + "quote", + "syn 2.0.72", +] + [[package]] name = "trust-dns-proto" version = "0.21.2" diff --git a/Cargo.toml b/Cargo.toml index 79a0f3ec8..e84686f12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -198,13 +198,16 @@ url = { workspace = true, features = ["serde"] } assert_cmd = { workspace = true } assert_fs = { workspace = true } assert-json-diff = { workspace = true } +dircpy = "0.3.18" duct = "0.13.7" git2 = { workspace = true, features = ["https"]} httpmock = { workspace = true } mime = "0.3.17" +portpicker = "0.1.1" predicates = { workspace = true } reqwest = { workspace = true, features = ["blocking", "native-tls-vendored"] } rstest = { workspace = true } serial_test = { workspace = true } speculoos = { workspace = true } tokio = { workspace = true } +tracing-test = "0.2.5" diff --git a/examples/supergraph-demo/pandas/package.json b/examples/supergraph-demo/pandas/package.json index 6ec5457e6..d93e30197 100644 --- a/examples/supergraph-demo/pandas/package.json +++ b/examples/supergraph-demo/pandas/package.json @@ -4,7 +4,7 @@ "description": "", "main": "pandas.mjs", "scripts": { - "start": "nodemon -e mjs,graphql", + "start": "nodemon -e mjs,graphql pandas.mjs", "dev": "cargo rover dev --name pandas --url http://localhost:4003", "clean": "rm -rf node_modules package-lock.json" }, diff --git a/examples/supergraph-demo/pandas/pandas.mjs b/examples/supergraph-demo/pandas/pandas.mjs index 7d5772714..7979e34e7 100644 --- a/examples/supergraph-demo/pandas/pandas.mjs +++ b/examples/supergraph-demo/pandas/pandas.mjs @@ -1,8 +1,8 @@ -import { buildSubgraphSchema } from '@apollo/subgraph'; -import { readFileSync } from 'fs'; -import { gql } from 'graphql-tag'; -import { ApolloServer } from '@apollo/server'; -import { startStandaloneServer } from '@apollo/server/standalone'; +import {buildSubgraphSchema} from '@apollo/subgraph'; +import {readFileSync} from 'fs'; +import {gql} from 'graphql-tag'; +import {ApolloServer} from '@apollo/server'; +import {startStandaloneServer} from '@apollo/server/standalone'; const typeDefs = gql(readFileSync('./pandas.graphql', { encoding: 'utf-8' }).toString()); @@ -26,5 +26,7 @@ const server = new ApolloServer({ schema: buildSubgraphSchema({ typeDefs, resolvers }) }); -const { url } = await startStandaloneServer(server, { listen: { port: 4003 } }); +const port = parseInt(process.argv[2], 10) || 4003 + +const { url } = await startStandaloneServer(server, { listen: { port: port } }); console.log(`🚀 Pandas server ready at ${url}`); \ No newline at end of file diff --git a/tests/e2e/dev.rs b/tests/e2e/dev.rs index 7f9f0cfa3..6887be51f 100644 --- a/tests/e2e/dev.rs +++ b/tests/e2e/dev.rs @@ -4,6 +4,7 @@ use std::time::Duration; use assert_cmd::prelude::CommandCargoExt; use mime::APPLICATION_JSON; +use portpicker::pick_unused_port; use reqwest::header::CONTENT_TYPE; use reqwest::Client; use rstest::*; @@ -11,19 +12,21 @@ use serde_json::{json, Value}; use speculoos::assert_that; use tempfile::TempDir; use tokio::time::timeout; +use tracing::error; +use tracing_test::traced_test; use crate::e2e::{ run_subgraphs_retail_supergraph, test_graphql_connection, GRAPHQL_TIMEOUT_DURATION, }; const ROVER_DEV_TIMEOUT: Duration = Duration::from_secs(45); -const ROUTER_PORT: u32 = 4123; #[fixture] #[once] fn run_rover_dev(#[from(run_subgraphs_retail_supergraph)] working_dir: &TempDir) -> String { let mut cmd = Command::cargo_bin("rover").expect("Could not find necessary binary"); - let router_url = format!("http://localhost:{}", ROUTER_PORT); + let port = pick_unused_port().expect("No ports free"); + let router_url = format!("http://localhost:{}", port); let client = Client::new(); cmd.args([ @@ -33,7 +36,7 @@ fn run_rover_dev(#[from(run_subgraphs_retail_supergraph)] working_dir: &TempDir) "--router-config", "router-config-dev.yaml", "--supergraph-port", - &format!("{}", ROUTER_PORT), + &format!("{}", port), "--elv2-license", "accept", ]); @@ -64,6 +67,7 @@ fn run_rover_dev(#[from(run_subgraphs_retail_supergraph)] working_dir: &TempDir) #[case::deprecated_introspection("query {__type(name:\"Review\"){ fields(includeDeprecated: true) { name isDeprecated deprecationReason } } }", json!({"data":{"__type":{"fields":[{"name":"id","isDeprecated":false,"deprecationReason":null},{"name":"body","isDeprecated":false,"deprecationReason":null},{"name":"author","isDeprecated":true,"deprecationReason":"Use the new `user` field"},{"name":"user","isDeprecated":false,"deprecationReason":null},{"name":"product","isDeprecated":false,"deprecationReason":null}]}}}))] #[ignore] #[tokio::test(flavor = "multi_thread")] +#[traced_test] async fn e2e_test_rover_dev( #[from(run_rover_dev)] router_url: &str, #[case] query: String, @@ -85,7 +89,7 @@ async fn e2e_test_rover_dev( break; } Err(e) => { - println!("Error: {}", e) + error!("Error: {}", e) } }; } diff --git a/tests/e2e/mod.rs b/tests/e2e/mod.rs index ea295b42b..4a7483171 100644 --- a/tests/e2e/mod.rs +++ b/tests/e2e/mod.rs @@ -1,24 +1,29 @@ use std::collections::HashMap; +use std::env; +use std::path::{Path, PathBuf}; use std::process::Command; use std::time::Duration; use anyhow::Error; -use camino::Utf8PathBuf; +use dircpy::CopyBuilder; use duct::cmd; use git2::Repository; +use portpicker::pick_unused_port; use reqwest::Client; use rstest::*; use serde::Deserialize; use serde_json::json; use tempfile::TempDir; use tokio::time::timeout; +use tracing::{info, warn}; mod dev; +mod subgraph; const GRAPHQL_TIMEOUT_DURATION: Duration = Duration::from_secs(30); #[derive(Debug, Deserialize)] -struct ReducedSuperGraphConfig { +struct ReducedSupergraphConfig { subgraphs: HashMap, } #[derive(Debug, Deserialize)] @@ -26,7 +31,7 @@ struct ReducedSubgraphConfig { routing_url: String, } -impl ReducedSuperGraphConfig { +impl ReducedSupergraphConfig { pub fn get_subgraph_urls(self) -> Vec { self.subgraphs .values() @@ -35,10 +40,12 @@ impl ReducedSuperGraphConfig { } } +const RETAIL_SUPERGRAPH_SCHEMA_NAME: &'static str = "supergraph-config-dev.yaml"; + #[fixture] #[once] fn run_subgraphs_retail_supergraph() -> TempDir { - println!("Cloning required git repository"); + info!("Cloning required git repository"); // Clone the Git Repository that's needed to a temporary folder let cloned_dir = TempDir::new().expect("Could not create temporary directory"); Repository::clone( @@ -47,7 +54,7 @@ fn run_subgraphs_retail_supergraph() -> TempDir { ) .expect("Could not clone supergraph repository"); // Jump into that temporary folder and run npm commands to kick off subgraphs - println!("Installing subgraph dependencies"); + info!("Installing subgraph dependencies"); cmd!("npm", "install") .dir(cloned_dir.path()) .run() @@ -56,17 +63,16 @@ fn run_subgraphs_retail_supergraph() -> TempDir { .dir(cloned_dir.path()) .run() .expect("Could not install nodemon"); - println!("Kicking off subgraphs"); + info!("Kicking off subgraphs"); let mut cmd = Command::new("npx"); cmd.env("NODE_ENV", "dev"); cmd.args(["nodemon", "index.js"]).current_dir(&cloned_dir); cmd.spawn().expect("Could not spawn subgraph process"); - println!("Finding subgraph URLs"); - let subgraph_urls = get_subgraph_urls( - Utf8PathBuf::from_path_buf(cloned_dir.path().join("supergraph-config-dev.yaml")) - .expect("Could not create path to config"), - ); - println!("Testing subgraph connectivity"); + info!("Finding subgraph URLs"); + let subgraph_urls = + get_supergraph_config(cloned_dir.path().join(RETAIL_SUPERGRAPH_SCHEMA_NAME)) + .get_subgraph_urls(); + info!("Testing subgraph connectivity"); for subgraph_url in subgraph_urls { tokio::task::block_in_place(|| { let client = Client::new(); @@ -83,6 +89,44 @@ fn run_subgraphs_retail_supergraph() -> TempDir { cloned_dir } +#[fixture] +async fn run_single_mutable_subgraph() -> (String, TempDir, String) { + // Create a copy of one of the subgraphs in a temporary subfolder + let target = TempDir::new().expect("Could not create temporary directory"); + let cargo_manifest_dir = + env::var("CARGO_MANIFEST_DIR").expect("Could not find CARGO_MANIFEST_DIR"); + CopyBuilder::new( + Path::new(&cargo_manifest_dir).join("examples/supergraph-demo/pandas"), + &target, + ) + .with_include_filter(".") + .run() + .expect("Could not perform copy"); + + info!("Installing subgraph dependencies"); + cmd!("npm", "run", "clean") + .dir(&target.path()) + .run() + .expect("Could not clean directory"); + cmd!("npm", "install") + .dir(&target.path()) + .run() + .expect("Could not install subgraph dependencies"); + info!("Kicking off subgraphs"); + let mut cmd = Command::new("npm"); + let port = pick_unused_port().expect("No free ports"); + let url = format!("http://localhost:{}", port); + cmd.args(["run", "start", "--", &port.to_string()]) + .current_dir(&target.path()); + cmd.spawn().expect("Could not spawn subgraph process"); + info!("Testing subgraph connectivity"); + let client = Client::new(); + test_graphql_connection(&client, &url, GRAPHQL_TIMEOUT_DURATION) + .await + .expect("Could not execute connectivity check"); + (url, target, String::from("pandas.graphql")) +} + async fn test_graphql_connection( client: &Client, url: &str, @@ -99,7 +143,7 @@ async fn test_graphql_connection( } } Err(e) => { - println!( + warn!( "Could not connect to GraphQL process on {}: {:} - Will retry", url, e ); @@ -109,14 +153,12 @@ async fn test_graphql_connection( } }) .await?; - println!("Established connection to {}", url); + info!("Established connection to {}", url); Ok(()) } -fn get_subgraph_urls(supergraph_yaml_path: Utf8PathBuf) -> Vec { +fn get_supergraph_config(supergraph_yaml_path: PathBuf) -> ReducedSupergraphConfig { let content = std::fs::read_to_string(supergraph_yaml_path) .expect("Could not read supergraph schema file"); - let sc_config: ReducedSuperGraphConfig = - serde_yaml::from_str(&content).expect("Could not parse supergraph schema file"); - sc_config.get_subgraph_urls() + serde_yaml::from_str(&content).expect("Could not parse supergraph schema file") } diff --git a/tests/e2e/subgraph/introspect.rs b/tests/e2e/subgraph/introspect.rs new file mode 100644 index 000000000..dff354008 --- /dev/null +++ b/tests/e2e/subgraph/introspect.rs @@ -0,0 +1,126 @@ +use std::fs::{read_to_string, OpenOptions}; +use std::io::{Seek, SeekFrom, Write}; +use std::process::Command; +use std::time::Duration; + +use assert_cmd::prelude::CommandCargoExt; +use rstest::rstest; +use serde_json::{json, Value}; +use speculoos::assert_that; +use tempfile::{Builder, TempDir}; +use tracing_test::traced_test; + +use crate::e2e::{ + get_supergraph_config, run_single_mutable_subgraph, run_subgraphs_retail_supergraph, + RETAIL_SUPERGRAPH_SCHEMA_NAME, +}; + +#[rstest] +#[ignore] +#[tokio::test(flavor = "multi_thread")] +#[traced_test] +async fn e2e_test_rover_subgraph_introspect( + #[from(run_subgraphs_retail_supergraph)] supergraph_dir: &TempDir, +) { + // Extract the inventory URL from the supergraph.yaml + let supergraph_config_path = supergraph_dir.path().join(RETAIL_SUPERGRAPH_SCHEMA_NAME); + let url = get_supergraph_config(supergraph_config_path) + .subgraphs + .get("inventory") + .unwrap() + .routing_url + .clone(); + + // Set up the command to output + let out_file = Builder::new() + .suffix(".json") + .tempfile() + .expect("Could not create output file"); + let mut cmd = Command::cargo_bin("rover").expect("Could not find necessary binary"); + cmd.args([ + "subgraph", + "introspect", + &url, + "--format", + "json", + "--output", + out_file.path().to_str().unwrap(), + ]); + cmd.output().expect("Could not run command"); + + // Slurp the output and then compare it to the canonical one + let actual_value: Value = serde_json::from_reader(out_file.as_file()).unwrap(); + let expected_value = json!({ + "data":{ + "introspection_response":"extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ntype Variant\n @key(fields: \"id\")\n{\n id: ID!\n\n \"\"\"Checks the warehouse API for inventory information.\"\"\"\n inventory: Inventory\n}\n\n\"\"\"Inventory details about a specific Variant\"\"\"\ntype Inventory {\n \"\"\"Returns true if the inventory count is greater than 0\"\"\"\n inStock: Boolean!\n\n \"\"\"The raw count of not purchased items in the warehouse\"\"\"\n inventory: Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Variant\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}", + "success":true + }, + "error":null, + "json_version":"1" + }); + + assert_that!(actual_value).is_equal_to(expected_value); +} + +#[rstest] +#[ignore] +#[tokio::test(flavor = "multi_thread")] +#[traced_test] +async fn e2e_test_rover_subgraph_introspect_watch( + #[from(run_single_mutable_subgraph)] + #[future] + subgraph_details: (String, TempDir, String), +) { + // Set up the command to output the original file + let mut out_file = Builder::new() + .suffix(".json") + .tempfile() + .expect("Could not create output file"); + let (url, subgraph_dir, schema_name) = subgraph_details.await; + let mut cmd = Command::cargo_bin("rover").expect("Could not find necessary binary"); + cmd.args([ + "subgraph", + "introspect", + &url, + "--watch", + "--format", + "json", + "--output", + out_file.path().to_str().unwrap(), + ]); + let mut child = cmd.spawn().expect("Could not run command"); + tokio::time::sleep(Duration::from_secs(1)).await; + // Store the result + let original_value: Value = serde_json::from_reader(out_file.as_file()).unwrap(); + // Make a change to the schema + let schema_path = subgraph_dir.into_path().join(schema_name); + let schema = read_to_string(&schema_path).expect("Could not read schema file"); + let new_schema = schema.replace("allPandas", "getMeAllThePandas"); + let mut schema_file = OpenOptions::new() + .write(true) + .truncate(true) + .open(schema_path) + .expect("Cannot open schema file"); + schema_file + .write(new_schema.as_bytes()) + .expect("Could not update schema"); + tokio::time::sleep(Duration::from_secs(2)).await; + // Get the new result + out_file + .seek(SeekFrom::Start(0)) + .expect("Could not rewind file"); + let new_value: Value = serde_json::from_reader(out_file.as_file()).unwrap(); + // Ensure that the two are different + assert_that!(new_value).is_not_equal_to(original_value); + // Ensure the changed schema is what we expect it to be + let expected_value = json!({ + "data":{ + "introspection_response":"directive @tag(name: String!) repeatable on FIELD_DEFINITION\n\ndirective @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @extends on OBJECT | INTERFACE\n\ntype Query {\n getMeAllThePandas: [Panda]\n panda(name: ID!): Panda\n _service: _Service!\n}\n\ntype Panda {\n name: ID!\n favoriteFood: String @tag(name: \"nom-nom-nom\")\n}\n\nscalar _FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}", + "success":true + }, + "error":null, + "json_version":"1" + }); + assert_that!(new_value).is_equal_to(expected_value); + child.kill().unwrap(); +} diff --git a/tests/e2e/subgraph/mod.rs b/tests/e2e/subgraph/mod.rs new file mode 100644 index 000000000..a8495a506 --- /dev/null +++ b/tests/e2e/subgraph/mod.rs @@ -0,0 +1 @@ +mod introspect;