Completed
Push — master ( 6fb2a7...77b356 )
by Adrien
02:36
created

TypesTest::testFieldWithExtraArgumentMustThrow()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQLTests\Doctrine;
6
7
use DateTime;
8
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain;
9
use Doctrine\ORM\Tools\SchemaValidator;
10
use GraphQL\Doctrine\Types;
11
use GraphQL\Type\Definition\BooleanType;
12
use GraphQL\Type\Definition\InputObjectType;
13
use GraphQL\Type\Definition\ObjectType;
14
use GraphQL\Type\Definition\Type;
15
use GraphQL\Type\Schema;
16
use GraphQLTests\Doctrine\Blog\Model\Post;
17
use GraphQLTests\Doctrine\Blog\Model\User;
18
use GraphQLTests\Doctrine\Blog\Types\CustomType;
19
use GraphQLTests\Doctrine\Blog\Types\DateTimeType;
20
use GraphQLTests\Doctrine\Blog\Types\PostStatusType;
21
use stdClass;
22
use Zend\ServiceManager\ServiceManager;
23
24
class TypesTest extends \PHPUnit\Framework\TestCase
25
{
26
    use EntityManagerTrait;
27
28
    /**
29
     * @var Types
30
     */
31
    private $types;
32
33
    public function setUp(): void
34
    {
35
        $this->setUpEntityManager();
36
37
        $customTypes = new ServiceManager([
38
            'invokables' => [
39
                BooleanType::class => BooleanType::class,
40
                DateTime::class => DateTimeType::class,
41
                stdClass::class => CustomType::class,
42
                'PostStatus' => PostStatusType::class,
43
            ],
44
        ]);
45
46
        $this->types = new Types($this->entityManager, $customTypes);
47
    }
48
49
    public function testBlogMapping(): void
50
    {
51
        $validator = new SchemaValidator($this->entityManager);
52
        $errors = $validator->validateMapping();
53
54
        self::assertEmpty($errors, 'doctrine annotations should be valid');
55
    }
56
57
    public function testGraphQLSchemaFromDocumentationMustBeValid(): void
58
    {
59
        $schema = new Schema([
60
            'query' => new ObjectType([
61
                'name' => 'query',
62
                'fields' => [
63
                    'users' => [
64
                        'type' => Type::listOf($this->types->getOutput(User::class)), // Use automated ObjectType for output
65
                        '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

65
                        '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

65
                        '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...
66
                            // call to repository...
67
                        },
68
                    ],
69
                    'posts' => [
70
                        'type' => Type::listOf($this->types->getOutput(Post::class)), // Use automated ObjectType for output
71
                        '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

71
                        '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

71
                        '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...
72
                            // call to repository...
73
                        },
74
                    ],
75
                ],
76
            ]),
77
            'mutation' => new ObjectType([
78
                'name' => 'mutation',
79
                'fields' => [
80
                    'createUser' => [
81
                        'type' => Type::nonNull($this->types->getOutput(User::class)),
82
                        'args' => [
83
                            'input' => Type::nonNull($this->types->getInput(User::class)), // Use automated InputObjectType for input
84
                        ],
85
                        '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

85
                        '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

85
                        '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...
86
                            // create new user and flush...
87
                        },
88
                    ],
89
                    'updateUser' => [
90
                        'type' => Type::nonNull($this->types->getOutput(User::class)),
91
                        'args' => [
92
                            'id' => Type::nonNull(Type::id()), // Use standard API when needed
93
                            'input' => $this->types->getInput(User::class),
94
                        ],
95
                        '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

95
                        '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

95
                        '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...
96
                            // update existing user and flush...
97
                        },
98
                    ],
99
                    'createPost' => [
100
                        'type' => Type::nonNull($this->types->getOutput(Post::class)),
101
                        'args' => [
102
                            'input' => Type::nonNull($this->types->getInput(Post::class)), // Use automated InputObjectType for input
103
                        ],
104
                        '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

104
                        '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

104
                        '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...
105
                            // create new post and flush...
106
                        },
107
                    ],
108
                    'updatePost' => [
109
                        'type' => Type::nonNull($this->types->getOutput(Post::class)),
110
                        'args' => [
111
                            'id' => Type::nonNull(Type::id()), // Use standard API when needed
112
                            'input' => $this->types->getInput(Post::class),
113
                        ],
114
                        '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

114
                        '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

114
                        '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...
115
                            // update existing post and flush...
116
                        },
117
                    ],
118
                ],
