diff --git a/Cargo.lock b/Cargo.lock index 95c13b92..07ec70a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1359,10 +1359,13 @@ version = "0.1.0" dependencies = [ "anyhow", "insta", + "ndc-models", + "ndc-test-helpers", "reqwest 0.12.4", "serde", "serde_json", "tokio", + "url", ] [[package]] diff --git a/arion-compose/integration-tests.nix b/arion-compose/integration-tests.nix index 7f49ebf7..1eb25fd1 100644 --- a/arion-compose/integration-tests.nix +++ b/arion-compose/integration-tests.nix @@ -14,6 +14,7 @@ let map-host-ports = false; }; + connector-port = "7130"; engine-port = "7100"; in { @@ -22,6 +23,7 @@ in services = services // { test = import ./services/integration-tests.nix { inherit pkgs; + connector-url = "http://connector:${connector-port}/"; engine-graphql-url = "http://engine:${engine-port}/graphql"; service.depends_on = { connector.condition = "service_healthy"; diff --git a/arion-compose/services/integration-tests.nix b/arion-compose/services/integration-tests.nix index 1cb9b737..fa99283a 100644 --- a/arion-compose/services/integration-tests.nix +++ b/arion-compose/services/integration-tests.nix @@ -1,4 +1,5 @@ { pkgs +, connector-url , engine-graphql-url , service ? { } # additional options to customize this service configuration }: @@ -12,6 +13,7 @@ let "${pkgs.pkgsCross.linux.integration-tests}/bin/integration-tests" ]; environment = { + CONNECTOR_URL = connector-url; ENGINE_GRAPHQL_URL = engine-graphql-url; INSTA_WORKSPACE_ROOT = repo-source-mount-point; MONGODB_IMAGE = builtins.getEnv "MONGODB_IMAGE"; diff --git a/crates/integration-tests/Cargo.toml b/crates/integration-tests/Cargo.toml index 1d584a21..f8e9a380 100644 --- a/crates/integration-tests/Cargo.toml +++ b/crates/integration-tests/Cargo.toml @@ -7,9 +7,13 @@ edition = "2021" integration = [] [dependencies] +ndc-models = { workspace = true } +ndc-test-helpers = { path = "../ndc-test-helpers" } + anyhow = "1" insta = { version = "^1.38", features = ["yaml"] } reqwest = { version = "^0.12.4", features = ["json"] } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "^1.37.0", features = ["full"] } +url = "^2.5.0" diff --git a/crates/integration-tests/src/connector.rs b/crates/integration-tests/src/connector.rs new file mode 100644 index 00000000..b7d6807e --- /dev/null +++ b/crates/integration-tests/src/connector.rs @@ -0,0 +1,70 @@ +use ndc_models::{ErrorResponse, QueryRequest, QueryResponse}; +use ndc_test_helpers::QueryRequestBuilder; +use reqwest::Client; +use serde::{Deserialize, Serialize}; + +use crate::get_connector_url; + +#[derive(Clone, Debug, Serialize)] +#[serde(transparent)] +pub struct ConnectorQueryRequest { + query_request: QueryRequest, +} + +impl ConnectorQueryRequest { + pub async fn run(&self) -> anyhow::Result { + let connector_url = get_connector_url()?; + let client = Client::new(); + let response = client + .post(connector_url.join("query")?) + .header("x-hasura-role", "admin") + .json(self) + .send() + .await?; + let query_response = response.json().await?; + Ok(query_response) + } +} + +impl From for ConnectorQueryRequest { + fn from(query_request: QueryRequest) -> Self { + ConnectorQueryRequest { query_request } + } +} + +impl From for ConnectorQueryRequest { + fn from(builder: QueryRequestBuilder) -> Self { + let request: QueryRequest = builder.into(); + request.into() + } +} + +pub async fn run_connector_query( + request: impl Into, +) -> anyhow::Result { + let request: ConnectorQueryRequest = request.into(); + request.run().await +} + +// Using a custom Result-like enum because we need untagged deserialization +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum ConnectorQueryResponse { + Ok(QueryResponse), + Err(ErrorResponse), +} + +impl ConnectorQueryResponse { + pub fn into_result(self) -> Result { + match self { + ConnectorQueryResponse::Ok(resp) => Ok(resp), + ConnectorQueryResponse::Err(err) => Err(err), + } + } +} + +impl From for Result { + fn from(value: ConnectorQueryResponse) -> Self { + value.into_result() + } +} diff --git a/crates/integration-tests/src/graphql.rs b/crates/integration-tests/src/graphql.rs new file mode 100644 index 00000000..d027b056 --- /dev/null +++ b/crates/integration-tests/src/graphql.rs @@ -0,0 +1,70 @@ +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use serde_json::{to_value, Value}; + +use crate::get_graphql_url; + +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GraphQLRequest { + query: String, + #[serde(skip_serializing_if = "Option::is_none")] + operation_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + variables: Option, +} + +impl GraphQLRequest { + pub fn new(query: String) -> Self { + GraphQLRequest { + query, + operation_name: Default::default(), + variables: Default::default(), + } + } + + pub fn operation_name(mut self, name: String) -> Self { + self.operation_name = Some(name); + self + } + + pub fn variables(mut self, vars: impl Serialize) -> Self { + self.variables = Some(to_value(&vars).unwrap()); + self + } + + pub async fn run(&self) -> anyhow::Result { + let graphql_url = get_graphql_url()?; + let client = Client::new(); + let response = client + .post(graphql_url) + .header("x-hasura-role", "admin") + .json(self) + .send() + .await?; + let graphql_response = response.json().await?; + Ok(graphql_response) + } +} + +impl From for GraphQLRequest { + fn from(query: String) -> Self { + GraphQLRequest::new(query) + } +} + +impl From<&str> for GraphQLRequest { + fn from(query: &str) -> Self { + GraphQLRequest::new(query.to_owned()) + } +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct GraphQLResponse { + pub data: Value, + pub errors: Option>, +} + +pub fn graphql_query(q: impl ToString) -> GraphQLRequest { + q.to_string().into() +} diff --git a/crates/integration-tests/src/lib.rs b/crates/integration-tests/src/lib.rs index 46038622..9044753e 100644 --- a/crates/integration-tests/src/lib.rs +++ b/crates/integration-tests/src/lib.rs @@ -6,78 +6,24 @@ #[cfg(all(test, feature = "integration"))] mod tests; +mod connector; +mod graphql; + use std::env; use anyhow::anyhow; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use serde_json::{to_value, Value}; - -const ENGINE_GRAPHQL_URL: &str = "ENGINE_GRAPHQL_URL"; - -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct GraphQLRequest { - query: String, - #[serde(skip_serializing_if = "Option::is_none")] - operation_name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - variables: Option, -} - -impl GraphQLRequest { - pub fn new(query: String) -> Self { - GraphQLRequest { - query, - operation_name: Default::default(), - variables: Default::default(), - } - } +use url::Url; - pub fn operation_name(mut self, name: String) -> Self { - self.operation_name = Some(name); - self - } +pub use self::connector::{run_connector_query, ConnectorQueryRequest}; +pub use self::graphql::{graphql_query, GraphQLRequest, GraphQLResponse}; - pub fn variables(mut self, vars: impl Serialize) -> Self { - self.variables = Some(to_value(&vars).unwrap()); - self - } - - pub async fn run(&self) -> anyhow::Result { - let graphql_url = get_graphql_url()?; - let client = Client::new(); - let response = client - .post(graphql_url) - .header("x-hasura-role", "admin") - .json(self) - .send() - .await?; - let graphql_response = response.json().await?; - Ok(graphql_response) - } -} - -impl From for GraphQLRequest { - fn from(query: String) -> Self { - GraphQLRequest::new(query) - } -} - -impl From<&str> for GraphQLRequest { - fn from(query: &str) -> Self { - GraphQLRequest::new(query.to_owned()) - } -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GraphQLResponse { - data: Value, - errors: Option>, -} +const CONNECTOR_URL: &str = "CONNECTOR_URL"; +const ENGINE_GRAPHQL_URL: &str = "ENGINE_GRAPHQL_URL"; -pub fn query(q: impl ToString) -> GraphQLRequest { - q.to_string().into() +fn get_connector_url() -> anyhow::Result { + let input = env::var(CONNECTOR_URL).map_err(|_| anyhow!("please set {CONNECTOR_URL} to the the base URL of a running MongoDB connector instance"))?; + let url = Url::parse(&input)?; + Ok(url) } fn get_graphql_url() -> anyhow::Result { diff --git a/crates/integration-tests/src/tests/basic.rs b/crates/integration-tests/src/tests/basic.rs index 8b0d3920..984614bb 100644 --- a/crates/integration-tests/src/tests/basic.rs +++ b/crates/integration-tests/src/tests/basic.rs @@ -1,10 +1,10 @@ -use crate::query; +use crate::graphql_query; use insta::assert_yaml_snapshot; #[tokio::test] async fn runs_a_query() -> anyhow::Result<()> { assert_yaml_snapshot!( - query( + graphql_query( r#" query Movies { movies(limit: 10, order_by: { id: Asc }) { diff --git a/crates/integration-tests/src/tests/local_relationship.rs b/crates/integration-tests/src/tests/local_relationship.rs index 151752c0..842d83e5 100644 --- a/crates/integration-tests/src/tests/local_relationship.rs +++ b/crates/integration-tests/src/tests/local_relationship.rs @@ -1,11 +1,11 @@ -use crate::query; +use crate::graphql_query; use insta::assert_yaml_snapshot; use serde_json::json; #[tokio::test] async fn joins_local_relationships() -> anyhow::Result<()> { assert_yaml_snapshot!( - query( + graphql_query( r#" query { movies(limit: 2, order_by: {title: Asc}, where: {title: {_iregex: "Rear"}}) { diff --git a/crates/integration-tests/src/tests/native_procedure.rs b/crates/integration-tests/src/tests/native_procedure.rs index 15cdfef8..c17a1da5 100644 --- a/crates/integration-tests/src/tests/native_procedure.rs +++ b/crates/integration-tests/src/tests/native_procedure.rs @@ -1,4 +1,4 @@ -use crate::{query, GraphQLResponse}; +use crate::{graphql_query, GraphQLResponse}; use insta::assert_yaml_snapshot; use serde_json::json; @@ -15,11 +15,11 @@ async fn updates_with_native_procedure() -> anyhow::Result<()> { } "#; - let res1 = query(mutation) + let res1 = graphql_query(mutation) .variables(json!({ "id": id_1, "name": "Regina Spektor" })) .run() .await?; - query(mutation) + graphql_query(mutation) .variables(json!({ "id": id_2, "name": "Ok Go" })) .run() .await?; @@ -38,7 +38,7 @@ async fn updates_with_native_procedure() -> anyhow::Result<()> { ); assert_yaml_snapshot!( - query( + graphql_query( r#" query { artist1: artist(where: { artistId: { _eq: 5471 } }, limit: 1) { diff --git a/crates/integration-tests/src/tests/native_query.rs b/crates/integration-tests/src/tests/native_query.rs index 53d7327b..1e929ee5 100644 --- a/crates/integration-tests/src/tests/native_query.rs +++ b/crates/integration-tests/src/tests/native_query.rs @@ -1,4 +1,4 @@ -use crate::query; +use crate::graphql_query; use insta::assert_yaml_snapshot; #[tokio::test] @@ -15,7 +15,7 @@ async fn runs_native_query_with_function_representation() -> anyhow::Result<()> } assert_yaml_snapshot!( - query( + graphql_query( r#" query NativeQuery { hello(name: "world") @@ -31,7 +31,7 @@ async fn runs_native_query_with_function_representation() -> anyhow::Result<()> #[tokio::test] async fn runs_native_query_with_collection_representation() -> anyhow::Result<()> { assert_yaml_snapshot!( - query( + graphql_query( r#" query { title_word_frequencies( diff --git a/crates/integration-tests/src/tests/remote_relationship.rs b/crates/integration-tests/src/tests/remote_relationship.rs index f9d4b52d..9864f860 100644 --- a/crates/integration-tests/src/tests/remote_relationship.rs +++ b/crates/integration-tests/src/tests/remote_relationship.rs @@ -1,11 +1,12 @@ -use crate::query; +use crate::{graphql_query, run_connector_query}; use insta::assert_yaml_snapshot; +use ndc_test_helpers::{equal, field, query, query_request, target, variable}; use serde_json::json; #[tokio::test] async fn provides_source_and_target_for_remote_relationship() -> anyhow::Result<()> { assert_yaml_snapshot!( - query( + graphql_query( r#" query AlbumMovies($limit: Int, $movies_limit: Int) { album(limit: $limit, order_by: { title: Asc }) { @@ -25,3 +26,21 @@ async fn provides_source_and_target_for_remote_relationship() -> anyhow::Result< ); Ok(()) } + +#[tokio::test] +async fn handles_request_with_single_variable_set() -> anyhow::Result<()> { + assert_yaml_snapshot!( + run_connector_query( + query_request() + .collection("movies") + .variables([vec![("id", json!("573a1390f29313caabcd50e5"))]]) + .query( + query() + .predicate(equal(target!("_id"), variable!(id))) + .fields([field!("title")]), + ), + ) + .await? + ); + Ok(()) +} diff --git a/crates/integration-tests/src/tests/snapshots/integration_tests__tests__basic__runs_a_query.snap b/crates/integration-tests/src/tests/snapshots/integration_tests__tests__basic__runs_a_query.snap index a4fec50d..b90d3938 100644 --- a/crates/integration-tests/src/tests/snapshots/integration_tests__tests__basic__runs_a_query.snap +++ b/crates/integration-tests/src/tests/snapshots/integration_tests__tests__basic__runs_a_query.snap @@ -1,57 +1,57 @@ --- source: crates/integration-tests/src/tests/basic.rs -expression: "query(r#\"\n query Movies {\n movies(limit: 10, order_by: { id: Asc }) {\n title\n imdb {\n rating\n votes\n }\n }\n }\n \"#).run().await?" +expression: "graphql_query(r#\"\n query Movies {\n movies(limit: 10, order_by: { id: Asc }) {\n title\n imdb {\n rating\n votes\n }\n }\n }\n \"#).run().await?" --- data: movies: - - imdb: + - title: Blacksmith Scene + imdb: rating: $numberDouble: "6.2" votes: 1189 - title: Blacksmith Scene - - imdb: + - title: The Great Train Robbery + imdb: rating: $numberDouble: "7.4" votes: 9847 - title: The Great Train Robbery - - imdb: + - title: The Land Beyond the Sunset + imdb: rating: $numberDouble: "7.1" votes: 448 - title: The Land Beyond the Sunset - - imdb: + - title: A Corner in Wheat + imdb: rating: $numberDouble: "6.6" votes: 1375 - title: A Corner in Wheat - - imdb: + - title: "Winsor McCay, the Famous Cartoonist of the N.Y. Herald and His Moving Comics" + imdb: rating: $numberDouble: "7.3" votes: 1034 - title: "Winsor McCay, the Famous Cartoonist of the N.Y. Herald and His Moving Comics" - - imdb: + - title: Traffic in Souls + imdb: rating: $numberInt: "6" votes: 371 - title: Traffic in Souls - - imdb: + - title: Gertie the Dinosaur + imdb: rating: $numberDouble: "7.3" votes: 1837 - title: Gertie the Dinosaur - - imdb: + - title: In the Land of the Head Hunters + imdb: rating: $numberDouble: "5.8" votes: 223 - title: In the Land of the Head Hunters - - imdb: + - title: The Perils of Pauline + imdb: rating: $numberDouble: "7.6" votes: 744 - title: The Perils of Pauline - - imdb: + - title: The Birth of a Nation + imdb: rating: $numberDouble: "6.8" votes: 15715 - title: The Birth of a Nation errors: ~ diff --git a/crates/integration-tests/src/tests/snapshots/integration_tests__tests__local_relationship__joins_local_relationships.snap b/crates/integration-tests/src/tests/snapshots/integration_tests__tests__local_relationship__joins_local_relationships.snap index ac32decb..1af2a2bf 100644 --- a/crates/integration-tests/src/tests/snapshots/integration_tests__tests__local_relationship__joins_local_relationships.snap +++ b/crates/integration-tests/src/tests/snapshots/integration_tests__tests__local_relationship__joins_local_relationships.snap @@ -1,65 +1,65 @@ --- source: crates/integration-tests/src/tests/local_relationship.rs -expression: "query(r#\"\n query {\n movies(limit: 2, order_by: {title: Asc}, where: {title: {_iregex: \"Rear\"}}) {\n id\n title\n comments(limit: 2, order_by: {id: Asc}) {\n email\n text\n movie {\n id\n title\n }\n user {\n email\n comments(limit: 2, order_by: {id: Asc}) {\n email\n text\n user {\n email\n comments(limit: 2, order_by: {id: Asc}) {\n id\n email\n }\n }\n }\n }\n }\n }\n }\n \"#).variables(json!({\n \"limit\": 11, \"movies_limit\": 2\n })).run().await?" +expression: "graphql_query(r#\"\n query {\n movies(limit: 2, order_by: {title: Asc}, where: {title: {_iregex: \"Rear\"}}) {\n id\n title\n comments(limit: 2, order_by: {id: Asc}) {\n email\n text\n movie {\n id\n title\n }\n user {\n email\n comments(limit: 2, order_by: {id: Asc}) {\n email\n text\n user {\n email\n comments(limit: 2, order_by: {id: Asc}) {\n id\n email\n }\n }\n }\n }\n }\n }\n }\n \"#).variables(json!({\n \"limit\": 11, \"movies_limit\": 2\n })).run().await?" --- data: movies: - - comments: + - id: 573a1398f29313caabceb0b1 + title: A Night in the Life of Jimmy Reardon + comments: - email: iain_glen@gameofthron.es + text: Debitis tempore cum natus quaerat dolores quibusdam perferendis. Pariatur aspernatur officia libero quod pariatur nobis neque. Maiores non ipsam iste repellendus distinctio praesentium iure. movie: id: 573a1398f29313caabceb0b1 title: A Night in the Life of Jimmy Reardon - text: Debitis tempore cum natus quaerat dolores quibusdam perferendis. Pariatur aspernatur officia libero quod pariatur nobis neque. Maiores non ipsam iste repellendus distinctio praesentium iure. user: + email: iain_glen@gameofthron.es comments: - email: iain_glen@gameofthron.es text: Minus sequi incidunt cum magnam. Quam voluptatum vitae ab voluptatum cum. Autem perferendis nisi nulla dolores aut recusandae. user: - comments: - - email: iain_glen@gameofthron.es - id: 5a9427648b0beebeb69579f3 - - email: iain_glen@gameofthron.es - id: 5a9427648b0beebeb6957b0f email: iain_glen@gameofthron.es + comments: + - id: 5a9427648b0beebeb69579f3 + email: iain_glen@gameofthron.es + - id: 5a9427648b0beebeb6957b0f + email: iain_glen@gameofthron.es - email: iain_glen@gameofthron.es text: Impedit consectetur ex cupiditate enim. Placeat assumenda reiciendis iste neque similique nesciunt aperiam. user: - comments: - - email: iain_glen@gameofthron.es - id: 5a9427648b0beebeb69579f3 - - email: iain_glen@gameofthron.es - id: 5a9427648b0beebeb6957b0f email: iain_glen@gameofthron.es - email: iain_glen@gameofthron.es - id: 573a1398f29313caabceb0b1 - title: A Night in the Life of Jimmy Reardon - - comments: + comments: + - id: 5a9427648b0beebeb69579f3 + email: iain_glen@gameofthron.es + - id: 5a9427648b0beebeb6957b0f + email: iain_glen@gameofthron.es + - id: 573a1394f29313caabcdfa00 + title: Rear Window + comments: - email: owen_teale@gameofthron.es + text: Nobis corporis rem hic ipsa cum impedit. Esse nihil cum est minima ducimus temporibus minima. Sed reprehenderit tempore similique nam. Ipsam nesciunt veniam aut amet ut. movie: id: 573a1394f29313caabcdfa00 title: Rear Window - text: Nobis corporis rem hic ipsa cum impedit. Esse nihil cum est minima ducimus temporibus minima. Sed reprehenderit tempore similique nam. Ipsam nesciunt veniam aut amet ut. user: + email: owen_teale@gameofthron.es comments: - email: owen_teale@gameofthron.es text: A ut dolor illum deleniti repellendus. Iste fugit in quas minus nobis sunt rem. Animi possimus dolor alias natus consequatur saepe. Nihil quam magni aspernatur nisi. user: - comments: - - email: owen_teale@gameofthron.es - id: 5a9427648b0beebeb6957b44 - - email: owen_teale@gameofthron.es - id: 5a9427648b0beebeb6957cf6 email: owen_teale@gameofthron.es + comments: + - id: 5a9427648b0beebeb6957b44 + email: owen_teale@gameofthron.es + - id: 5a9427648b0beebeb6957cf6 + email: owen_teale@gameofthron.es - email: owen_teale@gameofthron.es text: Repudiandae repellat quia officiis. Quidem voluptatum vel id itaque et. Corrupti corporis magni voluptas quae itaque fugiat quae. user: - comments: - - email: owen_teale@gameofthron.es - id: 5a9427648b0beebeb6957b44 - - email: owen_teale@gameofthron.es - id: 5a9427648b0beebeb6957cf6 email: owen_teale@gameofthron.es - email: owen_teale@gameofthron.es - id: 573a1394f29313caabcdfa00 - title: Rear Window + comments: + - id: 5a9427648b0beebeb6957b44 + email: owen_teale@gameofthron.es + - id: 5a9427648b0beebeb6957cf6 + email: owen_teale@gameofthron.es errors: ~ diff --git a/crates/integration-tests/src/tests/snapshots/integration_tests__tests__native_query__runs_native_query_with_collection_representation.snap b/crates/integration-tests/src/tests/snapshots/integration_tests__tests__native_query__runs_native_query_with_collection_representation.snap index c044a25f..c2d65132 100644 --- a/crates/integration-tests/src/tests/snapshots/integration_tests__tests__native_query__runs_native_query_with_collection_representation.snap +++ b/crates/integration-tests/src/tests/snapshots/integration_tests__tests__native_query__runs_native_query_with_collection_representation.snap @@ -1,57 +1,57 @@ --- source: crates/integration-tests/src/tests/native_query.rs -expression: "query(r#\"\n query {\n title_word_frequencies(\n where: {count: {_eq: 2}}\n order_by: {word: Asc}\n offset: 100\n limit: 25\n ) {\n word\n count\n }\n }\n \"#).run().await?" +expression: "graphql_query(r#\"\n query {\n title_word_frequencies(\n where: {count: {_eq: 2}}\n order_by: {word: Asc}\n offset: 100\n limit: 25\n ) {\n word\n count\n }\n }\n \"#).run().await?" --- data: title_word_frequencies: - - count: 2 - word: Amish - - count: 2 - word: Amor? - - count: 2 - word: Anara - - count: 2 - word: Anarchy - - count: 2 - word: Anastasia - - count: 2 - word: Anchorman - - count: 2 - word: Andre - - count: 2 - word: Andrei - - count: 2 - word: Andromeda - - count: 2 - word: Andrè - - count: 2 - word: Angela - - count: 2 - word: Angelica - - count: 2 - word: "Angels'" - - count: 2 - word: "Angels:" - - count: 2 - word: Angst - - count: 2 - word: Animation - - count: 2 - word: Annabelle - - count: 2 - word: Anonyma - - count: 2 - word: Anonymous - - count: 2 - word: Answer - - count: 2 - word: Ant - - count: 2 - word: Antarctic - - count: 2 - word: Antoinette - - count: 2 - word: Anybody - - count: 2 - word: Anywhere + - word: Amish + count: 2 + - word: Amor? + count: 2 + - word: Anara + count: 2 + - word: Anarchy + count: 2 + - word: Anastasia + count: 2 + - word: Anchorman + count: 2 + - word: Andre + count: 2 + - word: Andrei + count: 2 + - word: Andromeda + count: 2 + - word: Andrè + count: 2 + - word: Angela + count: 2 + - word: Angelica + count: 2 + - word: "Angels'" + count: 2 + - word: "Angels:" + count: 2 + - word: Angst + count: 2 + - word: Animation + count: 2 + - word: Annabelle + count: 2 + - word: Anonyma + count: 2 + - word: Anonymous + count: 2 + - word: Answer + count: 2 + - word: Ant + count: 2 + - word: Antarctic + count: 2 + - word: Antoinette + count: 2 + - word: Anybody + count: 2 + - word: Anywhere + count: 2 errors: ~ diff --git a/crates/integration-tests/src/tests/snapshots/integration_tests__tests__remote_relationship__handles_request_with_single_variable_set.snap b/crates/integration-tests/src/tests/snapshots/integration_tests__tests__remote_relationship__handles_request_with_single_variable_set.snap new file mode 100644 index 00000000..83a4bd06 --- /dev/null +++ b/crates/integration-tests/src/tests/snapshots/integration_tests__tests__remote_relationship__handles_request_with_single_variable_set.snap @@ -0,0 +1,6 @@ +--- +source: crates/integration-tests/src/tests/remote_relationship.rs +expression: "{\n run_connector_query(query_request().collection(\"movies\").variables([vec![(\"id\",\n json!(\"573a1390f29313caabcd50e5\"))]]).query(query().predicate(equal(target!(\"_id\"),\n variable!(id))).fields([field!(\"title\")]))).await?\n}" +--- +- rows: + - title: Gertie the Dinosaur diff --git a/crates/integration-tests/src/tests/snapshots/integration_tests__tests__remote_relationship__provides_source_and_target_for_remote_relationship.snap b/crates/integration-tests/src/tests/snapshots/integration_tests__tests__remote_relationship__provides_source_and_target_for_remote_relationship.snap index d13fc95d..acb32cbe 100644 --- a/crates/integration-tests/src/tests/snapshots/integration_tests__tests__remote_relationship__provides_source_and_target_for_remote_relationship.snap +++ b/crates/integration-tests/src/tests/snapshots/integration_tests__tests__remote_relationship__provides_source_and_target_for_remote_relationship.snap @@ -1,74 +1,74 @@ --- source: crates/integration-tests/src/tests/remote_relationship.rs -expression: "query(r#\"\n query AlbumMovies($limit: Int, $movies_limit: Int) {\n album(limit: $limit, order_by: { title: Asc }) {\n title\n movies(limit: $movies_limit, order_by: { title: Asc }) {\n title\n runtime\n }\n albumId\n }\n }\n \"#).variables(json!({\n \"limit\": 11, \"movies_limit\": 2\n })).run().await?" +expression: "graphql_query(r#\"\n query AlbumMovies($limit: Int, $movies_limit: Int) {\n album(limit: $limit, order_by: { title: Asc }) {\n title\n movies(limit: $movies_limit, order_by: { title: Asc }) {\n title\n runtime\n }\n albumId\n }\n }\n \"#).variables(json!({\n \"limit\": 11, \"movies_limit\": 2\n })).run().await?" --- data: album: - - albumId: 156 + - title: "...And Justice For All" movies: - - runtime: 156 - title: "20th Century Boys 3: Redemption" - - runtime: 156 - title: A Majority of One - title: "...And Justice For All" - - albumId: 257 + - title: "20th Century Boys 3: Redemption" + runtime: 156 + - title: A Majority of One + runtime: 156 + albumId: 156 + - title: "20th Century Masters - The Millennium Collection: The Best of Scorpions" movies: - - runtime: 257 - title: Storm of the Century - title: "20th Century Masters - The Millennium Collection: The Best of Scorpions" - - albumId: 296 + - title: Storm of the Century + runtime: 257 + albumId: 257 + - title: "A Copland Celebration, Vol. I" movies: [] - title: "A Copland Celebration, Vol. I" - - albumId: 94 + albumId: 296 + - title: A Matter of Life and Death movies: - - runtime: 94 - title: 100 Girls - - runtime: 94 - title: 12 and Holding - title: A Matter of Life and Death - - albumId: 95 + - title: 100 Girls + runtime: 94 + - title: 12 and Holding + runtime: 94 + albumId: 94 + - title: A Real Dead One movies: - - runtime: 95 - title: (500) Days of Summer - - runtime: 95 - title: "1" - title: A Real Dead One - - albumId: 96 + - title: (500) Days of Summer + runtime: 95 + - title: "1" + runtime: 95 + albumId: 95 + - title: A Real Live One movies: - - runtime: 96 - title: "'Doc'" - - runtime: 96 - title: "'night, Mother" - title: A Real Live One - - albumId: 285 + - title: "'Doc'" + runtime: 96 + - title: "'night, Mother" + runtime: 96 + albumId: 96 + - title: A Soprano Inspired movies: [] - title: A Soprano Inspired - - albumId: 139 + albumId: 285 + - title: A TempestadeTempestade Ou O Livro Dos Dias movies: - - runtime: 139 - title: "20th Century Boys 2: The Last Hope" - - runtime: 139 - title: 42 Up - title: A TempestadeTempestade Ou O Livro Dos Dias - - albumId: 203 + - title: "20th Century Boys 2: The Last Hope" + runtime: 139 + - title: 42 Up + runtime: 139 + albumId: 139 + - title: A-Sides movies: - - runtime: 203 - title: Michael the Brave - - runtime: 203 - title: Michael the Brave - title: A-Sides - - albumId: 160 + - title: Michael the Brave + runtime: 203 + - title: Michael the Brave + runtime: 203 + albumId: 203 + - title: Ace Of Spades movies: - - runtime: 160 - title: "2001: A Space Odyssey" - - runtime: 160 - title: 7 Aum Arivu - title: Ace Of Spades - - albumId: 232 + - title: "2001: A Space Odyssey" + runtime: 160 + - title: 7 Aum Arivu + runtime: 160 + albumId: 160 + - title: Achtung Baby movies: - - runtime: 232 - title: Bratya Karamazovy - - runtime: 232 - title: Gormenghast - title: Achtung Baby + - title: Bratya Karamazovy + runtime: 232 + - title: Gormenghast + runtime: 232 + albumId: 232 errors: ~ diff --git a/crates/ndc-test-helpers/src/aggregates.rs b/crates/ndc-test-helpers/src/aggregates.rs index 6f0538ca..bfa83d41 100644 --- a/crates/ndc-test-helpers/src/aggregates.rs +++ b/crates/ndc-test-helpers/src/aggregates.rs @@ -3,7 +3,7 @@ macro_rules! column_aggregate { ($name:literal => $column:literal, $function:literal) => { ( $name, - ndc_sdk::models::Aggregate::SingleColumn { + $crate::ndc_models::Aggregate::SingleColumn { column: $column.to_owned(), function: $function.to_owned() }, @@ -16,7 +16,7 @@ macro_rules! star_count_aggregate { ($name:literal) => { ( $name, - ndc_sdk::models::Aggregate::StarCount {}, + $crate::ndc_models::Aggregate::StarCount {}, ) }; } @@ -26,7 +26,7 @@ macro_rules! column_count_aggregate { ($name:literal => $column:literal, distinct:$distinct:literal) => { ( $name, - ndc_sdk::models::Aggregate::ColumnCount { + $crate::ndc_models::Aggregate::ColumnCount { column: $column.to_owned(), distinct: $distinct.to_owned(), }, diff --git a/crates/ndc-test-helpers/src/comparison_target.rs b/crates/ndc-test-helpers/src/comparison_target.rs index 41f16ba7..7838365a 100644 --- a/crates/ndc-test-helpers/src/comparison_target.rs +++ b/crates/ndc-test-helpers/src/comparison_target.rs @@ -1,13 +1,13 @@ #[macro_export()] macro_rules! target { ($column:literal) => { - ndc_sdk::models::ComparisonTarget::Column { + $crate::ndc_models::ComparisonTarget::Column { name: $column.to_owned(), path: vec![], } }; ($column:literal, $path:expr $(,)?) => { - ndc_sdk::models::ComparisonTarget::Column { + $crate::ndc_models::ComparisonTarget::Column { name: $column.to_owned(), path: $path.into_iter().map(|x| x.into()).collect(), } diff --git a/crates/ndc-test-helpers/src/comparison_value.rs b/crates/ndc-test-helpers/src/comparison_value.rs index ee83b3ca..0d233bb5 100644 --- a/crates/ndc-test-helpers/src/comparison_value.rs +++ b/crates/ndc-test-helpers/src/comparison_value.rs @@ -1,7 +1,7 @@ #[macro_export] macro_rules! column_value { ($($column:tt)+) => { - ndc_sdk::models::ComparisonValue::Column { + $crate::ndc_models::ComparisonValue::Column { column: $crate::target!($($column)+), } }; @@ -10,7 +10,7 @@ macro_rules! column_value { #[macro_export] macro_rules! value { ($($value:tt)+) => { - ndc_sdk::models::ComparisonValue::Scalar { + $crate::ndc_models::ComparisonValue::Scalar { value: serde_json::json!($($value)+), } }; @@ -19,11 +19,11 @@ macro_rules! value { #[macro_export] macro_rules! variable { ($variable:ident) => { - ndc_sdk::models::ComparisonValue::Variable { + $crate::ndc_models::ComparisonValue::Variable { name: stringify!($variable).to_owned(), } }; ($variable:expr) => { - ndc_sdk::models::ComparisonValue::Variable { name: $expr } + $crate::ndc_models::ComparisonValue::Variable { name: $expr } }; } diff --git a/crates/ndc-test-helpers/src/exists_in_collection.rs b/crates/ndc-test-helpers/src/exists_in_collection.rs index f53a1aaf..5208086e 100644 --- a/crates/ndc-test-helpers/src/exists_in_collection.rs +++ b/crates/ndc-test-helpers/src/exists_in_collection.rs @@ -1,13 +1,13 @@ #[macro_export] macro_rules! related { ($rel:literal) => { - ndc_sdk::models::ExistsInCollection::Related { + $crate::ndc_models::ExistsInCollection::Related { relationship: $rel.to_owned(), arguments: Default::default(), } }; ($rel:literal, $args:expr $(,)?) => { - ndc_sdk::models::ExistsInCollection::Related { + $crate::ndc_models::ExistsInCollection::Related { relationship: $rel.to_owned(), arguments: $args.into_iter().map(|x| x.into()).collect(), } @@ -17,13 +17,13 @@ macro_rules! related { #[macro_export] macro_rules! unrelated { ($coll:literal) => { - ndc_sdk::models::ExistsInCollection::Unrelated { + $crate::ndc_models::ExistsInCollection::Unrelated { collection: $coll.to_owned(), arguments: Default::default(), } }; ($coll:literal, $args:expr $(,)?) => { - ndc_sdk::models::ExistsInCollection::Related { + $crate::ndc_models::ExistsInCollection::Related { collection: $coll.to_owned(), arguments: $args.into_iter().map(|x| x.into()).collect(), } diff --git a/crates/ndc-test-helpers/src/field.rs b/crates/ndc-test-helpers/src/field.rs index b1e1e98b..d844ee2e 100644 --- a/crates/ndc-test-helpers/src/field.rs +++ b/crates/ndc-test-helpers/src/field.rs @@ -3,7 +3,7 @@ macro_rules! field { ($name:literal) => { ( $name, - ndc_sdk::models::Field::Column { + $crate::ndc_models::Field::Column { column: $name.to_owned(), fields: None, }, @@ -12,7 +12,7 @@ macro_rules! field { ($name:literal => $column_name:literal) => { ( $name, - ndc_sdk::models::Field::Column { + $crate::ndc_models::Field::Column { column: $column_name.to_owned(), fields: None, }, @@ -21,7 +21,7 @@ macro_rules! field { ($name:literal => $column_name:literal, $fields:expr) => { ( $name, - ndc_sdk::models::Field::Column { + $crate::ndc_models::Field::Column { column: $column_name.to_owned(), fields: Some($fields.into()), }, @@ -32,7 +32,7 @@ macro_rules! field { #[macro_export] macro_rules! object { ($fields:expr) => { - ndc_sdk::models::NestedField::Object(ndc_sdk::models::NestedObject { + $crate::ndc_models::NestedField::Object($crate::ndc_models::NestedObject { fields: $fields .into_iter() .map(|(name, field)| (name.to_owned(), field)) @@ -44,7 +44,7 @@ macro_rules! object { #[macro_export] macro_rules! array { ($fields:expr) => { - ndc_sdk::models::NestedField::Array(ndc_sdk::models::NestedArray { + $crate::ndc_models::NestedField::Array($crate::ndc_models::NestedArray { fields: Box::new($fields), }) }; @@ -55,7 +55,7 @@ macro_rules! relation_field { ($relationship:literal => $name:literal) => { ( $name, - ndc_sdk::models::Field::Relationship { + $crate::ndc_models::Field::Relationship { query: Box::new($crate::query().into()), relationship: $relationship.to_owned(), arguments: Default::default(), @@ -65,7 +65,7 @@ macro_rules! relation_field { ($relationship:literal => $name:literal, $query:expr) => { ( $name, - ndc_sdk::models::Field::Relationship { + $crate::ndc_models::Field::Relationship { query: Box::new($query.into()), relationship: $relationship.to_owned(), arguments: Default::default(), diff --git a/crates/ndc-test-helpers/src/lib.rs b/crates/ndc-test-helpers/src/lib.rs index c1fe9731..06fb273f 100644 --- a/crates/ndc-test-helpers/src/lib.rs +++ b/crates/ndc-test-helpers/src/lib.rs @@ -17,6 +17,9 @@ use ndc_models::{ QueryRequest, Relationship, RelationshipArgument, RelationshipType, }; +// Export this crate's reference to ndc_models so that we can use this reference in macros. +pub extern crate ndc_models; + pub use collection_info::*; pub use comparison_target::*; pub use comparison_value::*; @@ -162,6 +165,11 @@ impl QueryBuilder { self } + pub fn limit(mut self, n: u32) -> Self { + self.limit = Some(n); + self + } + pub fn order_by(mut self, elements: Vec) -> Self { self.order_by = Some(OrderBy { elements }); self