• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 6067528200

04 Sep 2023 12:12AM UTC coverage: 36.875% (-21.9%) from 58.794%
6067528200

Pull #5791

github

web-flow
Merge 64157e578 into d09cfc9d2
Pull Request #5791: fix: strip down any sql function name

3096 of 3096 new or added lines in 205 files covered. (100.0%)

9926 of 26918 relevant lines covered (36.87%)

6.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/GraphQl/Tests/Type/FieldsBuilderTest.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\GraphQl\Tests\Type;
15

16
use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactoryInterface;
17
use ApiPlatform\GraphQl\Tests\Fixtures\Enum\GenderTypeEnum;
18
use ApiPlatform\GraphQl\Tests\Fixtures\Serializer\NameConverter\CustomConverter;
19
use ApiPlatform\GraphQl\Type\FieldsBuilder;
20
use ApiPlatform\GraphQl\Type\TypeBuilderEnumInterface;
21
use ApiPlatform\GraphQl\Type\TypeConverterInterface;
22
use ApiPlatform\GraphQl\Type\TypesContainerInterface;
23
use ApiPlatform\Metadata\ApiProperty;
24
use ApiPlatform\Metadata\ApiResource;
25
use ApiPlatform\Metadata\FilterInterface;
26
use ApiPlatform\Metadata\GraphQl\Mutation;
27
use ApiPlatform\Metadata\GraphQl\Operation;
28
use ApiPlatform\Metadata\GraphQl\Query;
29
use ApiPlatform\Metadata\GraphQl\QueryCollection;
30
use ApiPlatform\Metadata\GraphQl\Subscription;
31
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
32
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
33
use ApiPlatform\Metadata\Property\PropertyNameCollection;
34
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
35
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
36
use ApiPlatform\Metadata\ResourceClassResolverInterface;
37
use ApiPlatform\State\Pagination\Pagination;
38
use GraphQL\Type\Definition\InputObjectType;
39
use GraphQL\Type\Definition\InterfaceType;
40
use GraphQL\Type\Definition\ListOfType;
41
use GraphQL\Type\Definition\NonNull;
42
use GraphQL\Type\Definition\ObjectType;
43
use GraphQL\Type\Definition\Type as GraphQLType;
44
use PHPUnit\Framework\TestCase;
45
use Prophecy\Argument;
46
use Prophecy\PhpUnit\ProphecyTrait;
47
use Prophecy\Prophecy\ObjectProphecy;
48
use Psr\Container\ContainerInterface;
49
use Symfony\Component\PropertyInfo\Type;
50
use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface;
51

52
/**
53
 * @author Alan Poulain <contact@alanpoulain.eu>
54
 */
55
class FieldsBuilderTest extends TestCase
56
{
57
    use ProphecyTrait;
58

59
    private ObjectProphecy $propertyNameCollectionFactoryProphecy;
60
    private ObjectProphecy $propertyMetadataFactoryProphecy;
61
    private ObjectProphecy $resourceMetadataCollectionFactoryProphecy;
62
    private ObjectProphecy $typesContainerProphecy;
63
    private ObjectProphecy $typeBuilderProphecy;
64
    private ObjectProphecy $typeConverterProphecy;
65
    private ObjectProphecy $itemResolverFactoryProphecy;
66
    private ObjectProphecy $collectionResolverFactoryProphecy;
67
    private ObjectProphecy $itemMutationResolverFactoryProphecy;
68
    private ObjectProphecy $itemSubscriptionResolverFactoryProphecy;
69
    private ObjectProphecy $filterLocatorProphecy;
70
    private ObjectProphecy $resourceClassResolverProphecy;
71
    private FieldsBuilder $fieldsBuilder;
72

73
    /**
74
     * {@inheritdoc}
75
     */
76
    protected function setUp(): void
77
    {
78
        $this->propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
79
        $this->propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
80
        $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
81
        $this->typesContainerProphecy = $this->prophesize(TypesContainerInterface::class);
×
82
        $this->typeBuilderProphecy = $this->prophesize(TypeBuilderEnumInterface::class);
×
83
        $this->typeConverterProphecy = $this->prophesize(TypeConverterInterface::class);
×
84
        $this->itemResolverFactoryProphecy = $this->prophesize(ResolverFactoryInterface::class);
×
85
        $this->collectionResolverFactoryProphecy = $this->prophesize(ResolverFactoryInterface::class);
×
86
        $this->itemMutationResolverFactoryProphecy = $this->prophesize(ResolverFactoryInterface::class);
×
87
        $this->itemSubscriptionResolverFactoryProphecy = $this->prophesize(ResolverFactoryInterface::class);
×
88
        $this->filterLocatorProphecy = $this->prophesize(ContainerInterface::class);
×
89
        $this->resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
90
        $this->fieldsBuilder = $this->buildFieldsBuilder();
×
91
    }
92

93
    private function buildFieldsBuilder(AdvancedNameConverterInterface $advancedNameConverter = null): FieldsBuilder
94
    {
95
        return new FieldsBuilder($this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->resourceClassResolverProphecy->reveal(), $this->typesContainerProphecy->reveal(), $this->typeBuilderProphecy->reveal(), $this->typeConverterProphecy->reveal(), $this->itemResolverFactoryProphecy->reveal(), $this->collectionResolverFactoryProphecy->reveal(), $this->itemMutationResolverFactoryProphecy->reveal(), $this->itemSubscriptionResolverFactoryProphecy->reveal(), $this->filterLocatorProphecy->reveal(), new Pagination(), $advancedNameConverter ?? new CustomConverter(), '__');
×
96
    }
97

98
    public function testGetNodeQueryFields(): void
99
    {
100
        $nodeInterfaceType = $this->prophesize(InterfaceType::class)->reveal();
×
101
        $this->typeBuilderProphecy->getNodeInterface()->shouldBeCalled()->willReturn($nodeInterfaceType);
×
102

103
        $itemResolver = function (): void {
×
104
        };
×
105
        $this->itemResolverFactoryProphecy->__invoke()->shouldBeCalled()->willReturn($itemResolver);
×
106

107
        $nodeQueryFields = $this->fieldsBuilder->getNodeQueryFields();
×
108
        $this->assertArrayHasKey('type', $nodeQueryFields);
×
109
        $this->assertArrayHasKey('args', $nodeQueryFields);
×
110
        $this->assertArrayHasKey('resolve', $nodeQueryFields);
×
111

112
        $this->assertSame($nodeInterfaceType, $nodeQueryFields['type']);
×
113
        $this->assertArrayHasKey('id', $nodeQueryFields['args']);
×
114
        $this->assertArrayHasKey('type', $nodeQueryFields['args']['id']);
×
115
        $this->assertInstanceOf(NonNull::class, $nodeQueryFields['args']['id']['type']);
×
116
        /** @var NonNull $idType */
117
        $idType = $nodeQueryFields['args']['id']['type'];
×
118
        $this->assertSame(GraphQLType::id(), $idType->getWrappedType());
×
119
        $this->assertSame($itemResolver, $nodeQueryFields['resolve']);
×
120
    }
121

122
    /**
123
     * @dataProvider itemQueryFieldsProvider
124
     */
125
    public function testGetItemQueryFields(string $resourceClass, Operation $operation, array $configuration, ?GraphQLType $graphqlType, ?callable $resolver, array $expectedQueryFields): void
126
    {
127
        $this->resourceClassResolverProphecy->isResourceClass($resourceClass)->willReturn(true);
×
128
        $this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType);
×
129
        $this->typeConverterProphecy->resolveType(Argument::type('string'))->willReturn(GraphQLType::string());
×
130
        $this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false);