119
            ]),
120
        ]);
121
122
        $schema->assertValid();
123
        self::assertTrue(true, 'passed validation successfully');
124
    }
125
126
    public function testCanGetUserDefinedScalarTypes(): void
127
    {
128
        $bool = $this->types->get(BooleanType::class);
129
        $status = $this->types->get(PostStatusType::class);
130
131
        self::assertInstanceOf(BooleanType::class, $bool, 'must be a instance of bool');
132
        self::assertInstanceOf(PostStatusType::class, $status, 'must be an instance of post status');
133
134
        self::assertSame($bool, $this->types->get(BooleanType::class), 'must returns the same instance of bool');
135
        self::assertSame($status, $this->types->get(PostStatusType::class), 'must returns the same instance of post status');
136
    }
137
138
    public function testCanGetUserMappedTypes(): void
139
    {
140
        $type = $this->types->get(stdClass::class);
141
142
        self::assertInstanceOf(CustomType::class, $type, 'must be a instance of CustomType');
143
        self::assertSame($type, $this->types->get('customName'));
144
    }
145
146
    public function testCanGetTypesWithBackslashPrefix(): void
147
    {
148
        $type = $this->types->getOutput(Post::class);
149
        self::assertSame($type, $this->types->getOutput('\\' . Post::class));
150
    }
151
152
    public function testCanGetOutputTypes(): void
153
    {
154
        $userType = $this->types->getOutput(User::class);
155
156
        $this->assertObjectType('data/UserOutput.php', $userType);
157
        self::assertSame($userType, $this->types->getOutput(User::class), 'must returns the same instance of user type');
158
159
        $postType = $this->types->getOutput(Post::class);
160
        $this->assertObjectType('data/PostOutput.php', $postType);
161
        self::assertSame($postType, $this->types->getOutput(Post::class), 'must returns the same instance of post type');
162
    }
163
164
    public function testCanGetInputTypes(): void
165
    {
166
        $userType = $this->types->getInput(User::class);
167
        $this->assertInputType('data/UserInput.php', $userType);
168
        self::assertSame($userType, $this->types->getInput(User::class), 'must returns the same instance of user type');
169
170
        $postType = $this->types->getInput(Post::class);
171
        $this->assertInputType('data/PostInput.php', $postType);
172
        self::assertSame($postType, $this->types->getInput(Post::class), 'must returns the same instance of post type');
173
    }
174
175
    private function assertType(string $expectedFile, Type $type, bool $assertArgs): void
176
    {
177
        $fields = [];
178
        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

178
        foreach ($type->/** @scrutinizer ignore-call */ getFields() as $field) {
Loading history...
179
            $data = [
180
                'name' => $field->name,
181
                'type' => $field->getType()->toString(),
182
                'description' => $field->description,
183
            ];
184
185
            if ($assertArgs) {
186
                $args = [];
187
                foreach ($field->args as $arg) {
188
                    $argData = [
189
                        'name' => $arg->name,
190
                        'type' => $arg->getType()->toString(),
191
                        'description' => $arg->description,
192
193
                    ];
194
195
                    if ($arg->defaultValueExists()) {
196
                        $argData['defaultValue'] = $arg->defaultValue;
197
                    }
198
199
                    $args[] = $argData;
200
                }
201
                $data['args'] = $args;
202
            } elseif ($field->defaultValueExists()) {
203
                $data['defaultValue'] = $field->defaultValue;
204
            }
205
206
            $fields[] = $data;
207
        }
208
209
        $actual = [
210
            'name' => $type->name,
211
            'description' => $type->description,
212
            'fields' => $fields,
213
        ];
214
215
        $expected = require $expectedFile;
216
        self::assertEquals($expected, $actual, 'Should equals expectation from: ' . $expectedFile);
217
    }
218
219
    private function assertInputType(string $expectedFile, InputObjectType $type): void
220
    {
221
        $this->assertType($expectedFile, $type, false);
222
    }
223
224
    private function assertObjectType(string $expectedFile, ObjectType $type): void
225
    {
226
        $this->assertType($expectedFile, $type, true);
227
    }
228
229
    public function testNonPublicGetterMustBeIgnored(): void
230
    {
231
        $actual = $this->types->getOutput(Blog\Model\Special\IgnoredGetter::class);
232
        $this->assertObjectType('data/IgnoredGetter.php', $actual);
233
    }
234
235
    public function testCanDeclareArrayOfEntity(): void
