From 299ae92a1e48c5b0a0159dc59420ee139d497cea Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 2 Oct 2026 00:10:02 +0200 Subject: [PATCH] test(graphql): cover nested input without related mutation --- .../TestBundle/Entity/NestedInputItem.php | 37 +++++++++ .../TestBundle/Entity/NestedInputOwner.php | 73 +++++++++++++++++ .../NestedInputWithoutMutationTest.php | 82 +++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 tests/Fixtures/TestBundle/Entity/NestedInputItem.php create mode 100644 tests/Fixtures/TestBundle/Entity/NestedInputOwner.php create mode 100644 tests/Functional/GraphQl/NestedInputWithoutMutationTest.php diff --git a/tests/Fixtures/TestBundle/Entity/NestedInputItem.php b/tests/Fixtures/TestBundle/Entity/NestedInputItem.php new file mode 100644 index 0000000000..51dd042c2f --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/NestedInputItem.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity; + +use ApiPlatform\Metadata\ApiResource; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Attribute\Groups; + +#[ApiResource(graphQlOperations: [])] +#[ORM\Entity] +class NestedInputItem +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] + private ?int $id = null; + + #[ORM\Column(type: 'string')] + #[Groups(['nested_input_write'])] + public string $label = ''; + + public function getId(): ?int + { + return $this->id; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/NestedInputOwner.php b/tests/Fixtures/TestBundle/Entity/NestedInputOwner.php new file mode 100644 index 0000000000..3870336371 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/NestedInputOwner.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GraphQl\Mutation; +use ApiPlatform\Metadata\GraphQl\Query; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Attribute\Groups; + +#[ApiResource( + normalizationContext: ['groups' => ['nested_input_read']], + denormalizationContext: ['groups' => ['nested_input_write']], + graphQlOperations: [ + new Query(), + new Mutation(name: 'create'), + ], +)] +#[ORM\Entity] +class NestedInputOwner +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] + #[Groups(['nested_input_read'])] + private ?int $id = null; + + #[ORM\Column(type: 'string')] + #[Groups(['nested_input_read', 'nested_input_write'])] + public string $name = ''; + + /** + * @var Collection + */ + #[ORM\ManyToMany(targetEntity: NestedInputItem::class, cascade: ['persist'])] + #[ORM\JoinTable(name: 'nested_input_owner_items')] + #[ORM\InverseJoinColumn(unique: true)] + #[Groups(['nested_input_write'])] + public Collection $items; + + public function __construct() + { + $this->items = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function addItem(NestedInputItem $item): void + { + $this->items->add($item); + } + + public function removeItem(NestedInputItem $item): void + { + $this->items->removeElement($item); + } +} diff --git a/tests/Functional/GraphQl/NestedInputWithoutMutationTest.php b/tests/Functional/GraphQl/NestedInputWithoutMutationTest.php new file mode 100644 index 0000000000..3958eca0e0 --- /dev/null +++ b/tests/Functional/GraphQl/NestedInputWithoutMutationTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Functional\GraphQl; + +use ApiPlatform\GraphQl\Test\GraphQlTestTrait; +use ApiPlatform\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\NestedInputItem; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\NestedInputOwner; +use ApiPlatform\Tests\RecreateSchemaTrait; +use ApiPlatform\Tests\SetupClassResourcesTrait; + +final class NestedInputWithoutMutationTest extends ApiTestCase +{ + use GraphQlTestTrait; + use RecreateSchemaTrait; + use SetupClassResourcesTrait; + + protected static ?bool $alwaysBootKernel = false; + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [NestedInputOwner::class, NestedInputItem::class]; + } + + public function testRelatedResourceWithoutMutationIsNotExposedAsMutation(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped('ORM only fixture.'); + } + + $response = $this->introspectSchema(); + + $this->assertResponseIsSuccessful(); + $mutationFields = []; + foreach ($response->toArray()['data']['__schema']['types'] as $type) { + if ('Mutation' === $type['name']) { + $mutationFields = array_column($type['fields'], 'name'); + } + } + $this->assertContains('createNestedInputOwner', $mutationFields); + $this->assertNotContains('createNestedInputItem', $mutationFields); + } + + public function testCreateWithNestedInputPersistsRelatedResource(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped('ORM only fixture.'); + } + + $this->recreateSchema([NestedInputOwner::class]); + + $response = $this->executeGraphQl(<<<'QUERY' + mutation { + createNestedInputOwner(input: {name: "owner", items: [{label: "first"}, {label: "second"}]}) { + nestedInputOwner { id name } + } + } + QUERY); + + $this->assertResponseIsSuccessful(); + $json = $response->toArray(false); + $this->assertArrayNotHasKey('errors', $json, json_encode($json['errors'] ?? null)); + $this->assertSame('owner', $json['data']['createNestedInputOwner']['nestedInputOwner']['name']); + + $items = $this->getManager()->getRepository(NestedInputItem::class)->findBy([], ['id' => 'ASC']); + $this->assertSame(['first', 'second'], array_map(static fn (NestedInputItem $i): string => $i->label, $items)); + } +}