×
131
        $this->itemResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($resolver);
×
132

133
        $queryFields = $this->fieldsBuilder->getItemQueryFields($resourceClass, $operation, $configuration);
×
134

135
        $this->assertEquals($expectedQueryFields, $queryFields);
×
136
    }
137

138
    public static function itemQueryFieldsProvider(): array
139
    {
140
        return [
×
141
            'no resource field configuration' => ['resourceClass', (new Query())->withClass('resourceClass')->withName('action'), [], null, null, []],
×
142
            'nested item query' => ['resourceClass', (new Query())->withNested(true)->withClass('resourceClass')->withName('action')->withShortName('ShortName'), [], new ObjectType(['name' => 'item', 'fields' => []]), function (): void {
×
143
            }, []],
×
144
            'nominal standard type case with deprecation reason and description' => ['resourceClass', (new Query())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withDeprecationReason('not useful')->withDescription('Custom description.'), [], GraphQLType::string(), null,
×
145
                [
×
146
                    'actionShortName' => [
×
147
                        'type' => GraphQLType::string(),
×
148
                        'description' => 'Custom description.',
×
149
                        'args' => [
×
150
                            'id' => ['type' => GraphQLType::nonNull(GraphQLType::id())],
×
151
                        ],
×
152
                        'resolve' => null,
×
153
                        'deprecationReason' => 'not useful',
×
154
                    ],
×
155
                ],
×
156
            ],
×
157
            'nominal item case' => ['resourceClass', (new Query())->withClass('resourceClass')->withName('action')->withShortName('ShortName'), [], $graphqlType = new ObjectType(['name' => 'item', 'fields' => []]), $resolver = function (): void {
×
158
            },
×
159
                [
×
160
                    'actionShortName' => [
×
161
                        'type' => $graphqlType,
×
162
                        'description' => null,
×
163
                        'args' => [
×
164
                            'id' => ['type' => GraphQLType::nonNull(GraphQLType::id())],
×
165
                        ],
×
166
                        'resolve' => $resolver,
×
167
                        'deprecationReason' => null,
×
168
                    ],
×
169
                ],
×
170
            ],
×
171
            'empty overridden args and add fields' => [
×
172
                'resourceClass', (new Query())->withClass('resourceClass')->withShortName('ShortName'), ['args' => [], 'name' => 'customActionName'], GraphQLType::string(), null,
×
173
                [
×
174
                    'shortName' => [
×
175
                        'type' => GraphQLType::string(),
×
176
                        'description' => null,
×
177
                        'args' => [],
×
178
                        'resolve' => null,
×
179
                        'deprecationReason' => null,
×
180
                        'name' => 'customActionName',
×
181
                    ],
×
182
                ],
×
183
            ],
×
184
            'override args with custom ones' => [
×
185
                'resourceClass', (new Query())->withClass('resourceClass')->withShortName('ShortName'), ['args' => ['customArg' => ['type' => 'a type']]], GraphQLType::string(), null,
×
186
                [
×
187
                    'shortName' => [
×
188
                        'type' => GraphQLType::string(),
×
189
                        'description' => null,
×
190
                        'args' => [
×
191
                            'customArg' => [
×
192
                                'type' => GraphQLType::string(),
×
193
                            ],
×
194
                        ],
×
195
                        'resolve' => null,
×
196
                        'deprecationReason' => null,
×
197
                    ],
×
198
                ],
×
199
            ],
×
200
        ];
×
201
    }
