From 8359613311f8a855424b8480383b058e30071fe1 Mon Sep 17 00:00:00 2001 From: dariuszkuc <9501705+dariuszkuc@users.noreply.github.com> Date: Thu, 2 Nov 2023 15:11:09 -0500 Subject: [PATCH] re-add snapshot tests --- tests/composition_tests.rs | 185 ++++++++++++++++++ ...ests__tests__can_compose_supergraph-2.snap | 25 +++ ..._tests__tests__can_compose_supergraph.snap | 59 ++++++ ...pose_types_from_different_subgraphs-2.snap | 18 ++ ...ompose_types_from_different_subgraphs.snap | 52 +++++ ...ests__can_compose_with_descriptions-2.snap | 26 +++ ..._tests__can_compose_with_descriptions.snap | 59 ++++++ ...mpose_removes_federation_directives-2.snap | 13 ++ ...compose_removes_federation_directives.snap | 47 +++++ tests/subgraph/mod.rs | 1 + tests/subgraph/parse_expand_tests.rs | 164 ++++++++++++++++ 11 files changed, 649 insertions(+) create mode 100644 tests/composition_tests.rs create mode 100644 tests/snapshots/composition_tests__tests__can_compose_supergraph-2.snap create mode 100644 tests/snapshots/composition_tests__tests__can_compose_supergraph.snap create mode 100644 tests/snapshots/composition_tests__tests__can_compose_types_from_different_subgraphs-2.snap create mode 100644 tests/snapshots/composition_tests__tests__can_compose_types_from_different_subgraphs.snap create mode 100644 tests/snapshots/composition_tests__tests__can_compose_with_descriptions-2.snap create mode 100644 tests/snapshots/composition_tests__tests__can_compose_with_descriptions.snap create mode 100644 tests/snapshots/composition_tests__tests__compose_removes_federation_directives-2.snap create mode 100644 tests/snapshots/composition_tests__tests__compose_removes_federation_directives.snap create mode 100644 tests/subgraph/mod.rs create mode 100644 tests/subgraph/parse_expand_tests.rs diff --git a/tests/composition_tests.rs b/tests/composition_tests.rs new file mode 100644 index 00000000..a5180cd7 --- /dev/null +++ b/tests/composition_tests.rs @@ -0,0 +1,185 @@ +mod subgraph; + +#[cfg(test)] +mod tests { + use apollo_compiler::Schema; + use apollo_federation::subgraph::Subgraph; + use apollo_federation::Supergraph; + + fn print_sdl(schema: &Schema) -> String { + let mut schema = schema.clone(); + schema.types.sort_keys(); + schema.directive_definitions.sort_keys(); + schema.to_string() + } + + #[test] + fn can_compose_supergraph() { + let s1 = Subgraph::parse_and_expand( + "Subgraph1", + "https://subgraph1", + r#" + type Query { + t: T + } + + type T @key(fields: "k") { + k: ID + } + + type S { + x: Int + } + + union U = S | T + "#, + ) + .unwrap(); + let s2 = Subgraph::parse_and_expand( + "Subgraph2", + "https://subgraph2", + r#" + type T @key(fields: "k") { + k: ID + a: Int + b: String + } + + enum E { + V1 + V2 + } + "#, + ) + .unwrap(); + + let supergraph = Supergraph::compose(vec![&s1, &s2]).unwrap(); + insta::assert_snapshot!(print_sdl(&supergraph.schema)); + insta::assert_snapshot!(print_sdl(&supergraph.to_api_schema())); + } + + #[test] + fn can_compose_with_descriptions() { + let s1 = Subgraph::parse_and_expand( + "Subgraph1", + "https://subgraph1", + r#" + "The foo directive description" + directive @foo(url: String) on FIELD + + "A cool schema" + schema { + query: Query + } + + """ + Available queries + Not much yet + """ + type Query { + "Returns tea" + t( + "An argument that is very important" + x: String! + ): String + } + "#, + ) + .unwrap(); + + let s2 = Subgraph::parse_and_expand( + "Subgraph2", + "https://subgraph2", + r#" + "The foo directive description" + directive @foo(url: String) on FIELD + + "An enum" + enum E { + "The A value" + A + "The B value" + B + } + "#, + ) + .unwrap(); + + let supergraph = Supergraph::compose(vec![&s1, &s2]).unwrap(); + insta::assert_snapshot!(print_sdl(&supergraph.schema)); + insta::assert_snapshot!(print_sdl(&supergraph.to_api_schema())); + } + + #[test] + fn can_compose_types_from_different_subgraphs() { + let s1 = Subgraph::parse_and_expand( + "SubgraphA", + "https://subgraphA", + r#" + type Query { + products: [Product!] + } + + type Product { + sku: String! + name: String! + } + "#, + ) + .unwrap(); + + let s2 = Subgraph::parse_and_expand( + "SubgraphB", + "https://subgraphB", + r#" + type User { + name: String + email: String! + } + "#, + ) + .unwrap(); + let supergraph = Supergraph::compose(vec![&s1, &s2]).unwrap(); + insta::assert_snapshot!(print_sdl(&supergraph.schema)); + insta::assert_snapshot!(print_sdl(&supergraph.to_api_schema())); + } + + #[test] + fn compose_removes_federation_directives() { + let s1 = Subgraph::parse_and_expand( + "SubgraphA", + "https://subgraphA", + r#" + extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: [ "@key", "@provides", "@external" ]) + + type Query { + products: [Product!] @provides(fields: "name") + } + + type Product @key(fields: "sku") { + sku: String! + name: String! @external + } + "#, + ) + .unwrap(); + + let s2 = Subgraph::parse_and_expand( + "SubgraphB", + "https://subgraphB", + r#" + extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: [ "@key", "@shareable" ]) + + type Product @key(fields: "sku") { + sku: String! + name: String! @shareable + } + "#, + ) + .unwrap(); + + let supergraph = Supergraph::compose(vec![&s1, &s2]).unwrap(); + insta::assert_snapshot!(print_sdl(&supergraph.schema)); + insta::assert_snapshot!(print_sdl(&supergraph.to_api_schema())); + } +} diff --git a/tests/snapshots/composition_tests__tests__can_compose_supergraph-2.snap b/tests/snapshots/composition_tests__tests__can_compose_supergraph-2.snap new file mode 100644 index 00000000..25157aa3 --- /dev/null +++ b/tests/snapshots/composition_tests__tests__can_compose_supergraph-2.snap @@ -0,0 +1,25 @@ +--- +source: apollo-federation/tests/composition_tests.rs +expression: print_sdl(&supergraph.to_api_schema()) +--- +enum E { + V1 + V2 +} + +type Query { + t: T +} + +type S { + x: Int +} + +type T { + k: ID + a: Int + b: String +} + +union U = S | T + diff --git a/tests/snapshots/composition_tests__tests__can_compose_supergraph.snap b/tests/snapshots/composition_tests__tests__can_compose_supergraph.snap new file mode 100644 index 00000000..48ecee47 --- /dev/null +++ b/tests/snapshots/composition_tests__tests__can_compose_supergraph.snap @@ -0,0 +1,59 @@ +--- +source: apollo-federation/tests/composition_tests.rs +expression: print_sdl(&supergraph.schema) +--- +schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { + query: Query +} + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on INTERFACE | OBJECT + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on ENUM | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +enum E @join__type(graph: SUBGRAPH2) { + V1 @join__enumValue(graph: SUBGRAPH2) + V2 @join__enumValue(graph: SUBGRAPH2) +} + +type Query @join__type(graph: SUBGRAPH1) @join__type(graph: SUBGRAPH2) { + t: T @join__field(graph: SUBGRAPH1) +} + +type S @join__type(graph: SUBGRAPH1) { + x: Int +} + +type T @join__type(graph: SUBGRAPH1, key: "k") @join__type(graph: SUBGRAPH2, key: "k") { + k: ID + a: Int @join__field(graph: SUBGRAPH2) + b: String @join__field(graph: SUBGRAPH2) +} + +union U @join__type(graph: SUBGRAPH1) @join__unionMember(graph: SUBGRAPH1, member: "S") @join__unionMember(graph: SUBGRAPH1, member: "T") = S | T + +scalar join__FieldSet + +enum join__Graph { + SUBGRAPH1 @join__graph(name: "Subgraph1", url: "https://subgraph1") + SUBGRAPH2 @join__graph(name: "Subgraph2", url: "https://subgraph2") +} + +scalar link__Import + +enum link__Purpose { + "SECURITY features provide metadata necessary to securely resolve fields." + SECURITY + "EXECUTION features provide metadata necessary for operation execution." + EXECUTION +} + diff --git a/tests/snapshots/composition_tests__tests__can_compose_types_from_different_subgraphs-2.snap b/tests/snapshots/composition_tests__tests__can_compose_types_from_different_subgraphs-2.snap new file mode 100644 index 00000000..a29eec95 --- /dev/null +++ b/tests/snapshots/composition_tests__tests__can_compose_types_from_different_subgraphs-2.snap @@ -0,0 +1,18 @@ +--- +source: apollo-federation/tests/composition_tests.rs +expression: print_sdl(&supergraph.to_api_schema()) +--- +type Product { + sku: String! + name: String! +} + +type Query { + products: [Product!] +} + +type User { + name: String + email: String! +} + diff --git a/tests/snapshots/composition_tests__tests__can_compose_types_from_different_subgraphs.snap b/tests/snapshots/composition_tests__tests__can_compose_types_from_different_subgraphs.snap new file mode 100644 index 00000000..ff49e761 --- /dev/null +++ b/tests/snapshots/composition_tests__tests__can_compose_types_from_different_subgraphs.snap @@ -0,0 +1,52 @@ +--- +source: apollo-federation/tests/composition_tests.rs +expression: print_sdl(&supergraph.schema) +--- +schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { + query: Query +} + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on INTERFACE | OBJECT + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on ENUM | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +type Product @join__type(graph: SUBGRAPHA) { + sku: String! + name: String! +} + +type Query @join__type(graph: SUBGRAPHA) @join__type(graph: SUBGRAPHB) { + products: [Product!] @join__field(graph: SUBGRAPHA) +} + +type User @join__type(graph: SUBGRAPHB) { + name: String + email: String! +} + +scalar join__FieldSet + +enum join__Graph { + SUBGRAPHA @join__graph(name: "SubgraphA", url: "https://subgraphA") + SUBGRAPHB @join__graph(name: "SubgraphB", url: "https://subgraphB") +} + +scalar link__Import + +enum link__Purpose { + "SECURITY features provide metadata necessary to securely resolve fields." + SECURITY + "EXECUTION features provide metadata necessary for operation execution." + EXECUTION +} + diff --git a/tests/snapshots/composition_tests__tests__can_compose_with_descriptions-2.snap b/tests/snapshots/composition_tests__tests__can_compose_with_descriptions-2.snap new file mode 100644 index 00000000..dcd50e42 --- /dev/null +++ b/tests/snapshots/composition_tests__tests__can_compose_with_descriptions-2.snap @@ -0,0 +1,26 @@ +--- +source: apollo-federation/tests/composition_tests.rs +expression: print_sdl(&supergraph.to_api_schema()) +--- +"A cool schema" +schema { + query: Query +} + +"An enum" +enum E { + "The A value" + A + "The B value" + B +} + +"Available queries\nNot much yet" +type Query { + "Returns tea" + t( + "An argument that is very important" + x: String!, + ): String +} + diff --git a/tests/snapshots/composition_tests__tests__can_compose_with_descriptions.snap b/tests/snapshots/composition_tests__tests__can_compose_with_descriptions.snap new file mode 100644 index 00000000..b5e9eb22 --- /dev/null +++ b/tests/snapshots/composition_tests__tests__can_compose_with_descriptions.snap @@ -0,0 +1,59 @@ +--- +source: apollo-federation/tests/composition_tests.rs +expression: print_sdl(&supergraph.schema) +--- +"A cool schema" +schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { + query: Query +} + +"The foo directive description" +directive @foo(url: String) on FIELD + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on INTERFACE | OBJECT + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on ENUM | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +"An enum" +enum E @join__type(graph: SUBGRAPH2) { + "The A value" + A @join__enumValue(graph: SUBGRAPH2) + "The B value" + B @join__enumValue(graph: SUBGRAPH2) +} + +"Available queries\nNot much yet" +type Query @join__type(graph: SUBGRAPH1) @join__type(graph: SUBGRAPH2) { + "Returns tea" + t( + "An argument that is very important" + x: String!, + ): String @join__field(graph: SUBGRAPH1) +} + +scalar join__FieldSet + +enum join__Graph { + SUBGRAPH1 @join__graph(name: "Subgraph1", url: "https://subgraph1") + SUBGRAPH2 @join__graph(name: "Subgraph2", url: "https://subgraph2") +} + +scalar link__Import + +enum link__Purpose { + "SECURITY features provide metadata necessary to securely resolve fields." + SECURITY + "EXECUTION features provide metadata necessary for operation execution." + EXECUTION +} + diff --git a/tests/snapshots/composition_tests__tests__compose_removes_federation_directives-2.snap b/tests/snapshots/composition_tests__tests__compose_removes_federation_directives-2.snap new file mode 100644 index 00000000..02e107db --- /dev/null +++ b/tests/snapshots/composition_tests__tests__compose_removes_federation_directives-2.snap @@ -0,0 +1,13 @@ +--- +source: apollo-federation/tests/composition_tests.rs +expression: print_sdl(&supergraph.to_api_schema()) +--- +type Product { + sku: String! + name: String! +} + +type Query { + products: [Product!] +} + diff --git a/tests/snapshots/composition_tests__tests__compose_removes_federation_directives.snap b/tests/snapshots/composition_tests__tests__compose_removes_federation_directives.snap new file mode 100644 index 00000000..06486d31 --- /dev/null +++ b/tests/snapshots/composition_tests__tests__compose_removes_federation_directives.snap @@ -0,0 +1,47 @@ +--- +source: apollo-federation/tests/composition_tests.rs +expression: print_sdl(&supergraph.schema) +--- +schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { + query: Query +} + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on INTERFACE | OBJECT + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on ENUM | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +type Product @join__type(graph: SUBGRAPHA, key: "sku") @join__type(graph: SUBGRAPHB, key: "sku") { + sku: String! + name: String! @join__field(graph: SUBGRAPHA, external: true) @join__field(graph: SUBGRAPHB) +} + +type Query @join__type(graph: SUBGRAPHA) @join__type(graph: SUBGRAPHB) { + products: [Product!] @join__field(graph: SUBGRAPHA, provides: "name") +} + +scalar join__FieldSet + +enum join__Graph { + SUBGRAPHA @join__graph(name: "SubgraphA", url: "https://subgraphA") + SUBGRAPHB @join__graph(name: "SubgraphB", url: "https://subgraphB") +} + +scalar link__Import + +enum link__Purpose { + "SECURITY features provide metadata necessary to securely resolve fields." + SECURITY + "EXECUTION features provide metadata necessary for operation execution." + EXECUTION +} + diff --git a/tests/subgraph/mod.rs b/tests/subgraph/mod.rs new file mode 100644 index 00000000..7610f592 --- /dev/null +++ b/tests/subgraph/mod.rs @@ -0,0 +1 @@ +mod parse_expand_tests; diff --git a/tests/subgraph/parse_expand_tests.rs b/tests/subgraph/parse_expand_tests.rs new file mode 100644 index 00000000..2ed0d366 --- /dev/null +++ b/tests/subgraph/parse_expand_tests.rs @@ -0,0 +1,164 @@ +use apollo_federation::subgraph::Subgraph; + +#[test] +fn can_parse_and_expand() -> Result<(), String> { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key" ]) + + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + x: Int + } + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://s1", schema).map_err(|e| { + println!("{}", e.msg); + String::from("failed to parse and expand the subgraph, see errors above for details") + })?; + assert!(subgraph.schema.types.contains_key("T")); + assert!(subgraph.schema.directive_definitions.contains_key("key")); + assert!(subgraph + .schema + .directive_definitions + .contains_key("federation__requires")); + Ok(()) +} + +#[test] +fn can_parse_and_expand_with_renames() -> Result<(), String> { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ { name: "@key", as: "@myKey" }, "@provides" ]) + + type Query { + t: T @provides(fields: "x") + } + + type T @myKey(fields: "id") { + id: ID! + x: Int + } + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://s1", schema).map_err(|e| { + println!("{}", e.msg); + String::from("failed to parse and expand the subgraph, see errors above for details") + })?; + assert!(subgraph.schema.directive_definitions.contains_key("myKey")); + assert!(subgraph + .schema + .directive_definitions + .contains_key("provides")); + Ok(()) +} + +#[test] +fn can_parse_and_expand_with_namespace() -> Result<(), String> { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key" ], as: "fed" ) + + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + x: Int + } + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://s1", schema).map_err(|e| { + println!("{}", e.msg); + String::from("failed to parse and expand the subgraph, see errors above for details") + })?; + assert!(subgraph.schema.directive_definitions.contains_key("key")); + assert!(subgraph + .schema + .directive_definitions + .contains_key("fed__requires")); + Ok(()) +} + +#[test] +fn can_parse_and_expand_preserves_user_definitions() -> Result<(), String> { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/link/v1.0", import: ["Import", "Purpose"]) + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key" ]) + + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + x: Int + } + + enum Purpose { + SECURITY + EXECUTION + } + + scalar Import + + directive @link(url: String, as: String, import: [Import], for: Purpose) repeatable on SCHEMA + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://s1", schema).map_err(|e| { + println!("{}", e.msg); + String::from("failed to parse and expand the subgraph, see errors above for details") + })?; + assert!(subgraph.schema.types.contains_key("Purpose")); + Ok(()) +} + +#[test] +fn can_parse_and_expand_works_with_fed_v1() -> Result<(), String> { + let schema = r#" + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + x: Int + } + "#; + + let subgraph = Subgraph::parse_and_expand("S1", "http://s1", schema).map_err(|e| { + println!("{}", e.msg); + String::from("failed to parse and expand the subgraph, see errors above for details") + })?; + assert!(subgraph.schema.types.contains_key("T")); + assert!(subgraph.schema.directive_definitions.contains_key("key")); + Ok(()) +} + +#[test] +fn can_parse_and_expand_will_fail_when_importing_same_spec_twice() { + let schema = r#" + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key" ] ) + @link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@provides" ] ) + + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + x: Int + } + "#; + + let result = Subgraph::parse_and_expand("S1", "http://s1", schema) + .expect_err("importing same specification twice should fail"); + assert_eq!("invalid graphql schema - multiple @link imports for the federation specification are not supported", result.msg); +}