236
    {
237
        $actual = $this->types->getOutput(Blog\Model\Special\ArrayOfEntity::class);
238
        $this->assertObjectType('data/ArrayOfEntity.php', $actual);
239
    }
240
241
    public function testDefaultValuesInput(): void
242
    {
243
        $actual = $this->types->getInput(Blog\Model\Special\DefaultValue::class);
244
        $this->assertInputType('data/DefaultValueInput.php', $actual);
245
    }
246
247
    public function testDefaultValuesPartialInput(): void
248
    {
249
        $actual = $this->types->getPartialInput(Blog\Model\Special\DefaultValue::class);
250
        $this->assertInputType('data/DefaultValuePartialInput.php', $actual);
251
    }
252
253
    public function testDefaultValuesOutput(): void
254
    {
255
        $actual = $this->types->getOutput(Blog\Model\Special\DefaultValue::class);
256
        $this->assertObjectType('data/DefaultValue.php', $actual);
257
    }
258
259
    public function testFieldWithoutTypeMustThrow(): void
260
    {
261
        $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.');
262
        $type = $this->types->getOutput(Blog\Model\Special\NoType::class);
263
        $type->getFields();
264
    }
265
266
    public function testFieldReturningCollectionWithoutTypeMustThrow(): void
267
    {
268
        $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.');
269
        $type = $this->types->getOutput(Blog\Model\Special\NoTypeCollection::class);
270
        $type->getFields();
271
    }
272
273
    public function testCannotGetInvalidType(): void
274
    {
275
        $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.');
276
        $this->types->getOutput(\DateTimeImmutable::class);
277
    }
278
279
    public function testArgumentWithoutTypeMustThrow(): void
280
    {
281
        $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.');
282
        $type = $this->types->getOutput(Blog\Model\Special\NoTypeArgument::class);
283
        $type->getFields();
284
    }
285
286
    public function testInputWithoutTypeMustThrow(): void
287
    {
288
        $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.');
289
        $type = $this->types->getInput(Blog\Model\Special\NoTypeInput::class);
290
        $type->getFields();
291
    }
292
293
    public function testFieldWithExtraArgumentMustThrow(): void
294
    {
295
        $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');
296
        $type = $this->types->getOutput(Blog\Model\Special\ExtraArgument::class);
297
        $type->getFields();
298
    }
299
300
    public function testFieldWithArrayArgumentMustThrow(): void
301
    {
302
        $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.');
303
        $type = $this->types->getOutput(Blog\Model\Special\ArrayArgument::class);
304
        $type->getFields();
305
    }
306
307
    public function testFieldWithObjectTypeArgumentMustThrow(): void
308
    {
309
        $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.');
310
        $type = $this->types->getOutput(Blog\Model\Special\ObjectTypeArgument::class);
311
        $type->getFields();
312
    }
313
314
    public function testCanGetMappedTypesEitherByMappedPhpClassOrDirectTypeClass(): void
315
    {
316
        $viaPhp = $this->types->get(DateTime::class);
317
        $viaType = $this->types->get(DateTimeType::class);
318
        self::assertSame($viaPhp, $viaType);
319
    }
320
321
    public function testDoctrineWithoutAnnotationDriverMustThrow(): void
322
    {
323
        // Replace annotation driver with a driver chain
324
        $config = $this->entityManager->getConfiguration();
325
        $chain = new MappingDriverChain();
326
        $chain->setDefaultDriver($config->getMetadataDriverImpl());
327
        $config->setMetadataDriverImpl($chain);
328
329
        $type = $this->types->getOutput(Post::class);
330
331
        $this->expectExceptionMessage('graphql-doctrine requires Doctrine to be configured with a `Doctrine\Common\Persistence\Mapping\Driver\AnnotationDriver`.');
332
        $type->getFields();
333
    }
334
335
    public function testNonRegisteredCustomTypeMustThrow(): void
336
    {
337
        $this->expectExceptionMessage('No type registered with key `foo`. Either correct the usage, or register it in your custom types container when instantiating `GraphQL\Doctrine\Types`');
338
        $this->types->get('foo');
339
    }
340
341
    public function testHas(): void
342
    {
343
        $this->assertTrue($this->types->has(stdClass::class), 'should have custom registered key');
344
        $this->assertFalse($this->types->has('non-existing'), 'should not have non-existing things');
345
346
        $this->types->get(stdClass::class);
347
        $this->assertTrue($this->types->has('customName'), 'should have custom registered type by its name, even if custom key was different, once type is created');
348
    }
349
}
350