202

203
    /**
204
     * @dataProvider collectionQueryFieldsProvider
205
     */
206
    public function testGetCollectionQueryFields(string $resourceClass, Operation $operation, array $configuration, ?GraphQLType $graphqlType, ?callable $resolver, array $expectedQueryFields): void
207
    {
208
        $this->resourceClassResolverProphecy->isResourceClass($resourceClass)->willReturn(true);
×
209
        $this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType);
×
210
        $this->typeConverterProphecy->resolveType(Argument::type('string'))->willReturn(GraphQLType::string());
×
211
        $this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(true);
×
212
        $this->typeBuilderProphecy->getPaginatedCollectionType($graphqlType, $operation)->willReturn($graphqlType);
×
213
        $this->collectionResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($resolver);
×
214
        $this->filterLocatorProphecy->has('my_filter')->willReturn(true);
×
215
        $filterProphecy = $this->prophesize(FilterInterface::class);
×
216
        $filterProphecy->getDescription($resourceClass)->willReturn([
×
217
            'boolField' => ['type' => 'bool', 'required' => true],
×
218
            'boolField[]' => ['type' => 'bool', 'required' => true],
×
219
            'parent.child[related.nested]' => ['type' => 'bool', 'required' => true],
×
220
            'dateField[before]' => ['type' => \DateTimeInterface::class, 'required' => false],
×
221
        ]);
×
222
        $this->filterLocatorProphecy->get('my_filter')->willReturn($filterProphecy->reveal());
×
223
        $this->typesContainerProphecy->has('ShortNameFilter_dateField')->willReturn(false);
×
224
        $this->typesContainerProphecy->has('ShortNameFilter_parent__child')->willReturn(false);
×
225
        $this->typesContainerProphecy->set('ShortNameFilter_dateField', Argument::type(ListOfType::class));
×
226
        $this->typesContainerProphecy->set('ShortNameFilter_parent__child', Argument::type(ListOfType::class));
×
227

228
        $queryFields = $this->fieldsBuilder->getCollectionQueryFields($resourceClass, $operation, $configuration);
×
229

230
        $this->assertEquals($expectedQueryFields, $queryFields);
×
231
    }
232

233
    public static function collectionQueryFieldsProvider(): array
234
    {
235
        return [
×
236
            'no resource field configuration' => ['resourceClass', (new QueryCollection())->withClass('resourceClass')->withName('action'), [], null, null, []],
×
237
            'nested collection query' => ['resourceClass', (new QueryCollection())->withNested(true)->withClass('resourceClass')->withName('action')->withShortName('ShortName'), [], GraphQLType::listOf(new ObjectType(['name' => 'collection', 'fields' => []])), function (): void {
×
238
            }, []],
×
239
            'nominal collection case with deprecation reason and description' => ['resourceClass', (new QueryCollection())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withDeprecationReason('not useful')->withDescription('Custom description.'), [], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection', 'fields' => []])), $resolver = function (): void {
×
240
            },
×
241
                [
×
242
                    'actionShortNames' => [
×
243
                        'type' => $graphqlType,
×
244
                        'description' => 'Custom description.',
×
245
                        'args' => [
×
246
                            'first' => [
×
247
                                'type' => GraphQLType::int(),
×
248
                                'description' => 'Returns the first n elements from the list.',
×
249
                            ],
×
250
                            'last' => [
×
251
                                'type' => GraphQLType::int(),
×
252
                                'description' => 'Returns the last n elements from the list.',
×
253
                            ],
×
254
                            'before' => [
×
255
                                'type' => GraphQLType::string(),
×
256
                                'description' => 'Returns the elements in the list that come before the specified cursor.',
×
257
                            ],
×
258
                            'after' => [
×
259
                                'type' => GraphQLType::string(),
×
260
                                'description' => 'Returns the elements in the list that come after the specified cursor.',
×
261
                            ],
×
262
                        ],
×
263
                        'resolve' => $resolver,
×
264
                        'deprecationReason' => 'not useful',
×
265
                    ],
×
266
                ],
×
267
            ],
×
268
            'collection with filters' => ['resourceClass', (new QueryCollection())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withFilters(['my_filter']), [], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection', 'fields' => []])), $resolver = function (): void {
×
269
            },
×
270
                [
×
271
                    'actionShortNames' => [
×
272
                        'type' => $graphqlType,
×
273
                        'description' => null,
×
274
                        'args' => [
×
275
                            'first' => [
×
276
                                'type' => GraphQLType::int(),
×
277
                                'description' => 'Returns the first n elements from the list.',
×
278
                            ],
×
279
                            'last' => [
×
280
                                'type' => GraphQLType::int(),
×
281
                                'description' => 'Returns the last n elements from the list.',
×
282
                            ],
×
283
                            'before' => [
×
284
                                'type' => GraphQLType::string(),
×
285
                                'description' => 'Returns the elements in the list that come before the specified cursor.',
×
286
                            ],
×
287
                            'after' => [
×
288
                                'type' => GraphQLType::string(),
×
289
                                'description' => 'Returns the elements in the list that come after the specified cursor.',
×
290
                            ],
×
291
                            'boolField' => $graphqlType,
×
292
                            'boolField_list' => GraphQLType::listOf($graphqlType),
×
293
                            'parent__child' => GraphQLType::listOf(new InputObjectType(['name' => 'ShortNameFilter_parent__child', 'fields' => ['related__nested' => $graphqlType]])),
×
294
                            'dateField' => GraphQLType::listOf(new InputObjectType(['name' => 'ShortNameFilter_dateField', 'fields' => ['before' => $graphqlType]])),
×
295
                        ],
×
296
                        'resolve' => $resolver,
×
297
                        'deprecationReason' => null,
×
298
                    ],
×
299
                ],
×
300
            ],
