diff --git a/graphene_federation/__init__.py b/graphene_federation/__init__.py index 4d6bcf6..73786d0 100644 --- a/graphene_federation/__init__.py +++ b/graphene_federation/__init__.py @@ -7,3 +7,4 @@ from .inaccessible import inaccessible from .provides import provides from .override import override +from .compose_directive import mark_composable, is_composable diff --git a/graphene_federation/compose_directive.py b/graphene_federation/compose_directive.py new file mode 100644 index 0000000..824d697 --- /dev/null +++ b/graphene_federation/compose_directive.py @@ -0,0 +1,64 @@ +from typing import Optional + +from graphql import GraphQLDirective + + +def is_composable(directive: GraphQLDirective) -> bool: + """ + Checks if the directive will be composed to supergraph. + Validates the presence of _compose_import_url attribute + """ + return hasattr(directive, "_compose_import_url") + + +def mark_composable( + directive: GraphQLDirective, import_url: str, import_as: Optional[str] = None +) -> GraphQLDirective: + """ + Marks directive with _compose_import_url and _compose_import_as + Enables Identification of directives which are to be composed to supergraph + """ + setattr(directive, "_compose_import_url", import_url) + if import_as: + setattr(directive, "_compose_import_as", import_as) + return directive + + +def compose_directive_schema_extensions(directives: list[GraphQLDirective]): + """ + Generates schema extends string for ComposeDirective + """ + link_schema = "" + compose_directive_schema = "" + # Using dictionary to generate cleaner schema when multiple directives imports from same URL. + links: dict = {} + + for directive in directives: + # TODO: Replace with walrus operator when dropping Python 3.8 support + if hasattr(directive, "_compose_import_url"): + compose_import_url = getattr(directive, "_compose_import_url") + if hasattr(directive, "_compose_import_as"): + compose_import_as = getattr(directive, "_compose_import_as") + import_value = ( + f'{{ name: "@{directive.name}, as: "@{compose_import_as}" }}' + ) + imported_name = compose_import_as + else: + import_value = f'"@{directive.name}"' + imported_name = directive.name + + import_url = compose_import_url + + if links.get(import_url): + links[import_url] = links[import_url].append(import_value) + else: + links[import_url] = [import_value] + + compose_directive_schema += ( + f' @composeDirective(name: "@{imported_name}")\n' + ) + + for import_url in links: + link_schema += f' @link(url: "{import_url}", import: [{",".join(value for value in links[import_url])}])\n' + + return link_schema + compose_directive_schema diff --git a/graphene_federation/main.py b/graphene_federation/main.py index e1a1fa0..87ad5ca 100644 --- a/graphene_federation/main.py +++ b/graphene_federation/main.py @@ -23,16 +23,20 @@ def _get_query(schema: Schema, query_cls: Optional[ObjectType] = None) -> Object def build_schema( query: Optional[ObjectType] = None, mutation: Optional[ObjectType] = None, - enable_federation_2=False, + federation_version: Optional[float] = None, + enable_federation_2: bool = False, schema: Optional[Schema] = None, **kwargs ) -> Schema: schema = schema or Schema(query=query, mutation=mutation, **kwargs) schema.auto_camelcase = kwargs.get("auto_camelcase", True) - schema.federation_version = 2 if enable_federation_2 else 1 - federation_query = _get_query(schema, schema.query if schema else query) - return Schema( - query=federation_query, - mutation=schema.mutation if schema else mutation, - **kwargs + schema.federation_version = float( + (federation_version or 2) if (enable_federation_2 or federation_version) else 1 ) + federation_query = _get_query(schema, schema.query) + # Use shallow copy to prevent recursion error + kwargs = schema.__dict__.copy() + kwargs.pop("query") + kwargs.pop("graphql_schema") + kwargs.pop("federation_version") + return type(schema)(query=federation_query, **kwargs) diff --git a/graphene_federation/service.py b/graphene_federation/service.py index 786ea19..27655c9 100644 --- a/graphene_federation/service.py +++ b/graphene_federation/service.py @@ -5,6 +5,7 @@ from graphene.types.union import UnionOptions from graphql import GraphQLInterfaceType, GraphQLObjectType +from .compose_directive import is_composable, compose_directive_schema_extensions from .external import get_external_fields from .inaccessible import get_inaccessible_types, get_inaccessible_fields from .override import get_override_fields @@ -120,37 +121,50 @@ def get_sdl(schema: Schema) -> str: external_fields = get_external_fields(schema) override_fields = get_override_fields(schema) - _schema = "" + schema_extensions = [] - if schema.federation_version == 2: + if schema.federation_version >= 2: shareable_types = get_shareable_types(schema) inaccessible_types = get_inaccessible_types(schema) shareable_fields = get_shareable_fields(schema) tagged_fields = get_tagged_fields(schema) inaccessible_fields = get_inaccessible_fields(schema) - _schema_import = [] + federation_spec_import = [] if extended_types: - _schema_import.append('"@extends"') + federation_spec_import.append('"@extends"') if external_fields: - _schema_import.append('"@external"') + federation_spec_import.append('"@external"') if entities: - _schema_import.append('"@key"') + federation_spec_import.append('"@key"') if override_fields: - _schema_import.append('"@override"') + federation_spec_import.append('"@override"') if provides_parent_types or provides_fields: - _schema_import.append('"@provides"') + federation_spec_import.append('"@provides"') if required_fields: - _schema_import.append('"@requires"') + federation_spec_import.append('"@requires"') if inaccessible_types or inaccessible_fields: - _schema_import.append('"@inaccessible"') + federation_spec_import.append('"@inaccessible"') if shareable_types or shareable_fields: - _schema_import.append('"@shareable"') + federation_spec_import.append('"@shareable"') if tagged_fields: - _schema_import.append('"@tag"') - schema_import = ", ".join(_schema_import) - _schema = f'extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: [{schema_import}])\n' + federation_spec_import.append('"@tag"') + + if schema.federation_version >= 2.1: + preserved_directives = [ + directive for directive in schema.directives if is_composable(directive) + ] + if preserved_directives: + federation_spec_import.append('"@composeDirective"') + schema_extensions.append( + compose_directive_schema_extensions(preserved_directives) + ) + + schema_import = ", ".join(federation_spec_import) + schema_extensions = [ + f'@link(url: "https://specs.apollo.dev/federation/v{schema.federation_version}", import: [{schema_import}])' + ] + schema_extensions # Add fields directives (@external, @provides, @requires, @shareable, @inaccessible) entities_ = ( @@ -161,7 +175,7 @@ def get_sdl(schema: Schema) -> str: | set(provides_fields.values()) ) - if schema.federation_version == 2: + if schema.federation_version >= 2: entities_ = ( entities_ | set(shareable_types.values()) @@ -183,62 +197,57 @@ def get_sdl(schema: Schema) -> str: # Add entity keys declarations get_field_name = type_attribute_to_field_name(schema) for entity_name, entity in entities.items(): - type_def_re = rf"(type {entity_name} [^\{{]*)" + " " + type_def_re = rf"(type {entity_name} [^\{{]*)" # resolvable argument of @key directive is true by default. If false, we add 'resolvable: false' to sdl. if ( - schema.federation_version == 2 + schema.federation_version >= 2 and hasattr(entity, "_resolvable") and not entity._resolvable ): - type_annotation = ( - ( - " ".join( - [ - f'@key(fields: "{get_field_name(key)}"' - for key in entity._keys - ] - ) - ) - + f", resolvable: {str(entity._resolvable).lower()})" - + " " - ) - else: type_annotation = ( " ".join( - [f'@key(fields: "{get_field_name(key)}")' for key in entity._keys] + [f'@key(fields: "{get_field_name(key)}"' for key in entity._keys] ) - ) + " " - repl_str = rf"\1{type_annotation}" + ) + f", resolvable: {str(entity._resolvable).lower()})" + else: + type_annotation = " ".join( + [f'@key(fields: "{get_field_name(key)}")' for key in entity._keys] + ) + repl_str = rf"\1 {type_annotation} " pattern = re.compile(type_def_re) string_schema = pattern.sub(repl_str, string_schema) - if schema.federation_version == 2: + if schema.federation_version >= 2: for type_name, type in shareable_types.items(): # noinspection PyProtectedMember if isinstance(type._meta, UnionOptions): type_def_re = rf"(union {type_name})" else: - type_def_re = rf"(type {type_name} [^\{{]*)" + " " - type_annotation = " @shareable" - repl_str = rf"\1{type_annotation} " + type_def_re = rf"(type {type_name} [^\{{]*)" + type_annotation = "@shareable" + repl_str = rf"\1 {type_annotation} " pattern = re.compile(type_def_re) string_schema = pattern.sub(repl_str, string_schema) for type_name, type in inaccessible_types.items(): # noinspection PyProtectedMember if isinstance(type._meta, InterfaceOptions): - type_def_re = rf"(interface {type_name}[^\{{]*)" + " " + type_def_re = rf"(interface {type_name}[^\{{]*)" elif isinstance(type._meta, UnionOptions): type_def_re = rf"(union {type_name})" else: - type_def_re = rf"(type {type_name} [^\{{]*)" + " " - type_annotation = " @inaccessible" - repl_str = rf"\1{type_annotation} " + type_def_re = rf"(type {type_name} [^\{{]*)" + type_annotation = "@inaccessible" + repl_str = rf"\1 {type_annotation} " pattern = re.compile(type_def_re) string_schema = pattern.sub(repl_str, string_schema) - return _schema + string_schema + if schema_extensions: + string_schema = ( + "extend schema\n " + "\n ".join(schema_extensions) + "\n" + string_schema + ) + return re.sub(r"[ ]+", " ", re.sub(r"\n+", "\n", string_schema)) # noqa def get_service_query(schema: Schema): diff --git a/graphene_federation/tests/test_annotation_corner_cases.py b/graphene_federation/tests/test_annotation_corner_cases.py index ce2705b..72aeea0 100644 --- a/graphene_federation/tests/test_annotation_corner_cases.py +++ b/graphene_federation/tests/test_annotation_corner_cases.py @@ -1,13 +1,14 @@ -import pytest +from textwrap import dedent from graphql import graphql_sync from graphene import ObjectType, ID, String, Field -from .. import external, requires -from ..entity import key -from ..extend import extend -from ..main import build_schema +from graphene_federation import external, requires +from graphene_federation.entity import key +from graphene_federation.extend import extend +from graphene_federation.main import build_schema +from graphene_federation.utils import clean_schema def test_similar_field_name(): @@ -31,39 +32,41 @@ class ChatQuery(ObjectType): message = Field(ChatMessage, id=ID(required=True)) chat_schema = build_schema(query=ChatQuery, enable_federation_2=True) - assert ( - str(chat_schema).strip() - == """schema { - query: ChatQuery -} - -type ChatQuery { - message(id: ID!): ChatMessage - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type ChatMessage { - id: ID! - user: ChatUser -} - -type ChatUser { - uid: ID - identified: ID - id: ID - iD: ID - ID: ID -} - -union _Entity = ChatUser - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + schema { + query: ChatQuery + } + + type ChatQuery { + message(id: ID!): ChatMessage + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type ChatMessage { + id: ID! + user: ChatUser + } + + type ChatUser { + uid: ID + identified: ID + id: ID + iD: ID + ID: ID + } + + union _Entity = ChatUser + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(chat_schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -74,29 +77,27 @@ class ChatQuery(ObjectType): """ result = graphql_sync(chat_schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key"]) - - -type ChatQuery { - message(id: ID!): ChatMessage -} - -type ChatMessage { - id: ID! - user: ChatUser -} - -extend type ChatUser @key(fields: "id") { - uid: ID - identified: ID - id: ID @external - iD: ID - ID: ID -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key"]) + + type ChatQuery { + message(id: ID!): ChatMessage + } + type ChatMessage { + id: ID! + user: ChatUser + } + extend type ChatUser @key(fields: "id") { + uid: ID + identified: ID + id: ID @external + iD: ID + ID: ID + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_camel_case_field_name(): @@ -115,29 +116,31 @@ class Query(ObjectType): camel = Field(Camel) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - camel: Camel - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Camel { - autoCamel: String - forcedCamel: String - aSnake: String - aCamel: String -} - -union _Entity = Camel - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + camel: Camel + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Camel { + autoCamel: String + forcedCamel: String + aSnake: String + aCamel: String + } + + union _Entity = Camel + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -148,21 +151,21 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@requires"]) -type Query { - camel: Camel -} - -extend type Camel @key(fields: "autoCamel") { - autoCamel: String @external - forcedCamel: String @requires(fields: "autoCamel") - aSnake: String - aCamel: String -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@requires"]) + type Query { + camel: Camel + } + extend type Camel @key(fields: "autoCamel") { + autoCamel: String @external + forcedCamel: String @requires(fields: "autoCamel") + aSnake: String + aCamel: String + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_camel_case_field_name_without_auto_camelcase(): @@ -181,29 +184,31 @@ class Query(ObjectType): camel = Field(Camel) schema = build_schema(query=Query, auto_camelcase=False, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - camel: Camel - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Camel { - auto_camel: String - forcedCamel: String - a_snake: String - aCamel: String -} - -union _Entity = Camel - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + camel: Camel + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Camel { + auto_camel: String + forcedCamel: String + a_snake: String + aCamel: String + } + + union _Entity = Camel + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -214,21 +219,22 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@requires"]) -type Query { - camel: Camel -} - -extend type Camel @key(fields: "auto_camel") { - auto_camel: String @external - forcedCamel: String @requires(fields: "auto_camel") - a_snake: String - aCamel: String -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@requires"]) + type Query { + camel: Camel + } + + extend type Camel @key(fields: "auto_camel") { + auto_camel: String @external + forcedCamel: String @requires(fields: "auto_camel") + a_snake: String + aCamel: String + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_annotated_field_also_used_in_filter(): @@ -250,31 +256,33 @@ class Query(ObjectType): a = Field(A) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - a: A - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type A { - id: ID - b(id: ID): B -} - -type B { - id: ID -} - -union _Entity = A | B - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + a: A + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type A { + id: ID + b(id: ID): B + } + + type B { + id: ID + } + + union _Entity = A | B + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -285,23 +293,24 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key"]) -type Query { - a: A -} - -extend type A @key(fields: "id") { - id: ID @external - b(id: ID): B -} - -type B @key(fields: "id") { - id: ID -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key"]) + type Query { + a: A + } + + extend type A @key(fields: "id") { + id: ID @external + b(id: ID): B + } + + type B @key(fields: "id") { + id: ID + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_annotate_object_with_meta_name(): @@ -324,31 +333,33 @@ class Query(ObjectType): a = Field(A) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - a: Banana - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Banana { - id: ID - b(id: ID): Potato -} - -type Potato { - id: ID -} - -union _Entity = Banana | Potato - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + a: Banana + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Banana { + id: ID + b(id: ID): Potato + } + + type Potato { + id: ID + } + + union _Entity = Banana | Potato + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -359,20 +370,22 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key"]) -type Query { - a: Banana -} - -extend type Banana @key(fields: "id") { - id: ID @external - b(id: ID): Potato -} - -type Potato @key(fields: "id") { - id: ID -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key"]) + type Query { + a: Banana + } + + extend type Banana @key(fields: "id") { + id: ID @external + b(id: ID): Potato + } + + type Potato @key(fields: "id") { + id: ID + } + """ ) + # assert compare_schema(result.data["_service"]["sdl"].strip(), expected_result) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/tests/test_annotation_corner_cases_v1.py b/graphene_federation/tests/test_annotation_corner_cases_v1.py index 1acba52..82ca7fc 100644 --- a/graphene_federation/tests/test_annotation_corner_cases_v1.py +++ b/graphene_federation/tests/test_annotation_corner_cases_v1.py @@ -1,14 +1,15 @@ -import pytest +from textwrap import dedent from graphql import graphql_sync from graphene import ObjectType, ID, String, Field -from ..entity import key -from ..extend import extend -from ..external import external -from ..requires import requires -from ..main import build_schema +from graphene_federation.entity import key +from graphene_federation.extend import extend +from graphene_federation.external import external +from graphene_federation.requires import requires +from graphene_federation.main import build_schema +from graphene_federation.utils import clean_schema def test_similar_field_name(): @@ -32,39 +33,41 @@ class ChatQuery(ObjectType): message = Field(ChatMessage, id=ID(required=True)) chat_schema = build_schema(query=ChatQuery) - assert ( - str(chat_schema).strip() - == """schema { - query: ChatQuery -} - -type ChatQuery { - message(id: ID!): ChatMessage - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type ChatMessage { - id: ID! - user: ChatUser -} - -type ChatUser { - uid: ID - identified: ID - id: ID - iD: ID - ID: ID -} - -union _Entity = ChatUser - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + schema { + query: ChatQuery + } + + type ChatQuery { + message(id: ID!): ChatMessage + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type ChatMessage { + id: ID! + user: ChatUser + } + + type ChatUser { + uid: ID + identified: ID + id: ID + iD: ID + ID: ID + } + + union _Entity = ChatUser + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(chat_schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -75,27 +78,27 @@ class ChatQuery(ObjectType): """ result = graphql_sync(chat_schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type ChatQuery { - message(id: ID!): ChatMessage -} - -type ChatMessage { - id: ID! - user: ChatUser -} - -extend type ChatUser @key(fields: "id") { - uid: ID - identified: ID - id: ID @external - iD: ID - ID: ID -} -""".strip() + expected_result = dedent( + """ + type ChatQuery { + message(id: ID!): ChatMessage + } + + type ChatMessage { + id: ID! + user: ChatUser + } + + extend type ChatUser @key(fields: "id") { + uid: ID + identified: ID + id: ID @external + iD: ID + ID: ID + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_camel_case_field_name(): @@ -114,29 +117,31 @@ class Query(ObjectType): camel = Field(Camel) schema = build_schema(query=Query) - assert ( - str(schema).strip() - == """type Query { - camel: Camel - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Camel { - autoCamel: String - forcedCamel: String - aSnake: String - aCamel: String -} - -union _Entity = Camel - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + camel: Camel + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Camel { + autoCamel: String + forcedCamel: String + aSnake: String + aCamel: String + } + + union _Entity = Camel + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -147,21 +152,21 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type Query { - camel: Camel -} - -extend type Camel @key(fields: "autoCamel") { - autoCamel: String @external - forcedCamel: String @requires(fields: "autoCamel") - aSnake: String - aCamel: String -} -""".strip() + expected_result = dedent( + """ + type Query { + camel: Camel + } + + extend type Camel @key(fields: "autoCamel") { + autoCamel: String @external + forcedCamel: String @requires(fields: "autoCamel") + aSnake: String + aCamel: String + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_camel_case_field_name_without_auto_camelcase(): @@ -180,29 +185,31 @@ class Query(ObjectType): camel = Field(Camel) schema = build_schema(query=Query, auto_camelcase=False) - assert ( - str(schema).strip() - == """type Query { - camel: Camel - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Camel { - auto_camel: String - forcedCamel: String - a_snake: String - aCamel: String -} - -union _Entity = Camel - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + camel: Camel + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Camel { + auto_camel: String + forcedCamel: String + a_snake: String + aCamel: String + } + + union _Entity = Camel + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -213,21 +220,21 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type Query { - camel: Camel -} - -extend type Camel @key(fields: "auto_camel") { - auto_camel: String @external - forcedCamel: String @requires(fields: "auto_camel") - a_snake: String - aCamel: String -} -""".strip() + expected_result = dedent( + """ + type Query { + camel: Camel + } + + extend type Camel @key(fields: "auto_camel") { + auto_camel: String @external + forcedCamel: String @requires(fields: "auto_camel") + a_snake: String + aCamel: String + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_annotated_field_also_used_in_filter(): @@ -249,31 +256,33 @@ class Query(ObjectType): a = Field(A) schema = build_schema(query=Query) - assert ( - str(schema).strip() - == """type Query { - a: A - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type A { - id: ID - b(id: ID): B -} - -type B { - id: ID -} - -union _Entity = A | B - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + a: A + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type A { + id: ID + b(id: ID): B + } + + type B { + id: ID + } + + union _Entity = A | B + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -284,23 +293,23 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type Query { - a: A -} - -extend type A @key(fields: "id") { - id: ID @external - b(id: ID): B -} - -type B @key(fields: "id") { - id: ID -} -""".strip() + expected_result = dedent( + """ + type Query { + a: A + } + + extend type A @key(fields: "id") { + id: ID @external + b(id: ID): B + } + + type B @key(fields: "id") { + id: ID + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_annotate_object_with_meta_name(): @@ -323,31 +332,33 @@ class Query(ObjectType): a = Field(A) schema = build_schema(query=Query) - assert ( - str(schema).strip() - == """type Query { - a: Banana - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Banana { - id: ID - b(id: ID): Potato -} - -type Potato { - id: ID -} - -union _Entity = Banana | Potato - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + a: Banana + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Banana { + id: ID + b(id: ID): Potato + } + + type Potato { + id: ID + } + + union _Entity = Banana | Potato + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -358,20 +369,20 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type Query { - a: Banana -} - -extend type Banana @key(fields: "id") { - id: ID @external - b(id: ID): Potato -} - -type Potato @key(fields: "id") { - id: ID -} -""".strip() + expected_result = dedent( + """ + type Query { + a: Banana + } + + extend type Banana @key(fields: "id") { + id: ID @external + b(id: ID): Potato + } + + type Potato @key(fields: "id") { + id: ID + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/tests/test_custom_enum.py b/graphene_federation/tests/test_custom_enum.py index 6a9b202..933c559 100644 --- a/graphene_federation/tests/test_custom_enum.py +++ b/graphene_federation/tests/test_custom_enum.py @@ -1,8 +1,11 @@ +from textwrap import dedent + import graphene from graphene import ObjectType from graphql import graphql_sync from graphene_federation import build_schema, shareable, inaccessible +from graphene_federation.utils import clean_schema def test_custom_enum(): @@ -31,22 +34,24 @@ class Query(ObjectType): } """ result = graphql_sync(schema.graphql_schema, query) - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible", "@shareable"]) -type TestCustomEnum @shareable { - testShareableScalar: Episode @shareable - testInaccessibleScalar: Episode @inaccessible -} - -enum Episode { - NEWHOPE - EMPIRE - JEDI -} - -type Query { - test: Episode - test2: [TestCustomEnum]! -}""" + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible", "@shareable"]) + type TestCustomEnum @shareable { + testShareableScalar: Episode @shareable + testInaccessibleScalar: Episode @inaccessible + } + + enum Episode { + NEWHOPE + EMPIRE + JEDI + } + + type Query { + test: Episode + test2: [TestCustomEnum]! + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/tests/test_extend.py b/graphene_federation/tests/test_extend.py index 6dccaac..b34c884 100644 --- a/graphene_federation/tests/test_extend.py +++ b/graphene_federation/tests/test_extend.py @@ -1,11 +1,13 @@ +from textwrap import dedent + import pytest from graphene import ObjectType, ID, String, Field from graphql import graphql_sync from graphene_federation import build_schema, external, shareable - -from ..extend import extend +from graphene_federation.utils import clean_schema +from graphene_federation.extend import extend def test_extend_non_existing_field_failure(): @@ -69,31 +71,33 @@ class Query(ObjectType): user = Field(User) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - user: User - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type User { - id: ID - organization: Organization -} - -type Organization { - id: ID -} - -union _Entity = User - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + user: User + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type User { + id: ID + organization: Organization + } + + type Organization { + id: ID + } + + union _Entity = User + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -104,21 +108,21 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@shareable"]) -type Query { - user: User -} - -extend type User @key(fields: "id organization {id }") { - id: ID @external - organization: Organization -} - -type Organization @shareable { - id: ID -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@shareable"]) + type Query { + user: User + } + + extend type User @key(fields: "id organization {id }") { + id: ID @external + organization: Organization + } + + type Organization @shareable { + id: ID + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/tests/test_inaccessible.py b/graphene_federation/tests/test_inaccessible.py index e46795e..171c94b 100644 --- a/graphene_federation/tests/test_inaccessible.py +++ b/graphene_federation/tests/test_inaccessible.py @@ -1,8 +1,11 @@ +from textwrap import dedent + import graphene from graphene import ObjectType from graphql import graphql_sync -from .. import inaccessible, build_schema +from graphene_federation import inaccessible, build_schema +from graphene_federation.utils import clean_schema def test_inaccessible_interface(): @@ -44,18 +47,20 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible"]) -type Position @inaccessible { - x: Int! - y: Int! @inaccessible -} - -type Query { - inStockCount: Int! -}""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible"]) + type Position @inaccessible { + x: Int! + y: Int! @inaccessible + } + + type Query { + inStockCount: Int! + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_inaccessible_union(): @@ -93,27 +98,29 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible"]) -union SearchResult @inaccessible = Human | Droid | Starship - -type Human @inaccessible { - name: String - bornIn: String -} - -type Droid @inaccessible { - name: String @inaccessible - primaryFunction: String -} - -type Starship @inaccessible { - name: String - length: Int @inaccessible -} - -type Query { - inStockCount: Int! -}""".strip() + expected_schema = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible"]) + union SearchResult @inaccessible = Human | Droid | Starship + + type Human @inaccessible { + name: String + bornIn: String + } + + type Droid @inaccessible { + name: String @inaccessible + primaryFunction: String + } + + type Starship @inaccessible { + name: String + length: Int @inaccessible + } + + type Query { + inStockCount: Int! + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_schema) diff --git a/graphene_federation/tests/test_key.py b/graphene_federation/tests/test_key.py index 2b9b83f..5f09c95 100644 --- a/graphene_federation/tests/test_key.py +++ b/graphene_federation/tests/test_key.py @@ -1,11 +1,13 @@ import pytest +from textwrap import dedent from graphql import graphql_sync from graphene import ObjectType, ID, String, Field -from ..entity import key -from ..main import build_schema +from graphene_federation.entity import key +from graphene_federation.main import build_schema +from graphene_federation.utils import clean_schema def test_multiple_keys(): @@ -19,27 +21,29 @@ class Query(ObjectType): user = Field(User) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - user: User - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type User { - identifier: ID - email: String -} - -union _Entity = User - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + user: User + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type User { + identifier: ID + email: String + } + + union _Entity = User + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -50,19 +54,20 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) -type Query { - user: User -} - -type User @key(fields: "email") @key(fields: "identifier") { - identifier: ID - email: String -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) + type Query { + user: User + } + + type User @key(fields: "email") @key(fields: "identifier") { + identifier: ID + email: String + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_key_non_existing_field_failure(): @@ -91,31 +96,33 @@ class Query(ObjectType): user = Field(User) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - user: User - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type User { - id: ID - organization: Organization -} - -type Organization { - registrationNumber: ID -} - -union _Entity = User - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + user: User + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type User { + id: ID + organization: Organization + } + + type Organization { + registrationNumber: ID + } + + union _Entity = User + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -126,24 +133,24 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) -type Query { - user: User -} - -type User @key(fields: "id organization { registrationNumber }") { - id: ID - organization: Organization -} - -type Organization { - registrationNumber: ID -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) + type Query { + user: User + } + + type User @key(fields: "id organization { registrationNumber }") { + id: ID + organization: Organization + } + + type Organization { + registrationNumber: ID + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_compound_primary_key_with_depth(): @@ -164,37 +171,39 @@ class Query(ObjectType): user = Field(User) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - user: User - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type User { - id: ID - organization: Organization -} - -type Organization { - registrationNumber: ID - businessUnit: BusinessUnit -} - -type BusinessUnit { - id: ID - name: String -} - -union _Entity = User - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + user: User + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type User { + id: ID + organization: Organization + } + + type Organization { + registrationNumber: ID + businessUnit: BusinessUnit + } + + type BusinessUnit { + id: ID + name: String + } + + union _Entity = User + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -205,30 +214,30 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) -type Query { - user: User -} - -type User @key(fields: "id organization { businessUnit {id name}}") { - id: ID - organization: Organization -} - -type Organization { - registrationNumber: ID - businessUnit: BusinessUnit -} - -type BusinessUnit { - id: ID - name: String -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) + type Query { + user: User + } + + type User @key(fields: "id organization { businessUnit {id name}}") { + id: ID + organization: Organization + } + + type Organization { + registrationNumber: ID + businessUnit: BusinessUnit + } + + type BusinessUnit { + id: ID + name: String + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_invalid_compound_primary_key_failures(): diff --git a/graphene_federation/tests/test_key_v1.py b/graphene_federation/tests/test_key_v1.py index d96e91e..a25f81b 100644 --- a/graphene_federation/tests/test_key_v1.py +++ b/graphene_federation/tests/test_key_v1.py @@ -1,11 +1,14 @@ +from textwrap import dedent + import pytest from graphql import graphql_sync from graphene import ObjectType, ID, String, Field -from ..entity import key -from ..main import build_schema +from graphene_federation.entity import key +from graphene_federation.main import build_schema +from graphene_federation.utils import clean_schema def test_multiple_keys(): @@ -19,27 +22,29 @@ class Query(ObjectType): user = Field(User) schema = build_schema(query=Query) - assert ( - str(schema).strip() - == """type Query { - user: User - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type User { - identifier: ID - email: String -} - -union _Entity = User - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + user: User + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type User { + identifier: ID + email: String + } + + union _Entity = User + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -50,19 +55,19 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type Query { - user: User -} - -type User @key(fields: "email") @key(fields: "identifier") { - identifier: ID - email: String -} -""".strip() + expected_result = dedent( + """ + type Query { + user: User + } + + type User @key(fields: "email") @key(fields: "identifier") { + identifier: ID + email: String + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_key_non_existing_field_failure(): diff --git a/graphene_federation/tests/test_provides.py b/graphene_federation/tests/test_provides.py index 7a14083..021f6c3 100644 --- a/graphene_federation/tests/test_provides.py +++ b/graphene_federation/tests/test_provides.py @@ -1,11 +1,14 @@ +from textwrap import dedent + from graphql import graphql_sync from graphene import Field, Int, ObjectType, String -from .. import external -from ..provides import provides -from ..main import build_schema -from ..extend import extend +from graphene_federation import external +from graphene_federation.provides import provides +from graphene_federation.main import build_schema +from graphene_federation.extend import extend +from graphene_federation.utils import clean_schema def test_provides(): @@ -28,33 +31,36 @@ class Query(ObjectType): in_stock_count = Field(InStockCount) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - inStockCount: InStockCount - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type InStockCount { - product: Product! - quantity: Int! -} - -type Product { - sku: String! - name: String - weight: Int -} - -union _Entity = Product - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + inStockCount: InStockCount + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type InStockCount { + product: Product! + quantity: Int! + } + + type Product { + sku: String! + name: String + weight: Int + } + + union _Entity = Product + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) + # Check the federation service schema definition language query = """ query { @@ -65,25 +71,26 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@provides"]) -type Query { - inStockCount: InStockCount -} - -type InStockCount { - product: Product! @provides(fields: "name") - quantity: Int! -} - -extend type Product @key(fields: "sku") { - sku: String! @external - name: String @external - weight: Int @external -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@provides"]) + type Query { + inStockCount: InStockCount + } + + type InStockCount { + product: Product! @provides(fields: "name") + quantity: Int! + } + + extend type Product @key(fields: "sku") { + sku: String! @external + name: String @external + weight: Int @external + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_provides_multiple_fields(): @@ -106,33 +113,35 @@ class Query(ObjectType): in_stock_count = Field(InStockCount) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - inStockCount: InStockCount - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type InStockCount { - product: Product! - quantity: Int! -} - -type Product { - sku: String! - name: String - weight: Int -} - -union _Entity = Product - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + inStockCount: InStockCount + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type InStockCount { + product: Product! + quantity: Int! + } + + type Product { + sku: String! + name: String + weight: Int + } + + union _Entity = Product + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -143,25 +152,26 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@provides"]) -type Query { - inStockCount: InStockCount -} - -type InStockCount { - product: Product! @provides(fields: "name weight") - quantity: Int! -} - -extend type Product @key(fields: "sku") { - sku: String! @external - name: String @external - weight: Int @external -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@provides"]) + type Query { + inStockCount: InStockCount + } + + type InStockCount { + product: Product! @provides(fields: "name weight") + quantity: Int! + } + + extend type Product @key(fields: "sku") { + sku: String! @external + name: String @external + weight: Int @external + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_provides_multiple_fields_as_list(): @@ -184,33 +194,36 @@ class Query(ObjectType): in_stock_count = Field(InStockCount) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - inStockCount: InStockCount - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type InStockCount { - product: Product! - quantity: Int! -} - -type Product { - sku: String! - name: String - weight: Int -} - -union _Entity = Product - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + inStockCount: InStockCount + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type InStockCount { + product: Product! + quantity: Int! + } + + type Product { + sku: String! + name: String + weight: Int + } + + union _Entity = Product + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) + # Check the federation service schema definition language query = """ query { @@ -221,22 +234,23 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@provides"]) -type Query { - inStockCount: InStockCount -} - -type InStockCount { - product: Product! @provides(fields: "name weight") - quantity: Int! -} - -extend type Product @key(fields: "sku") { - sku: String! @external - name: String @external - weight: Int @external -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@provides"]) + type Query { + inStockCount: InStockCount + } + + type InStockCount { + product: Product! @provides(fields: "name weight") + quantity: Int! + } + + extend type Product @key(fields: "sku") { + sku: String! @external + name: String @external + weight: Int @external + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/tests/test_provides_v1.py b/graphene_federation/tests/test_provides_v1.py index 8d118aa..c59649b 100644 --- a/graphene_federation/tests/test_provides_v1.py +++ b/graphene_federation/tests/test_provides_v1.py @@ -1,11 +1,14 @@ +from textwrap import dedent + from graphql import graphql_sync from graphene import Field, Int, ObjectType, String -from ..provides import provides -from ..main import build_schema -from ..extend import extend -from ..external import external +from graphene_federation.provides import provides +from graphene_federation.main import build_schema +from graphene_federation.extend import extend +from graphene_federation.external import external +from graphene_federation.utils import clean_schema def test_provides(): @@ -28,33 +31,35 @@ class Query(ObjectType): in_stock_count = Field(InStockCount) schema = build_schema(query=Query) - assert ( - str(schema).strip() - == """type Query { - inStockCount: InStockCount - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type InStockCount { - product: Product! - quantity: Int! -} - -type Product { - sku: String! - name: String - weight: Int -} - -union _Entity = Product - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + inStockCount: InStockCount + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type InStockCount { + product: Product! + quantity: Int! + } + + type Product { + sku: String! + name: String + weight: Int + } + + union _Entity = Product + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -65,25 +70,25 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type Query { - inStockCount: InStockCount -} - -type InStockCount { - product: Product! @provides(fields: "name") - quantity: Int! -} - -extend type Product @key(fields: "sku") { - sku: String! @external - name: String @external - weight: Int @external -} -""".strip() + expected_result = dedent( + """ + type Query { + inStockCount: InStockCount + } + + type InStockCount { + product: Product! @provides(fields: "name") + quantity: Int! + } + + extend type Product @key(fields: "sku") { + sku: String! @external + name: String @external + weight: Int @external + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_provides_multiple_fields(): @@ -106,33 +111,35 @@ class Query(ObjectType): in_stock_count = Field(InStockCount) schema = build_schema(query=Query) - assert ( - str(schema).strip() - == """type Query { - inStockCount: InStockCount - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type InStockCount { - product: Product! - quantity: Int! -} - -type Product { - sku: String! - name: String - weight: Int -} - -union _Entity = Product - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + inStockCount: InStockCount + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type InStockCount { + product: Product! + quantity: Int! + } + + type Product { + sku: String! + name: String + weight: Int + } + + union _Entity = Product + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -143,25 +150,25 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type Query { - inStockCount: InStockCount -} - -type InStockCount { - product: Product! @provides(fields: "name weight") - quantity: Int! -} - -extend type Product @key(fields: "sku") { - sku: String! @external - name: String @external - weight: Int @external -} -""".strip() + expected_result = dedent( + """ + type Query { + inStockCount: InStockCount + } + + type InStockCount { + product: Product! @provides(fields: "name weight") + quantity: Int! + } + + extend type Product @key(fields: "sku") { + sku: String! @external + name: String @external + weight: Int @external + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_provides_multiple_fields_as_list(): @@ -184,33 +191,35 @@ class Query(ObjectType): in_stock_count = Field(InStockCount) schema = build_schema(query=Query) - assert ( - str(schema).strip() - == """type Query { - inStockCount: InStockCount - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type InStockCount { - product: Product! - quantity: Int! -} - -type Product { - sku: String! - name: String - weight: Int -} - -union _Entity = Product - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + inStockCount: InStockCount + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type InStockCount { + product: Product! + quantity: Int! + } + + type Product { + sku: String! + name: String + weight: Int + } + + union _Entity = Product + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -221,22 +230,22 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type Query { - inStockCount: InStockCount -} - -type InStockCount { - product: Product! @provides(fields: "name weight") - quantity: Int! -} - -extend type Product @key(fields: "sku") { - sku: String! @external - name: String @external - weight: Int @external -} -""".strip() + expected_result = dedent( + """ + type Query { + inStockCount: InStockCount + } + + type InStockCount { + product: Product! @provides(fields: "name weight") + quantity: Int! + } + + extend type Product @key(fields: "sku") { + sku: String! @external + name: String @external + weight: Int @external + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/tests/test_requires.py b/graphene_federation/tests/test_requires.py index 59f6114..469c95d 100644 --- a/graphene_federation/tests/test_requires.py +++ b/graphene_federation/tests/test_requires.py @@ -1,12 +1,15 @@ +from textwrap import dedent + import pytest from graphql import graphql_sync from graphene import Field, ID, Int, ObjectType, String -from .. import external, requires -from ..extend import extend -from ..main import build_schema +from graphene_federation import external, requires +from graphene_federation.extend import extend +from graphene_federation.main import build_schema +from graphene_federation.utils import clean_schema def test_chain_requires_failure(): @@ -39,29 +42,31 @@ class Query(ObjectType): product = Field(Product) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - product: Product - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Product { - sku: ID - size: Int - weight: Int - shippingEstimate: String -} - -union _Entity = Product - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + product: Product + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Product { + sku: ID + size: Int + weight: Int + shippingEstimate: String + } + + union _Entity = Product + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -72,21 +77,22 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@requires"]) -type Query { - product: Product -} - -extend type Product @key(fields: "sku") { - sku: ID @external - size: Int @external - weight: Int @external - shippingEstimate: String @requires(fields: "size weight") -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@requires"]) + type Query { + product: Product + } + + extend type Product @key(fields: "sku") { + sku: ID @external + size: Int @external + weight: Int @external + shippingEstimate: String @requires(fields: "size weight") + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_requires_multiple_fields_as_list(): @@ -105,29 +111,31 @@ class Query(ObjectType): product = Field(Product) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - product: Product - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Product { - sku: ID - size: Int - weight: Int - shippingEstimate: String -} - -union _Entity = Product - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + product: Product + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Product { + sku: ID + size: Int + weight: Int + shippingEstimate: String + } + + union _Entity = Product + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -138,21 +146,22 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@requires"]) -type Query { - product: Product -} - -extend type Product @key(fields: "sku") { - sku: ID @external - size: Int @external - weight: Int @external - shippingEstimate: String @requires(fields: "size weight") -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@requires"]) + type Query { + product: Product + } + + extend type Product @key(fields: "sku") { + sku: ID @external + size: Int @external + weight: Int @external + shippingEstimate: String @requires(fields: "size weight") + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_requires_with_input(): @@ -170,28 +179,30 @@ class Query(ObjectType): acme = Field(Acme) schema = build_schema(query=Query, enable_federation_2=True) - assert ( - str(schema).strip() - == """type Query { - acme: Acme - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Acme { - id: ID! - age: Int - foo(someInput: String): String -} - -union _Entity = Acme - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + acme: Acme + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Acme { + id: ID! + age: Int + foo(someInput: String): String + } + + union _Entity = Acme + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -202,17 +213,18 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@requires"]) -type Query { - acme: Acme -} - -extend type Acme @key(fields: "id") { - id: ID! @external - age: Int @external - foo(someInput: String): String @requires(fields: "age") -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key", "@requires"]) + type Query { + acme: Acme + } + + extend type Acme @key(fields: "id") { + id: ID! @external + age: Int @external + foo(someInput: String): String @requires(fields: "age") + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/tests/test_requires_v1.py b/graphene_federation/tests/test_requires_v1.py index c82f019..3505473 100644 --- a/graphene_federation/tests/test_requires_v1.py +++ b/graphene_federation/tests/test_requires_v1.py @@ -1,13 +1,16 @@ +from textwrap import dedent + import pytest from graphql import graphql_sync from graphene import Field, ID, Int, ObjectType, String -from ..extend import extend -from ..external import external -from ..requires import requires -from ..main import build_schema +from graphene_federation.extend import extend +from graphene_federation.external import external +from graphene_federation.requires import requires +from graphene_federation.main import build_schema +from graphene_federation.utils import clean_schema def test_chain_requires_failure(): @@ -40,29 +43,31 @@ class Query(ObjectType): product = Field(Product) schema = build_schema(query=Query) - assert ( - str(schema).strip() - == """type Query { - product: Product - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Product { - sku: ID - size: Int - weight: Int - shippingEstimate: String -} - -union _Entity = Product - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + product: Product + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Product { + sku: ID + size: Int + weight: Int + shippingEstimate: String + } + + union _Entity = Product + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -73,21 +78,21 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type Query { - product: Product -} - -extend type Product @key(fields: "sku") { - sku: ID @external - size: Int @external - weight: Int @external - shippingEstimate: String @requires(fields: "size weight") -} -""".strip() + expected_result = dedent( + """ + type Query { + product: Product + } + + extend type Product @key(fields: "sku") { + sku: ID @external + size: Int @external + weight: Int @external + shippingEstimate: String @requires(fields: "size weight") + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_requires_multiple_fields_as_list(): @@ -106,29 +111,31 @@ class Query(ObjectType): product = Field(Product) schema = build_schema(query=Query) - assert ( - str(schema).strip() - == """type Query { - product: Product - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Product { - sku: ID - size: Int - weight: Int - shippingEstimate: String -} - -union _Entity = Product - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + product: Product + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Product { + sku: ID + size: Int + weight: Int + shippingEstimate: String + } + + union _Entity = Product + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -139,21 +146,21 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type Query { - product: Product -} - -extend type Product @key(fields: "sku") { - sku: ID @external - size: Int @external - weight: Int @external - shippingEstimate: String @requires(fields: "size weight") -} -""".strip() + expected_result = dedent( + """ + type Query { + product: Product + } + + extend type Product @key(fields: "sku") { + sku: ID @external + size: Int @external + weight: Int @external + shippingEstimate: String @requires(fields: "size weight") + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_requires_with_input(): @@ -171,28 +178,30 @@ class Query(ObjectType): acme = Field(Acme) schema = build_schema(query=Query) - assert ( - str(schema).strip() - == """type Query { - acme: Acme - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type Acme { - id: ID! - age: Int - foo(someInput: String): String -} - -union _Entity = Acme - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + type Query { + acme: Acme + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Acme { + id: ID! + age: Int + foo(someInput: String): String + } + + union _Entity = Acme + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(schema) == clean_schema(expected_result) # Check the federation service schema definition language query = """ query { @@ -203,17 +212,17 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type Query { - acme: Acme -} - -extend type Acme @key(fields: "id") { - id: ID! @external - age: Int @external - foo(someInput: String): String @requires(fields: "age") -} -""".strip() + expected_result = dedent( + """ + type Query { + acme: Acme + } + + extend type Acme @key(fields: "id") { + id: ID! @external + age: Int @external + foo(someInput: String): String @requires(fields: "age") + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/tests/test_scalar.py b/graphene_federation/tests/test_scalar.py index 7539d2f..e80a2d0 100644 --- a/graphene_federation/tests/test_scalar.py +++ b/graphene_federation/tests/test_scalar.py @@ -1,3 +1,4 @@ +from textwrap import dedent from typing import Any import graphene @@ -5,6 +6,7 @@ from graphql import graphql_sync from graphene_federation import build_schema, shareable, inaccessible +from graphene_federation.utils import clean_schema def test_custom_scalar(): @@ -40,18 +42,20 @@ class Query(ObjectType): } """ result = graphql_sync(schema.graphql_schema, query) - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible", "@shareable"]) -type TestScalar @shareable { - testShareableScalar(x: AddressScalar): String @shareable - testInaccessibleScalar(x: AddressScalar): String @inaccessible -} - -scalar AddressScalar - -type Query { - test(x: AddressScalar): String - test2: [AddressScalar]! -}""" + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible", "@shareable"]) + type TestScalar @shareable { + testShareableScalar(x: AddressScalar): String @shareable + testInaccessibleScalar(x: AddressScalar): String @inaccessible + } + + scalar AddressScalar + + type Query { + test(x: AddressScalar): String + test2: [AddressScalar]! + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/tests/test_schema_annotation.py b/graphene_federation/tests/test_schema_annotation.py index 3c37dd5..7de4fe4 100644 --- a/graphene_federation/tests/test_schema_annotation.py +++ b/graphene_federation/tests/test_schema_annotation.py @@ -1,11 +1,14 @@ +from textwrap import dedent + from graphql import graphql_sync from graphene import ObjectType, ID, String, NonNull, Field -from .. import external -from ..entity import key -from ..extend import extend -from ..main import build_schema +from graphene_federation import external +from graphene_federation.entity import key +from graphene_federation.extend import extend +from graphene_federation.main import build_schema +from graphene_federation.utils import clean_schema # ------------------------ # User service @@ -89,32 +92,35 @@ def test_user_schema(): Check that the user schema has been annotated correctly and that a request to retrieve a user works. """ - assert ( - str(user_schema).strip() - == """schema { - query: UserQuery -} - -type UserQuery { - user(userId: ID!): User - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type User { - userId: ID! - email: String! - name: String -} - -union _Entity = User - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + schema { + query: UserQuery + } + + type UserQuery { + user(userId: ID!): User + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type User { + userId: ID! + email: String! + name: String + } + + union _Entity = User + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(user_schema) == clean_schema(expected_result) + query = """ query { user(userId: "2") { @@ -135,22 +141,22 @@ def test_user_schema(): """ result = graphql_sync(user_schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) - - -type UserQuery { - user(userId: ID!): User -} - -type User @key(fields: "email") @key(fields: "userId") { - userId: ID! - email: String! - name: String -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) + + type UserQuery { + user(userId: ID!): User + } + + type User @key(fields: "email") @key(fields: "userId") { + userId: ID! + email: String! + name: String + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_chat_schema(): @@ -158,37 +164,39 @@ def test_chat_schema(): Check that the chat schema has been annotated correctly and that a request to retrieve a chat message works. """ - assert ( - str(chat_schema).strip() - == """schema { - query: ChatQuery -} - -type ChatQuery { - message(id: ID!): ChatMessage - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type ChatMessage { - id: ID! - text: String - userId: ID - user: ChatUser! -} - -type ChatUser { - userId: ID! -} - -union _Entity = ChatUser - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + schema { + query: ChatQuery + } + + type ChatQuery { + message(id: ID!): ChatMessage + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type ChatMessage { + id: ID! + text: String + userId: ID + user: ChatUser! + } + + type ChatUser { + userId: ID! + } + + union _Entity = ChatUser + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(chat_schema) == clean_schema(expected_result) # Query the message field query = """ @@ -213,24 +221,23 @@ def test_chat_schema(): """ result = graphql_sync(chat_schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key"]) - - -type ChatQuery { - message(id: ID!): ChatMessage -} - -type ChatMessage { - id: ID! - text: String - userId: ID - user: ChatUser! -} - -extend type ChatUser @key(fields: "userId") { - userId: ID! @external -} -""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@extends", "@external", "@key"]) + type ChatQuery { + message(id: ID!): ChatMessage + } + + type ChatMessage { + id: ID! + text: String + userId: ID + user: ChatUser! + } + + extend type ChatUser @key(fields: "userId") { + userId: ID! @external + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/tests/test_schema_annotation_v1.py b/graphene_federation/tests/test_schema_annotation_v1.py index 25d726c..3ef2602 100644 --- a/graphene_federation/tests/test_schema_annotation_v1.py +++ b/graphene_federation/tests/test_schema_annotation_v1.py @@ -1,11 +1,14 @@ +from textwrap import dedent + from graphql import graphql_sync from graphene import ObjectType, ID, String, NonNull, Field -from ..entity import key -from ..extend import extend -from ..external import external -from ..main import build_schema +from graphene_federation.entity import key +from graphene_federation.extend import extend +from graphene_federation.external import external +from graphene_federation.main import build_schema +from graphene_federation.utils import clean_schema # ------------------------ # User service @@ -88,32 +91,34 @@ def test_user_schema(): Check that the user schema has been annotated correctly and that a request to retrieve a user works. """ - assert ( - str(user_schema).strip() - == """schema { - query: UserQuery -} - -type UserQuery { - user(userId: ID!): User - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type User { - userId: ID! - email: String! - name: String -} - -union _Entity = User - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + schema { + query: UserQuery + } + + type UserQuery { + user(userId: ID!): User + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type User { + userId: ID! + email: String! + name: String + } + + union _Entity = User + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(user_schema) == clean_schema(expected_result) query = """ query { user(userId: "2") { @@ -134,20 +139,20 @@ def test_user_schema(): """ result = graphql_sync(user_schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type UserQuery { - user(userId: ID!): User -} - -type User @key(fields: "email") @key(fields: "userId") { - userId: ID! - email: String! - name: String -} -""".strip() + expected_result = dedent( + """ + type UserQuery { + user(userId: ID!): User + } + + type User @key(fields: "email") @key(fields: "userId") { + userId: ID! + email: String! + name: String + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_chat_schema(): @@ -155,37 +160,39 @@ def test_chat_schema(): Check that the chat schema has been annotated correctly and that a request to retrieve a chat message works. """ - assert ( - str(chat_schema).strip() - == """schema { - query: ChatQuery -} - -type ChatQuery { - message(id: ID!): ChatMessage - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type ChatMessage { - id: ID! - text: String - userId: ID - user: ChatUser! -} - -type ChatUser { - userId: ID! -} - -union _Entity = ChatUser - -scalar _Any - -type _Service { - sdl: String -}""" + expected_result = dedent( + """ + schema { + query: ChatQuery + } + + type ChatQuery { + message(id: ID!): ChatMessage + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type ChatMessage { + id: ID! + text: String + userId: ID + user: ChatUser! + } + + type ChatUser { + userId: ID! + } + + union _Entity = ChatUser + + scalar _Any + + type _Service { + sdl: String + } + """ ) + assert clean_schema(chat_schema) == clean_schema(expected_result) # Query the message field query = """ @@ -210,22 +217,22 @@ def test_chat_schema(): """ result = graphql_sync(chat_schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """ -type ChatQuery { - message(id: ID!): ChatMessage -} - -type ChatMessage { - id: ID! - text: String - userId: ID - user: ChatUser! -} - -extend type ChatUser @key(fields: "userId") { - userId: ID! @external -} -""".strip() + expected_result = dedent( + """ + type ChatQuery { + message(id: ID!): ChatMessage + } + + type ChatMessage { + id: ID! + text: String + userId: ID + user: ChatUser! + } + + extend type ChatUser @key(fields: "userId") { + userId: ID! @external + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/tests/test_shareable.py b/graphene_federation/tests/test_shareable.py index 76a9daa..9d28b5a 100644 --- a/graphene_federation/tests/test_shareable.py +++ b/graphene_federation/tests/test_shareable.py @@ -1,9 +1,12 @@ +from textwrap import dedent + import graphene import pytest from graphene import ObjectType from graphql import graphql_sync -from .. import shareable, build_schema +from graphene_federation import shareable, build_schema +from graphene_federation.utils import clean_schema @pytest.mark.xfail( @@ -53,18 +56,20 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@shareable"]) -type Position @shareable { - x: Int! - y: Int! @shareable -} - -type Query { - inStockCount: Int! -}""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@shareable"]) + type Position @shareable { + x: Int! + y: Int! @shareable + } + + type Query { + inStockCount: Int! + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) def test_shareable_union(): @@ -102,27 +107,29 @@ class Query(ObjectType): """ result = graphql_sync(schema.graphql_schema, query) assert not result.errors - assert ( - result.data["_service"]["sdl"].strip() - == """extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@shareable"]) -union SearchResult @shareable = Human | Droid | Starship - -type Human @shareable { - name: String - bornIn: String -} - -type Droid @shareable { - name: String @shareable - primaryFunction: String -} - -type Starship @shareable { - name: String - length: Int @shareable -} - -type Query { - inStockCount: Int! -}""".strip() + expected_result = dedent( + """ + extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@shareable"]) + union SearchResult @shareable = Human | Droid | Starship + + type Human @shareable { + name: String + bornIn: String + } + + type Droid @shareable { + name: String @shareable + primaryFunction: String + } + + type Starship @shareable { + name: String + length: Int @shareable + } + + type Query { + inStockCount: Int! + } + """ ) + assert clean_schema(result.data["_service"]["sdl"]) == clean_schema(expected_result) diff --git a/graphene_federation/utils.py b/graphene_federation/utils.py index 917ce52..66bb6e6 100644 --- a/graphene_federation/utils.py +++ b/graphene_federation/utils.py @@ -1,3 +1,4 @@ +import re from typing import Any, Callable, List, Tuple import graphene @@ -102,3 +103,7 @@ def get_attributed_fields(attribute: str, schema: Schema): fields[type_name] = type_.graphene_type continue return fields + + +def clean_schema(schema): + return re.sub(r"[ \n]+", " ", str(schema)).strip()