Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/reduce code duplication in plugins #31

Open
wants to merge 18 commits into
base: refactored
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added tests/plugins/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions tests/plugins/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pytest

from turms.config import GeneratorConfig
from turms.plugins.enums import EnumsPlugin
from turms.plugins.inputs import InputsPlugin
from turms.plugins.objects import ObjectsPlugin
from turms.registry import ClassRegistry
from turms.stylers.default import DefaultStyler


@pytest.fixture
def enums_plugin():
return EnumsPlugin()


@pytest.fixture
def inputs_plugin():
return InputsPlugin()


@pytest.fixture
def objects_plugin():
return ObjectsPlugin()


@pytest.fixture
def config():
return GeneratorConfig()


@pytest.fixture
def registry(config):
return ClassRegistry(config, stylers=[DefaultStyler()])
85 changes: 85 additions & 0 deletions tests/plugins/test_inputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import pytest

from .utils import (
build_schema_from_sdl,
re_patterns_in_python_ast,
python_from_ast,
TestCase,
TestCaseGenerator,
)
from turms.errors import NoEnumFound


class InputTypeTestCaseGenerator(TestCaseGenerator):
gql_type_identifier = "input"


@pytest.mark.parametrize(
["test_case"],
InputTypeTestCaseGenerator.make_test_cases_primitive_field_value()
+ InputTypeTestCaseGenerator.make_test_cases_nested_field_value()
+ InputTypeTestCaseGenerator.make_test_cases_description()
+ InputTypeTestCaseGenerator.make_test_cases_deprecation()
+ InputTypeTestCaseGenerator.make_test_cases_is_keyword(),
)
def test_input(test_case: TestCase, inputs_plugin, config, registry):
schema = build_schema_from_sdl(test_case.sdl)
inputs_ast = inputs_plugin.generate_ast(schema, config, registry)
assert re_patterns_in_python_ast(
test_case.expected_re_patterns, inputs_ast
), python_from_ast(inputs_ast)


@pytest.mark.parametrize(
["test_case"], InputTypeTestCaseGenerator.make_test_cases_enum_field_value()
)
def test_input_enum_without_enum_plugin(test_case, inputs_plugin, config, registry):
schema = build_schema_from_sdl(test_case.sdl)
with pytest.raises(NoEnumFound):
inputs_plugin.generate_ast(schema, config, registry)


@pytest.mark.parametrize(
["test_case"], InputTypeTestCaseGenerator.make_test_cases_enum_field_value()
)
def test_input_enum_with_enum_plugin(
test_case, enums_plugin, inputs_plugin, config, registry
):
schema = build_schema_from_sdl(test_case.sdl)
enums_ast = enums_plugin.generate_ast(schema, config, registry)
inputs_ast = inputs_plugin.generate_ast(schema, config, registry)
assert re_patterns_in_python_ast(
test_case.expected_re_patterns, enums_ast + inputs_ast
), python_from_ast(enums_ast + inputs_ast)


@pytest.mark.parametrize(
["test_case"],
InputTypeTestCaseGenerator.make_test_cases_skip_underscore(should_skip=True)
+ InputTypeTestCaseGenerator.make_test_cases_skip_double_underscore(
should_skip=True
),
)
def test_skip_underscore(test_case: TestCase, inputs_plugin, config, registry):
schema = build_schema_from_sdl(test_case.sdl)
inputs_plugin.config.skip_underscore = True
inputs_ast = inputs_plugin.generate_ast(schema, config, registry)
assert re_patterns_in_python_ast(
test_case.expected_re_patterns, inputs_ast
), python_from_ast(inputs_ast)


@pytest.mark.parametrize(
["test_case"],
InputTypeTestCaseGenerator.make_test_cases_skip_underscore(should_skip=False)
+ InputTypeTestCaseGenerator.make_test_cases_skip_double_underscore(
should_skip=False
),
)
def test_ignore_underscore(test_case: TestCase, inputs_plugin, config, registry):
schema = build_schema_from_sdl(test_case.sdl)
inputs_plugin.config.skip_underscore = False
inputs_ast = inputs_plugin.generate_ast(schema, config, registry)
assert re_patterns_in_python_ast(
test_case.expected_re_patterns, inputs_ast
), python_from_ast(inputs_ast)
279 changes: 279 additions & 0 deletions tests/plugins/test_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
from typing import List