×
301
            'collection empty overridden args and add fields' => [
×
302
                'resourceClass', (new QueryCollection())->withArgs([])->withClass('resourceClass')->withName('action')->withShortName('ShortName'), ['args' => [], 'name' => 'customActionName'], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection', 'fields' => []])), $resolver = function (): void {
×
303
                },
×
304
                [
×
305
                    'actionShortNames' => [
×
306
                        'type' => $graphqlType,
×
307
                        'description' => null,
×
308
                        'args' => [],
×
309
                        'resolve' => $resolver,
×
310
                        'deprecationReason' => null,
×
311
                        'name' => 'customActionName',
×
312
                    ],
×
313
                ],
×
314
            ],
×
315
            'collection override args with custom ones' => [
×
316
                'resourceClass', (new QueryCollection())->withClass('resourceClass')->withName('action')->withShortName('ShortName'), ['args' => ['customArg' => ['type' => 'a type']]], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection', 'fields' => []])), $resolver = function (): void {
×
317
                },
×
318
                [
×
319
                    'actionShortNames' => [
×
320
                        'type' => $graphqlType,
×
321
                        'description' => null,
×
322
                        'args' => [
×
323
                            'customArg' => [
×
324
                                'type' => GraphQLType::string(),
×
325
                            ],
×
326
                        ],
×
327
                        'resolve' => $resolver,
×
328
                        'deprecationReason' => null,
×
329
                    ],
×
330
                ],
×
331
            ],
×
332
            'collection with page-based pagination enabled' => ['resourceClass', (new QueryCollection())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withPaginationType('page')->withFilters(['my_filter']), [], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection', 'fields' => []])), $resolver = function (): void {
×
333
            },
×
334
                [
×
335
                    'actionShortNames' => [
×
336
                        'type' => $graphqlType,
×
337
                        'description' => null,
×
338
                        'args' => [
×
339
                            'page' => [
×
340
                                'type' => GraphQLType::int(),
×
341
                                'description' => 'Returns the current page.',
×
342
                            ],
×
343
                            'boolField' => $graphqlType,
×
344
                            'boolField_list' => GraphQLType::listOf($graphqlType),
×
345
                            'parent__child' => GraphQLType::listOf(new InputObjectType(['name' => 'ShortNameFilter_parent__child', 'fields' => ['related__nested' => $graphqlType]])),
×
346
                            'dateField' => GraphQLType::listOf(new InputObjectType(['name' => 'ShortNameFilter_dateField', 'fields' => ['before' => $graphqlType]])),
×
347
                        ],
×
348
                        'resolve' => $resolver,
×
349
                        'deprecationReason' => null,
×
350
                    ],
×
351
                ],
×
352
            ],
×
353
        ];
×
354
    }
355

356
    /**
357
     * @dataProvider mutationFieldsProvider
358
     */
359
    public function testGetMutationFields(string $resourceClass, Operation $operation, GraphQLType $graphqlType, GraphQLType $inputGraphqlType, ?callable $mutationResolver, array $expectedMutationFields): void
360
    {
361
        $this->resourceClassResolverProphecy->isResourceClass($resourceClass)->willReturn(true);
×
362
        $this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType);
×
363
        $this->typeConverterProphecy->convertType(Argument::type(Type::class), true, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($inputGraphqlType);
×
364
        $this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false);
×
365
        $this->itemMutationResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($mutationResolver);
×
366

367
        $mutationFields = $this->fieldsBuilder->getMutationFields($resourceClass, $operation);
×
368

369
        $this->assertSame($expectedMutationFields, $mutationFields);
×
370
    }
371

372
    public static function mutationFieldsProvider(): array
373
    {
374
        return [
×
375
            'nominal case with deprecation reason' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withDeprecationReason('not useful'), $graphqlType = new ObjectType(['name' => 'mutation', 'fields' => []]), $inputGraphqlType = new ObjectType(['name' => 'input', 'fields' => []]), $mutationResolver = function (): void {
×
376
            },
×
377
                [
×
378
                    'actionShortName' => [
×
379
                        'type' => $graphqlType,
×
380
                        'description' => 'Actions a ShortName.',
×
381
                        'args' => [
×
382
                            'input' => [
×
383
                                'type' => $inputGraphqlType,
×
384
                                'description' => null,
×
385
                                'args' => [],
×
386
                                'resolve' => null,
×
387
                                'deprecationReason' => 'not useful',
×
388
                            ],
×
389
                        ],
×
390
                        'resolve' => $mutationResolver,
×
391
                        'deprecationReason' => 'not useful',
×
392
                    ],
×
393
                ],
×
394
            ],
×
395
            'custom description' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withDescription('Custom description.'), $graphqlType = new ObjectType(['name' => 'mutation', 'fields' => []]), $inputGraphqlType = new ObjectType(['name' => 'input', 'fields' => []]), $mutationResolver = function (): void {
×
396
            },
×
397
                [
×
398
                    'actionShortName' => [
×
399
                        'type' => $graphqlType,
×
400
                        'description' => 'Custom description.',
×
401
                        'args' => [
×
402
                            'input' => [
×
403
                                'type' => $inputGraphqlType,
×
404
                                'description' => null,
×
405
                                'args' => [],
×
406
                                'resolve' => null,
×
407
                                'deprecationReason' => null,
×
408
                            ],
×
409
                        ],
×
410
                        'resolve' => $mutationResolver,
×
411
                        'deprecationReason' => null,
×
412
                    ],
×
413
                ],
×
414
            ],
