Completed
Push — master ( 6cc2de...ecdd96 )
by Adrien
02:40
created

TypesTest::testCanDeclareArrayOfEntity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQLTests\Doctrine;
6
7
use DateTime;
8
use Doctrine\ORM\Tools\SchemaValidator;
9
use GraphQL\Doctrine\Types;
10
use GraphQL\Type\Definition\BooleanType;
11
use GraphQL\Type\Definition\InputObjectType;
12
use GraphQL\Type\Definition\ObjectType;
13
use GraphQL\Type\Definition\Type;
14
use GraphQL\Type\Schema;
15
use GraphQLTests\Doctrine\Blog\Model\Post;
16
use GraphQLTests\Doctrine\Blog\Model\User;
17
use GraphQLTests\Doctrine\Blog\Types\CustomType;
18
use GraphQLTests\Doctrine\Blog\Types\DateTimeType;
19
use GraphQLTests\Doctrine\Blog\Types\PostStatusType;
20
use stdClass;
21
22
class TypesTest extends \PHPUnit\Framework\TestCase
23
{
24
    use EntityManagerTrait;
25
26
    /**
27
     * @var Types
28
     */
29
    private $types;
30
31
    public function setUp(): void
32
    {
33
        $this->setUpEntityManager();
34
35
        $mapping = [
36
            DateTime::class => DateTimeType::class,
37
            stdClass::class => CustomType::class,
38
        ];
39
        $this->types = new Types($this->entityManager, $mapping);
40
    }
41
42
    public function testBlogMapping(): void
43
    {
44
        $validator = new SchemaValidator($this->entityManager);
45
        $errors = $validator->validateMapping();
46
47
        self::assertEmpty($errors, 'doctrine annotations should be valid');
48
    }
49
50
    public function testGraphQLSchemaFromDocumentationMustBeValid(): void
51
    {
52
        $schema = new Schema([
53
            'query' => new ObjectType([
54
                'name' => 'query',
55
                'fields' => [
56
                    'users' => [
57
                        'type' => Type::listOf($this->types->get(User::class)), // Use automated ObjectType for output
58
                        'resolve' => function ($root, $args): void {
2 ignored issues
show
Unused Code introduced by
The parameter $root is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

58
                        'resolve' => function (/** @scrutinizer ignore-unused */ $root, $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $args is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

58
                        'resolve' => function ($root, /** @scrutinizer ignore-unused */ $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
59
                            // call to repository...
60
                        },
61
                    ],
62
                    'posts' => [
63
                        'type' => Type::listOf($this->types->get(Post::class)), // Use automated ObjectType for output
64
                        'resolve' => function ($root, $args): void {
2 ignored issues
show
Unused Code introduced by
The parameter $root is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

64
                        'resolve' => function (/** @scrutinizer ignore-unused */ $root, $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $args is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

64
                        'resolve' => function ($root, /** @scrutinizer ignore-unused */ $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
65
                            // call to repository...
66
                        },
67
                    ],
68
                ],
69
            ]),
70
            'mutation' => new ObjectType([
71
                'name' => 'mutation',
72
                'fields' => [
73
                    'createUser' => [
74
                        'type' => Type::nonNull($this->types->get(User::class)),
75
                        'args' => [
76
                            'input' => Type::nonNull($this->types->getInput(User::class)), // Use automated InputObjectType for input
77
                        ],
78
                        'resolve' => function ($root, $args): void {
2 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

78
                        'resolve' => function ($root, /** @scrutinizer ignore-unused */ $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $root is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

78
                        'resolve' => function (/** @scrutinizer ignore-unused */ $root, $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
79
                            // create new user and flush...
80
                        },
81
                    ],
82
                    'updateUser' => [
83
                        'type' => Type::nonNull($this->types->get(User::class)),
84
                        'args' => [
85
                            'id' => Type::nonNull(Type::id()), // Use standard API when needed
86
                            'input' => $this->types->getInput(User::class),
87
                        ],
88
                        'resolve' => function ($root, $args): void {
2 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

88
                        'resolve' => function ($root, /** @scrutinizer ignore-unused */ $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $root is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

88
                        'resolve' => function (/** @scrutinizer ignore-unused */ $root, $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
89
                            // update existing user and flush...
90
                        },
91
                    ],
92
                    'createPost' => [
93
                        'type' => Type::nonNull($this->types->get(Post::class)),
94
                        'args' => [
95
                            'input' => Type::nonNull($this->types->getInput(Post::class)), // Use automated InputObjectType for input
96
                        ],
97
                        'resolve' => function ($root, $args): void {
2 ignored issues
show
Unused Code introduced by
The parameter $root is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

97
                        'resolve' => function (/** @scrutinizer ignore-unused */ $root, $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $args is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

97
                        'resolve' => function ($root, /** @scrutinizer ignore-unused */ $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
98
                            // create new post and flush...
99
                        },
100
                    ],
101
                    'updatePost' => [
102
                        'type' => Type::nonNull($this->types->get(Post::class)),
103
                        'args' => [
104
                            'id' => Type::nonNull(Type::id()), // Use standard API when needed
105
                            'input' => $this->types->getInput(Post::class),
106
                        ],
107
                        'resolve' => function ($root, $args): void {
2 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

107
                        'resolve' => function ($root, /** @scrutinizer ignore-unused */ $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $root is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

107
                        'resolve' => function (/** @scrutinizer ignore-unused */ $root, $args): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
108
                            // update existing post and flush...
109
                        },
110
                    ],
111
                ],
112
            ]),
113
        ]);
114
115
        $schema->assertValid();
116
        self::assertTrue(true, 'passed validation successfully');
117
    }
118
119
    public function testCanGetUserDefinedScalarTypes(): void
120
    {
121
        $bool = $this->types->get(BooleanType::class);
122
        $status = $this->types->get(PostStatusType::class);
123
124
        self::assertInstanceOf(BooleanType::class, $bool, 'must be a instance of bool');
125
        self::assertInstanceOf(PostStatusType::class, $status, 'must be an instance of post status');
126
127
        self::assertSame($bool, $this->types->get(BooleanType::class), 'must returns the same instance of bool');
128
        self::assertSame($status, $this->types->get(PostStatusType::class), 'must returns the same instance of post status');
129
    }
130
131
    public function testCanGetUserMappedTypes(): void
132
    {
133
        $type = $this->types->get(stdClass::class);
134
135
        self::assertInstanceOf(CustomType::class, $type, 'must be a instance of CustomType');
136
        self::assertSame($type, $this->types->get('customName'));
137
    }
138
139
    public function testCanGetTypesWithBackslashPrefix(): void
140
    {
141
        $type = $this->types->get(stdClass::class);
142
        self::assertSame($type, $this->types->get('\stdClass'));
143
    }
144
145
    public function testCanGetOutputTypes(): void
146
    {
147
        $userType = $this->types->get(User::class);
148
149
        $this->assertObjectType('data/UserOutput.php', $userType);
150
        self::assertSame($userType, $this->types->get(User::class), 'must returns the same instance of user type');
151
152
        $postType = $this->types->get(Post::class);
153
        $this->assertObjectType('data/PostOutput.php', $postType);
154
        self::assertSame($postType, $this->types->get(Post::class), 'must returns the same instance of post type');
155
    }
156
157
    public function testCanGetInputTypes(): void
158
    {
159
        $userType = $this->types->getInput(User::class);
160
        $this->assertInputType('data/UserInput.php', $userType);
161
        self::assertSame($userType, $this->types->getInput(User::class), 'must returns the same instance of user type');
162
163
        $postType = $this->types->getInput(Post::class);
164
        $this->assertInputType('data/PostInput.php', $postType);
165
        self::assertSame($postType, $this->types->getInput(Post::class), 'must returns the same instance of post type');
166
    }
167
168
    private function assertType(string $expectedFile, Type $type, bool $assertArgs): void
169
    {
170
        $fields = [];
171
        foreach ($type->getFields() as $field) {
0 ignored issues
show
Bug introduced by
The method getFields() does not exist on GraphQL\Type\Definition\Type. It seems like you code against a sub-type of GraphQL\Type\Definition\Type such as GraphQL\Type\Definition\InterfaceType or GraphQL\Type\Definition\ObjectType or GraphQL\Type\Definition\InputObjectType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

171
        foreach ($type->/** @scrutinizer ignore-call */ getFields() as $field) {
Loading history...
172
            $data = [
173
                'name' => $field->name,
174
                'type' => $field->getType()->toString(),
175
                'description' => $field->description,
176
            ];
177
178
            if ($assertArgs) {
179
                $args = [];
180
                foreach ($field->args as $arg) {
181
                    $args[] = [
182
                        'name' => $arg->name,
183
                        'type' => $arg->getType()->toString(),
184
                        'description' => $arg->description,
185
                        'defaultValue' => $arg->defaultValue,
186
                    ];
187
                }
188
                $data['args'] = $args;
189
            } else {
190
                $data['defaultValue'] = $field->defaultValue;
191
            }
192
193
            $fields[] = $data;
194
        }
195
196
        $actual = [
197
            'name' => $type->name,
198
            'description' => $type->description,
199
            'fields' => $fields,
200
        ];
201
202
        $expected = require $expectedFile;
203
        self::assertEquals($expected, $actual, 'Should equals expectation from: ' . $expectedFile);
204
    }
205
206
    private function assertInputType(string $expectedFile, InputObjectType $type): void
207
    {
208
        $this->assertType($expectedFile, $type, false);
209
    }
210
211
    private function assertObjectType(string $expectedFile, ObjectType $type): void
212
    {
213
        $this->assertType($expectedFile, $type, true);
214
    }
215
216
    public function testNonPublicGetterMustBeIgnored(): void
217
    {
218
        $actual = $this->types->get(Blog\Model\Special\IgnoredGetter::class);
219
        $this->assertObjectType('data/IgnoredGetter.php', $actual);
220
    }
221
222
    public function testCanDeclareArrayOfEntity(): void
223
    {
224
        $actual = $this->types->get(Blog\Model\Special\ArrayOfEntity::class);
225
        $this->assertObjectType('data/ArrayOfEntity.php', $actual);
226
    }
227
228
    public function testDefaultValuesInput(): void
229
    {
230
        $actual = $this->types->getInput(Blog\Model\Special\DefaultValue::class);
231
        $this->assertInputType('data/DefaultValueInput.php', $actual);
232
    }
233
234
    public function testDefaultValuesPartialInput(): void
235
    {
236
        $actual = $this->types->getPartialInput(Blog\Model\Special\DefaultValue::class);
237
        $this->assertInputType('data/DefaultValuePartialInput.php', $actual);
238
    }
239
240
    public function testDefaultValuesOutput(): void
241
    {
242
        $actual = $this->types->get(Blog\Model\Special\DefaultValue::class);
243
        $this->assertObjectType('data/DefaultValue.php', $actual);
244
    }
245
246
    public function testFieldWithoutTypeMustThrow(): void
247
    {
248
        $this->expectExceptionMessage('Could not find type for method `GraphQLTests\Doctrine\Blog\Model\Special\NoType::getWithoutTypeHint()`. Either type hint the return value, or specify the type with `@API\Field` annotation.');
249
        $type = $this->types->get(Blog\Model\Special\NoType::class);
250
        $type->getFields();
251
    }
252
253
    public function testFieldReturningCollectionWithoutTypeMustThrow(): void
254
    {
255
        $this->expectExceptionMessage('The method `GraphQLTests\Doctrine\Blog\Model\Special\NoTypeCollection::getFoos()` is type hinted with a return type of `Doctrine\Common\Collections\Collection`, but the entity contained in that collection could not be automatically detected. Either fix the type hint, fix the doctrine mapping, or specify the type with `@API\Field` annotation.');
256
        $type = $this->types->get(Blog\Model\Special\NoTypeCollection::class);
257
        $type->getFields();
258
    }
259
260
    public function testCannotGetInvalidType(): void
261
    {
262
        $this->expectExceptionMessage('Given class name `DateTimeImmutable` is not a Doctrine entity. Either register a custom GraphQL type for `DateTimeImmutable` when instantiating `GraphQL\Doctrine\Types`, or change the usage of that class to something else.');
263
        $this->types->get(\DateTimeImmutable::class);
264
    }
265
266
    public function testArgumentWithoutTypeMustThrow(): void
267
    {
268
        $this->expectExceptionMessage('Could not find type for parameter `$bar` for method `GraphQLTests\Doctrine\Blog\Model\Special\NoTypeArgument::getFoo()`. Either type hint the parameter, or specify the type with `@API\Argument` annotation.');
269
        $type = $this->types->get(Blog\Model\Special\NoTypeArgument::class);
270
        $type->getFields();
271
    }
272
273
    public function testInputWithoutTypeMustThrow(): void
274
    {
275
        $this->expectExceptionMessage('Could not find type for parameter `$bar` for method `GraphQLTests\Doctrine\Blog\Model\Special\NoTypeInput::setFoo()`. Either type hint the parameter, or specify the type with `@API\Input` annotation.');
276
        $type = $this->types->getInput(Blog\Model\Special\NoTypeInput::class);
277
        $type->getFields();
278
    }
279
280
    public function testFieldWithExtraArgumentMustThrow(): void
281
    {
282
        $this->expectExceptionMessage('The following arguments were declared via `@API\Argument` annotation but do not match actual parameter names on method `GraphQLTests\Doctrine\Blog\Model\Special\ExtraArgument::getWithParams()`. Either rename or remove the annotations: misspelled_name');
283
        $type = $this->types->get(Blog\Model\Special\ExtraArgument::class);
284
        $type->getFields();
285
    }
286
287
    public function testFieldWithArrayArgumentMustThrow(): void
288
    {
289
        $this->expectExceptionMessage('The parameter `$arg1` on method `GraphQLTests\Doctrine\Blog\Model\Special\ArrayArgument::getWithParams()` is type hinted as `array` and is not overridden via `@API\Argument` annotation. Either change the type hint or specify the type with `@API\Argument` annotation.');
290
        $type = $this->types->get(Blog\Model\Special\ArrayArgument::class);
291
        $type->getFields();
292
    }
293
294
    public function testFieldWithObjectTypeArgumentMustThrow(): void
295
    {
296
        $this->expectExceptionMessage('Type for parameter `$user` for method `GraphQLTests\Doctrine\Blog\Model\Special\ObjectTypeArgument::getWithParams()` must be an instance of `GraphQL\Type\Definition\InputType`, but was `GraphQL\Type\Definition\ObjectType`. Use `@API\Argument` annotation to specify a custom InputType.');
297
        $type = $this->types->get(Blog\Model\Special\ObjectTypeArgument::class);
298
        $type->getFields();
299
    }
300
301
    public function testCanGetMappedTypesEitherByMappedPhpClassOrDirectTypeClass(): void
302
    {
303
        $viaPhp = $this->types->get(DateTime::class);
304
        $viaType = $this->types->get(DateTimeType::class);
305
        self::assertSame($viaPhp, $viaType);
306
    }
307
}
308