import pytest

from .utils import (
build_schema_from_sdl,
re_patterns_in_python_ast,
python_from_ast,
TestCase,
TestCaseGenerator,
re_token_can_be_forward_reference,
re_token_one_of_base_classes,
)
from turms.errors import NoEnumFound, GenerationError


class ObjectTypeTestCaseGenerator(TestCaseGenerator):
gql_type_identifier = "type"

@classmethod
def make_test_cases_union_field_value(cls) -> List[List[TestCase]]:
"""
Todo: This test case points out a possible design-weakness for generating unions.
Both versions mentioned below, are technically identical and differ only in semantics!

Currently, the Union is generated by replacing it with the possible values.

class C(BaseModel):
bar: Union[A, B]

Wouldn't it be better to generate the following, to mirror the GraphQL-syntax?
Otherwise, you couldn't reference AorB by its name for typing.

class C(BaseModel):
bar: 'AorB'

AorB = Union[A, B]
"""
union_ab_sdl = """
type A {
foo: String!
}

type B {
foo: Int!
}

union AorB = A | B
"""
union_ab_regexes = [
r"class A",
r"foo: str",
r"class B",
r"foo: int",
]

test_cases = [
TestCase(
sdl=union_ab_sdl
+ """
type MandatoryUnion {
bar: AorB!
}
""",
expected_re_patterns=union_ab_regexes
+ [
r"class MandatoryUnion",
(
fr"bar: Union\["
fr"{re_token_can_be_forward_reference('A')}, "
fr"{re_token_can_be_forward_reference('B')}"
r"\]"
),
],
),
TestCase(
sdl=union_ab_sdl
+ """
type OptionalUnion {
bar: AorB
}
""",
expected_re_patterns=union_ab_regexes
+ [
r"class OptionalUnion",
(
fr"bar: Optional\[Union\["
fr"{re_token_can_be_forward_reference('A')}, "
fr"{re_token_can_be_forward_reference('B')}"
r"\]\]"
),
],
),
]
return cls._format_list_for_parametrize(test_cases)


class InterfaceTypeTestCaseGenerator(TestCaseGenerator):
gql_type_identifier = "interface"


@pytest.mark.parametrize(
["test_case"],
ObjectTypeTestCaseGenerator.make_test_cases_primitive_field_value()
+ ObjectTypeTestCaseGenerator.make_test_cases_nested_field_value()
+ ObjectTypeTestCaseGenerator.make_test_cases_description()
+ ObjectTypeTestCaseGenerator.make_test_cases_deprecation()
+ ObjectTypeTestCaseGenerator.make_test_cases_is_keyword(),
)
def test_objects(test_case: TestCase, objects_plugin, config, registry):
schema = build_schema_from_sdl(test_case.sdl)
inputs_ast = objects_plugin.generate_ast(schema, config, registry)
assert re_patterns_in_python_ast(
test_case.expected_re_patterns, inputs_ast
), python_from_ast(inputs_ast)


@pytest.mark.parametrize(
["test_case"],
InterfaceTypeTestCaseGenerator.make_test_cases_primitive_field_value()
+ InterfaceTypeTestCaseGenerator.make_test_cases_nested_field_value()
+ InterfaceTypeTestCaseGenerator.make_test_cases_description()
+ InterfaceTypeTestCaseGenerator.make_test_cases_deprecation()
+ InterfaceTypeTestCaseGenerator.make_test_cases_is_keyword(),
)
def test_interfaces__implementation_not_required(
test_case: TestCase, objects_plugin, config, registry
):
config.always_resolve_interfaces = False
schema = build_schema_from_sdl(test_case.sdl)
inputs_ast = objects_plugin.generate_ast(schema, config, registry)
assert re_patterns_in_python_ast(
test_case.expected_re_patterns, inputs_ast
), python_from_ast(inputs_ast)


@pytest.mark.parametrize(
["test_case"],
InterfaceTypeTestCaseGenerator.make_test_cases_primitive_field_value(),
)
def test_interfaces__trigger_always_resolve(
test_case: TestCase, objects_plugin, config, registry
):
schema = build_schema_from_sdl(test_case.sdl)
with pytest.raises(GenerationError):
objects_plugin.generate_ast(schema, config, registry)