×
415
        ];
×
416
    }
417

418
    /**
419
     * @dataProvider subscriptionFieldsProvider
420
     */
421
    public function testGetSubscriptionFields(string $resourceClass, Operation $operation, GraphQLType $graphqlType, GraphQLType $inputGraphqlType, ?callable $subscriptionResolver, array $expectedSubscriptionFields): void
422
    {
423
        $this->resourceClassResolverProphecy->isResourceClass($resourceClass)->willReturn(true);
×
424
        $this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType);
×
425
        $this->typeConverterProphecy->convertType(Argument::type(Type::class), true, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($inputGraphqlType);
×
426
        $this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false);
×
427
        $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operation->getName() => $operation])]));
×
428
        $this->itemSubscriptionResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($subscriptionResolver);
×
429

430
        $subscriptionFields = $this->fieldsBuilder->getSubscriptionFields($resourceClass, $operation);
×
431

432
        $this->assertSame($expectedSubscriptionFields, $subscriptionFields);
×
433
    }
434

435
    public static function subscriptionFieldsProvider(): array
436
    {
437
        return [
×
438
            'mercure not enabled' => ['resourceClass', (new Subscription())->withClass('resourceClass')->withName('action')->withShortName('ShortName'), new ObjectType(['name' => 'subscription', 'fields' => []]), new ObjectType(['name' => 'input', 'fields' => []]), null, [],
×
439
            ],
×
440
            'nominal case with deprecation reason' => ['resourceClass', (new Subscription())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withMercure(true)->withDeprecationReason('not useful'), $graphqlType = new ObjectType(['name' => 'subscription', 'fields' => []]), $inputGraphqlType = new ObjectType(['name' => 'input', 'fields' => []]), $subscriptionResolver = function (): void {
×
441
            },
×
442
                [
×
443
                    'actionShortNameSubscribe' => [
×
444
                        'type' => $graphqlType,
×
445
                        'description' => 'Subscribes to the action event of a ShortName.',
×
446
                        'args' => [
×
447
                            'input' => [
×
448
                                'type' => $inputGraphqlType,
×
449
                                'description' => null,
×
450
                                'args' => [],
×
451
                                'resolve' => null,
×
452
                                'deprecationReason' => 'not useful',
×
453
                            ],
×
454
                        ],
×
455
                        'resolve' => $subscriptionResolver,
×
456
                        'deprecationReason' => 'not useful',
×
457
                    ],
×
458
                ],
×
459
            ],
×
460
            'custom description' => ['resourceClass', (new Subscription())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withMercure(true)->withDescription('Custom description.'), $graphqlType = new ObjectType(['name' => 'subscription', 'fields' => []]), $inputGraphqlType = new ObjectType(['name' => 'input', 'fields' => []]), $subscriptionResolver = function (): void {
×
461
            },
×
462
                [
×
463
                    'actionShortNameSubscribe' => [
×
464
                        'type' => $graphqlType,
×
465
                        'description' => 'Custom description.',
×
466
                        'args' => [
×
467
                            'input' => [
×
468
                                'type' => $inputGraphqlType,
×
469
                                'description' => null,
×
470
                                'args' => [],
×
471
                                'resolve' => null,
×
472
                                'deprecationReason' => null,
×
473
                            ],
×
474
                        ],
×
475
                        'resolve' => $subscriptionResolver,
×
476
                        'deprecationReason' => null,
×
477
                    ],
×
478
                ],
×
479
            ],
×
480
        ];
×
481
    }
482

483
    /**
484
     * @dataProvider resourceObjectTypeFieldsProvider
485
     */
486
    public function testGetResourceObjectTypeFields(string $resourceClass, Operation $operation, array $properties, bool $input, int $depth, ?array $ioMetadata, array $expectedResourceObjectTypeFields, callable $advancedNameConverterFactory = null): void
487
    {
488
        $this->resourceClassResolverProphecy->isResourceClass($resourceClass)->willReturn(true);
×
489
        $this->resourceClassResolverProphecy->isResourceClass('nestedResourceClass')->willReturn(true);
×
490
        $this->resourceClassResolverProphecy->isResourceClass('nestedResourceNoQueryClass')->willReturn(true);
×
491
        $this->resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(false);
×
492
        $this->propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection(array_keys($properties)));
×
493
        foreach ($properties as $propertyName => $propertyMetadata) {
×
494
            $this->propertyMetadataFactoryProphecy->create($resourceClass, $propertyName, ['normalization_groups' => null, 'denormalization_groups' => null])->willReturn($propertyMetadata);
×
495
            $this->typeConverterProphecy->convertType(new Type(Type::BUILTIN_TYPE_NULL), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), '', $resourceClass, $propertyName, $depth + 1)->willReturn(null);
×
496
            $this->typeConverterProphecy->convertType(new Type(Type::BUILTIN_TYPE_CALLABLE), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), '', $resourceClass, $propertyName, $depth + 1)->willReturn('NotRegisteredType');
×
497
            $this->typeConverterProphecy->convertType(Argument::type(Type::class), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), '', $resourceClass, $propertyName, $depth + 1)->willReturn(GraphQLType::string());
×
498
            $this->typeConverterProphecy->convertType(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), '', $resourceClass, $propertyName, $depth + 1)->willReturn(GraphQLType::nonNull(GraphQLType::listOf(GraphQLType::nonNull(GraphQLType::string()))));
×
499

500
            if ('propertyObject' === $propertyName) {
×
501
                $this->typeConverterProphecy->convertType(Argument::type(Type::class), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), 'objectClass', $resourceClass, $propertyName, $depth + 1)->willReturn(new ObjectType(['name' => 'objectType', 'fields' => []]));
×
502
                $this->itemResolverFactoryProphecy->__invoke('objectClass', $resourceClass, $operation)->willReturn(static function (): void {
×
503
                });
×
504
            }
505
            if ('propertyNestedResource' === $propertyName) {
×
506
                $nestedResourceQueryOperation = new Query();
×
507
                $this->resourceMetadataCollectionFactoryProphecy->create('nestedResourceClass')->willReturn(new ResourceMetadataCollection('nestedResourceClass', [(new ApiResource())->withGraphQlOperations(['item_query' => $nestedResourceQueryOperation])]));
×
508
                $this->typeConverterProphecy->convertType(Argument::type(Type::class), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), 'nestedResourceClass', $resourceClass, $propertyName, $depth + 1)->willReturn(new ObjectType(['name' => 'objectType', 'fields' => []]));
×
509
                $this->itemResolverFactoryProphecy->__invoke('nestedResourceClass', $resourceClass, $nestedResourceQueryOperation)->willReturn(static function (): void {
×
510
                });
×
511
            }
512
        }
513
        $this->typesContainerProphecy->has('NotRegisteredType')->willReturn(false);
×
514
        $this->typesContainerProphecy->all()->willReturn([]);
×
515
        $this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false);
×
516

517
        $fieldsBuilder = $this->fieldsBuilder;
×
518
        if ($advancedNameConverterFactory) {
×
519
            $fieldsBuilder = $this->buildFieldsBuilder($advancedNameConverterFactory($this));
×
520
        }
521
        $resourceObjectTypeFields = $fieldsBuilder->getResourceObjectTypeFields($resourceClass, $operation, $input, $depth, $ioMetadata);
×
522

523
        $this->assertEquals($expectedResourceObjectTypeFields, $resourceObjectTypeFields);
×
524
    }
525

526
    public static function resourceObjectTypeFieldsProvider(): iterable
527
    {
528
        $advancedNameConverterFactory = function (self $that): AdvancedNameConverterInterface {
×
529
            $advancedNameConverterProphecy = $that->prophesize(AdvancedNameConverterInterface::class);
×
530
            $advancedNameConverterProphecy->normalize('field', 'resourceClass')->willReturn('normalizedField');
×
531

532
            return $advancedNameConverterProphecy->reveal();
×
533
        };
×
534

535
        yield 'query' => ['resourceClass', (new Query())->withClass('resourceClass'),
×
536
            [
×
537
                'property' => new ApiProperty(),
×
538
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(true)->withWritable(false),
×
539
                'propertyNotReadable' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(false),
×
540
                'nameConverted' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withReadable(true)->withWritable(false),
×
541
            ],
×
542
            false, 0, null,
×
543
            [
×
544
                'id' => [
×
545
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
546
                ],
×
547
                'propertyBool' => [
×
548
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
549
                    'description' => null,
×
550
                    'args' => [],
×
551
                    'resolve' => null,
×
552
                    'deprecationReason' => null,
×
553
                ],
×
554
                'name_converted' => [
×
555
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
556
                    'description' => null,
×
557
                    'args' => [],
×
558
                    'resolve' => null,
×
559
                    'deprecationReason' => null,
×
560
                ],
×
561
            ],
×
562
        ];
×
563
        yield 'query with advanced name converter' => ['resourceClass', (new Query())->withClass('resourceClass'),
×
564
            [
×
565
                'field' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withReadable(true)->withWritable(false),
×
566
            ],
×
567
            false, 0, null,
×
568
            [
×
569
                'id' => [
×
570
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
571
                ],
×
572
                'normalizedField' => [
×
573
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
574
                    'description' => null,
×
575
                    'args' => [],
×
576
                    'resolve' => null,
×
577
                    'deprecationReason' => null,
×
578
                ],
×
579
            ],
×
580
            $advancedNameConverterFactory,
×
581
        ];
×
582
        yield 'query input' => ['resourceClass', (new Query())->withClass('resourceClass'),
×
583
            [
×
584
                'property' => new ApiProperty(),
×
585
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(false),
×
586
            ],
×
587
            true, 0, null,
×
588
            [
×
589
                'id' => [
×
590
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
591
                ],
×
592
                'propertyBool' => [
×
593
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
594
                    'description' => null,
×
595
                    'args' => [],
×
596
                    'resolve' => null,
×
597
                    'deprecationReason' => null,
×
598
                ],
×
599
            ],
×
600
        ];
×
601
        yield 'query with simple non-null string array property' => ['resourceClass', (new Query())->withClass('resourceClass'),
×
602
            [
×
603
                'property' => (new ApiProperty())->withBuiltinTypes([
×
604
                    new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)),
×
605
                ])->withReadable(true)->withWritable(false),
×
606
            ],