@pytest.mark.parametrize(
["test_case"], ObjectTypeTestCaseGenerator.make_test_cases_union_field_value()
)
def test_object_with_union(test_case: TestCase, objects_plugin, config, registry):
schema = build_schema_from_sdl(test_case.sdl)
ast = objects_plugin.generate_ast(schema, config, registry)
assert re_patterns_in_python_ast(
test_case.expected_re_patterns, ast
), python_from_ast(ast)


@pytest.mark.parametrize(
["test_case"], ObjectTypeTestCaseGenerator.make_test_cases_enum_field_value()
)
def test_objects_without_enum_plugin(
test_case: TestCase, objects_plugin, config, registry
):
schema = build_schema_from_sdl(test_case.sdl)
with pytest.raises(NoEnumFound):
objects_plugin.generate_ast(schema, config, registry)


@pytest.mark.parametrize(
["test_case"], ObjectTypeTestCaseGenerator.make_test_cases_enum_field_value()
)
def test_objects_with_enum_plugin(
test_case: TestCase, enums_plugin, objects_plugin, config, registry
):
schema = build_schema_from_sdl(test_case.sdl)
enum_ast = enums_plugin.generate_ast(schema, config, registry)
objects_ast = objects_plugin.generate_ast(schema, config, registry)
ast = enum_ast + objects_ast
assert re_patterns_in_python_ast(
test_case.expected_re_patterns, ast
), python_from_ast(ast)


def test_interface_and_implementation(objects_plugin, config, registry):
sdl = """
interface Interface {
test: String!
}
type Implementation implements Interface {
test: String!
}
"""
schema = build_schema_from_sdl(sdl)
ast = objects_plugin.generate_ast(schema, config, registry)
assert re_patterns_in_python_ast(
[
r"class InterfaceBase",
r"test: str",
fr"class Implementation{re_token_one_of_base_classes('InterfaceBase')}",
r"Interface = Union\[Implementation,\]",
],
ast,
), python_from_ast(ast)


def test_multiple_interface_mro(objects_plugin, config, registry):
sdl = """interface A {
foo: String!
}

interface B implements A {
foo: String!
bar: String!
}

type ThisWorks implements B & A {
foo: String!
bar: String!
}

type ThisDoesnt implements A & B {
foo: String!
bar: String!
}"""
schema = build_schema_from_sdl(sdl)
ast = objects_plugin.generate_ast(schema, config, registry)
assert re_patterns_in_python_ast(
[
r"foo: str",
r"bar: str",
r"class ABase",
fr"class BBase{re_token_one_of_base_classes('ABase')}",
fr"class ThisWorks{re_token_one_of_base_classes('BBase')}",
fr"class ThisDoesnt{re_token_one_of_base_classes('BBase')}",
r"A = Union\[BBase, ThisDoesnt, ThisWorks\]",
r"B = Union\[ThisDoesnt, ThisWorks\]",
],
ast,
), python_from_ast(ast)


@pytest.mark.parametrize(
["test_case"],
ObjectTypeTestCaseGenerator.make_test_cases_skip_underscore(should_skip=True)
+ ObjectTypeTestCaseGenerator.make_test_cases_skip_double_underscore(
should_skip=True
),
)
def test_objects_plugin_skip_underscores(
test_case: TestCase, objects_plugin, config, registry
):
objects_plugin.config.skip_underscore = True
objects_plugin.config.skip_double_underscore = True
schema = build_schema_from_sdl(test_case.sdl)
ast = objects_plugin.generate_ast(schema, config, registry)
assert re_patterns_in_python_ast(
test_case.expected_re_patterns, ast
), python_from_ast(ast)


@pytest.mark.parametrize(
["test_case"],
ObjectTypeTestCaseGenerator.make_test_cases_skip_underscore(should_skip=False)
+ ObjectTypeTestCaseGenerator.make_test_cases_skip_double_underscore(
should_skip=False
),
)
def test_objects_plugin_ignore_underscores(
test_case: TestCase, objects_plugin, config, registry
):
objects_plugin.config.skip_underscore = False
objects_plugin.config.skip_double_underscore = False
schema = build_schema_from_sdl(test_case.sdl)
ast = objects_plugin.generate_ast(schema, config, registry)
assert re_patterns_in_python_ast(
test_case.expected_re_patterns, ast
), python_from_ast(ast)
Loading