×
607
            false, 0, null,
×
608
            [
×
609
                'id' => [
×
610
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
611
                ],
×
612
                'property' => [
×
613
                    'type' => GraphQLType::nonNull(GraphQLType::listOf(GraphQLType::nonNull(GraphQLType::string()))),
×
614
                    'description' => null,
×
615
                    'args' => [],
×
616
                    'resolve' => null,
×
617
                    'deprecationReason' => null,
×
618
                ],
×
619
            ],
×
620
        ];
×
621
        yield 'query with nested resources' => ['resourceClass', (new Query())->withClass('resourceClass'),
×
622
            [
×
623
                'propertyNestedResource' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'nestedResourceClass')])->withReadable(true)->withWritable(true),
×
624
            ],
×
625
            false, 0, null,
×
626
            [
×
627
                'id' => [
×
628
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
629
                ],
×
630
                'propertyNestedResource' => [
×
631
                    'type' => GraphQLType::nonNull(new ObjectType(['name' => 'objectType', 'fields' => []])),
×
632
                    'description' => null,
×
633
                    'args' => [],
×
634
                    'resolve' => static function (): void {
×
635
                    },
×
636
                    'deprecationReason' => null,
×
637
                ],
×
638
            ],
×
639
        ];
×
640
        yield 'mutation non input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('mutation'),
×
641
            [
×
642
                'property' => new ApiProperty(),
×
643
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
×
644
                'propertyReadable' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(true)->withWritable(true),
×
645
                'propertyObject' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'objectClass')])->withReadable(true)->withWritable(true),
×
646
            ],
×
647
            false, 0, null,
×
648
            [
×
649
                'id' => [
×
650
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
651
                ],
×
652
                'propertyReadable' => [
×
653
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
654
                    'description' => null,
×
655
                    'args' => [],
×
656
                    'resolve' => null,
×
657
                    'deprecationReason' => null,
×
658
                ],
×
659
                'propertyObject' => [
×
660
                    'type' => GraphQLType::nonNull(new ObjectType(['name' => 'objectType', 'fields' => []])),
×
661
                    'description' => null,
×
662
                    'args' => [],
×
663
                    'resolve' => static function (): void {
×
664
                    },
×
665
                    'deprecationReason' => null,
×
666
                ],
×
667
            ],
×
668
        ];
×
669
        yield 'mutation input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('mutation'),
×
670
            [
×
671
                'property' => new ApiProperty(),
×
672
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('propertyBool description')->withReadable(false)->withWritable(true)->withDeprecationReason('not useful'),
×
673
                'propertySubresource' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
×
674
                'id' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])->withReadable(false)->withWritable(true),
×
675
            ],
×
676
            true, 0, null,
×
677
            [
×
678
                'id' => [
×
679
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
680
                ],
×
681
                'propertyBool' => [
×
682
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
683
                    'description' => 'propertyBool description',
×
684
                    'args' => [],
×
685
                    'resolve' => null,
×
686
                    'deprecationReason' => 'not useful',
×
687
                ],
×
688
                'propertySubresource' => [
×
689
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
690
                    'description' => null,
×
691
                    'args' => [],
×
692
                    'resolve' => null,
×
693
                    'deprecationReason' => null,
×
694
                ],
×
695
                '_id' => [
×
696
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
697
                    'description' => null,
×
698
                    'args' => [],
×
699
                    'resolve' => null,
×
700
                    'deprecationReason' => null,
×
701
                ],
×
702
                'clientMutationId' => GraphQLType::string(),
×
703
            ],
×
704
        ];
×
705
        yield 'custom mutation' => ['resourceClass', (new Mutation())->withResolver('resolver')->withName('mutation'),
×
706
            [
×
707
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('propertyBool description')->withReadable(false)->withWritable(true),
×
708
            ],
×
709
            true, 0, null,
×
710
            [
×
711
                'propertyBool' => [
×
712
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
713
                    'description' => 'propertyBool description',
×
714
                    'args' => [],
×
715
                    'resolve' => null,
×
716
                    'deprecationReason' => null,
×
717
                ],
×
718
                'clientMutationId' => GraphQLType::string(),
×
719
            ],
×
720
        ];
×
721
        yield 'mutation nested input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('mutation'),
×
722
            [
×
723
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
×
724
            ],
×
725
            true, 1, null,
×
726
            [
×
727
                'id' => [
×
728
                    'type' => GraphQLType::id(),
×
729
                ],
×
730
                'propertyBool' => [
×
731
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
732
                    'description' => null,
×
733
                    'args' => [],
×
734
                    'resolve' => null,
×
735
                    'deprecationReason' => null,
×
736
                ],
×
737
                'clientMutationId' => GraphQLType::string(),
×
738
            ],
×
739
        ];
×
740
        yield 'delete mutation input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('delete'),
×
741
            [
×
742
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
×
743
            ],
×
744
            true, 0, null,
×
745
            [
×
746
                'id' => [
×
747
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
748
                ],
×
749
                'clientMutationId' => GraphQLType::string(),
×
750
            ],
×
751
        ];
×
752
        yield 'create mutation input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('create'),
×
753
            [
×
754
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
×
755
            ],
×
756
            true, 0, null,
×
757
            [
×
758
                'propertyBool' => [
×
759
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
760
                    'description' => null,
×
761
                    'args' => [],
×
762
                    'resolve' => null,
×
763
                    'deprecationReason' => null,
×
764
                ],
×
765
                'clientMutationId' => GraphQLType::string(),
×
766
            ],
×
767
        ];
×
768
        yield 'update mutation input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('update'),
×
769
            [
×
770
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
×
771
            ],
×
772
            true, 0, null,
×
773
            [
×
774
                'id' => [
×
775
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
776
                ],
×
777
                'propertyBool' => [
×
778
                    'type' => GraphQLType::string(),
×
779
                    'description' => null,
×
780
                    'args' => [],
×
781
                    'resolve' => null,
×
782
                    'deprecationReason' => null,
×
783
                ],
×
784
                'clientMutationId' => GraphQLType::string(),
×
785
            ],
×
786
        ];
×
787
        yield 'subscription non input' => ['resourceClass', (new Subscription())->withClass('resourceClass'),
×
788
            [
×
789
                'property' => new ApiProperty(),
×
790
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
×
791
                'propertyReadable' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(true)->withWritable(true),
×
792
            ],
×
793
            false, 0, null,
×
794
            [
×
795
                'id' => [
×
796
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
797
                ],
×
798
                'propertyReadable' => [
×
799
                    'type' => GraphQLType::nonNull(GraphQLType::string()),
×
800
                    'description' => null,
×
801
                    'args' => [],
×
802
                    'resolve' => null,
×
803
                    'deprecationReason' => null,
×
804
                ],
×
805
            ],
×
806
        ];
×
807
        yield 'subscription input' => ['resourceClass', (new Subscription())->withClass('resourceClass'),
×
808
            [
×
809
                'property' => new ApiProperty(),
×
810
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('propertyBool description')->withReadable(false)->withWritable(true)->withDeprecationReason('not useful'),
×
811
                'propertySubresource' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
×
812
                'id' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])->withReadable(false)->withWritable(true),
×
813
            ],
×
814
            true, 0, null,
×
815
            [
×
816
                'id' => [
×
817
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
818
                ],
×
819
                'clientSubscriptionId' => GraphQLType::string(),
×
820
            ],
×
821
        ];
×
822
        yield 'null io metadata non input' => ['resourceClass', (new Query())->withClass('resourceClass'),
×
823
            [
×
824
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
×
825
            ],
×
826
            false, 0, ['class' => null], [],
×
827
        ];
×
828
        yield 'null io metadata input' => ['resourceClass', (new Query())->withClass('resourceClass'),
×
829
            [
×
830
                'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
×
831
            ],
×
832
            true, 0, ['class' => null],
×
833
            [
×
834
                'clientMutationId' => GraphQLType::string(),
×
835
            ],
×
836
        ];
×
837
        yield 'invalid types' => ['resourceClass', (new Query())->withClass('resourceClass'),
×
838
            [
×
839
                'propertyInvalidType' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_NULL)])->withReadable(true)->withWritable(false),
×
840
                'propertyNotRegisteredType' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_CALLABLE)])->withReadable(true)->withWritable(false),
×
841
            ],
×
842
            false, 0, null,
×
843
            [
×
844
                'id' => [
×
845
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
×
846
                ],
×
847
            ],
×
848
        ];
×
849
    }
850

851
    public function testGetEnumFields(): void
852
    {
853
        $enumClass = GenderTypeEnum::class;
×
854

855
        $this->propertyMetadataFactoryProphecy->create($enumClass, GenderTypeEnum::MALE->name)->willReturn(new ApiProperty(
×
856
            description: 'Description of MALE case',
×
857
        ));
×
858
        $this->propertyMetadataFactoryProphecy->create($enumClass, GenderTypeEnum::FEMALE->name)->willReturn(new ApiProperty(
×
859
            description: 'Description of FEMALE case',
×
860
        ));
×
861

862
        $enumFields = $this->fieldsBuilder->getEnumFields($enumClass);
×
863

864
        $this->assertSame([
×
865
            GenderTypeEnum::MALE->name => ['value' => GenderTypeEnum::MALE->value, 'description' => 'Description of MALE case'],
×
866
            GenderTypeEnum::FEMALE->name => ['value' => GenderTypeEnum::FEMALE->value, 'description' => 'Description of FEMALE case'],
×
867
        ], $enumFields);
×
868
    }
869

870
    /**
871
     * @dataProvider resolveResourceArgsProvider
872
     */
873
    public function testResolveResourceArgs(array $args, array $expectedResolvedArgs, string $expectedExceptionMessage = null): void
874
    {
875
        if (null !== $expectedExceptionMessage) {
×
876
            $this->expectExceptionMessage($expectedExceptionMessage);
×
877
        }
878

879
        $this->typeConverterProphecy->resolveType(Argument::type('string'))->willReturn(GraphQLType::string());
×
880

881
        /** @var Operation $operation */
882
        $operation = (new Query())->withName('operation')->withShortName('shortName');
×
883
        $args = $this->fieldsBuilder->resolveResourceArgs($args, $operation);
×
884

885
        $this->assertSame($expectedResolvedArgs, $args);
×
886
    }
887

888
    public static function resolveResourceArgsProvider(): array
889
    {
890
        return [
×
891
            [[], []],
×
892
            [['customArg' => []], [], 'The argument "customArg" of the custom operation "operation" in shortName needs a "type" option.'],
×
893
            [['customArg' => ['type' => 'a type']], ['customArg' => ['type' => GraphQLType::string()]]],
×
894
        ];
×
895
    }
896
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc