From c19da417399189b1524c29d380715472401fb8ea Mon Sep 17 00:00:00 2001 From: ili101 Date: Mon, 8 Jul 2024 23:13:53 +0000 Subject: [PATCH] Test ID --- api/composer.json | 3 +- api/composer.lock | 220 ++++++++++++++++++++- api/config/bundles.php | 1 + api/config/packages/api_platform.yaml | 2 + api/config/packages/zenstruck_foundry.yaml | 7 + api/migrations/Version20240708230111.php | 47 +++++ api/src/Entity/Main.php | 130 ++++++++++++ api/src/Entity/Sub.php | 64 ++++++ api/src/Repository/MainRepository.php | 48 +++++ api/src/Repository/SubRepository.php | 48 +++++ api/symfony.lock | 12 ++ api/tests/Api/MainsTest.php | 168 ++++++++++++++++ 12 files changed, 748 insertions(+), 2 deletions(-) create mode 100644 api/config/packages/zenstruck_foundry.yaml create mode 100644 api/migrations/Version20240708230111.php create mode 100644 api/src/Entity/Main.php create mode 100644 api/src/Entity/Sub.php create mode 100644 api/src/Repository/MainRepository.php create mode 100644 api/src/Repository/SubRepository.php create mode 100644 api/tests/Api/MainsTest.php diff --git a/api/composer.json b/api/composer.json index 79d14a2aca5..3794b0a5d07 100644 --- a/api/composer.json +++ b/api/composer.json @@ -38,7 +38,8 @@ "symfony/phpunit-bridge": "6.4.*", "symfony/stopwatch": "6.4.*", "symfony/var-dumper": "6.4.*", - "symfony/web-profiler-bundle": "6.4.*" + "symfony/web-profiler-bundle": "6.4.*", + "zenstruck/foundry": "^2.0" }, "config": { "optimize-autoloader": true, diff --git a/api/composer.lock b/api/composer.lock index d6260742e0f..adc06c056ae 100644 --- a/api/composer.lock +++ b/api/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "30adfa37808ab6ae4a0ee1dc66860f87", + "content-hash": "844a9702a3b0f15f833ea363652650a5", "packages": [ { "name": "api-platform/core", @@ -6817,6 +6817,69 @@ }, "time": "2023-08-08T05:53:35+00:00" }, + { + "name": "fakerphp/faker", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + }, + "time": "2024-01-02T13:46:09+00:00" + }, { "name": "fidry/cpu-core-counter", "version": "1.1.0", @@ -8837,6 +8900,161 @@ ], "time": "2024-04-18T09:22:46+00:00" }, + { + "name": "zenstruck/assert", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/zenstruck/assert.git", + "reference": "60956bb6584a51c6c2ab9fa8707b7c013d770163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zenstruck/assert/zipball/60956bb6584a51c6c2ab9fa8707b7c013d770163", + "reference": "60956bb6584a51c6c2ab9fa8707b7c013d770163", + "shasum": "" + }, + "require": { + "php": ">=8.0", + "symfony/polyfill-php81": "^1.23", + "symfony/var-exporter": "^5.4|^6.0|^7.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.5", + "symfony/phpunit-bridge": "^6.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Zenstruck\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kevin Bond", + "email": "kevinbond@gmail.com" + } + ], + "description": "Standalone, lightweight, framework agnostic, test assertion library.", + "homepage": "https://github.com/zenstruck/assert", + "keywords": [ + "assertion", + "phpunit", + "test" + ], + "support": { + "issues": "https://github.com/zenstruck/assert/issues", + "source": "https://github.com/zenstruck/assert/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://github.com/kbond", + "type": "github" + } + ], + "time": "2023-12-02T09:08:04+00:00" + }, + { + "name": "zenstruck/foundry", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/zenstruck/foundry.git", + "reference": "0989c5db9aaf7565bd00263c0d8259539c269434" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zenstruck/foundry/zipball/0989c5db9aaf7565bd00263c0d8259539c269434", + "reference": "0989c5db9aaf7565bd00263c0d8259539c269434", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^2.0|^3.0", + "fakerphp/faker": "^1.23", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.2|^3.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "zenstruck/assert": "^1.4" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8", + "dama/doctrine-test-bundle": "^7.0|^8.0", + "doctrine/collections": "^1.7|^2.0", + "doctrine/common": "^3.2", + "doctrine/doctrine-bundle": "^2.10", + "doctrine/doctrine-migrations-bundle": "^2.2|^3.0", + "doctrine/mongodb-odm-bundle": "^4.6|^5.0", + "doctrine/orm": "^2.16|^3.0", + "phpunit/phpunit": "^9.5.0", + "symfony/console": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/maker-bundle": "^1.55", + "symfony/phpunit-bridge": "^6.4|^7.0", + "symfony/translation-contracts": "^3.4", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "target-directory": "bin/tools", + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions.php", + "src/Persistence/functions.php", + "src/phpunit_helper.php" + ], + "psr-4": { + "Zenstruck\\Foundry\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kevin Bond", + "email": "kevinbond@gmail.com" + }, + { + "name": "Nicolas PHILIPPE", + "email": "nikophil@gmail.com" + } + ], + "description": "A model factory library for creating expressive, auto-completable, on-demand dev/test fixtures with Symfony and Doctrine.", + "homepage": "https://github.com/zenstruck/foundry", + "keywords": [ + "Fixture", + "dev", + "doctrine", + "factory", + "faker", + "symfony", + "test" + ], + "support": { + "issues": "https://github.com/zenstruck/foundry/issues", + "source": "https://github.com/zenstruck/foundry/tree/v2.0.4" + }, + "funding": [ + { + "url": "https://github.com/kbond", + "type": "github" + } + ], + "time": "2024-06-20T16:34:40+00:00" + }, { "name": "zozlak/rdf-constants", "version": "1.2.1", diff --git a/api/config/bundles.php b/api/config/bundles.php index e33ab8c484f..090e8bd5132 100644 --- a/api/config/bundles.php +++ b/api/config/bundles.php @@ -13,4 +13,5 @@ Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true], + Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['dev' => true, 'test' => true], ]; diff --git a/api/config/packages/api_platform.yaml b/api/config/packages/api_platform.yaml index 27fe9908d1b..daf39074e1d 100644 --- a/api/config/packages/api_platform.yaml +++ b/api/config/packages/api_platform.yaml @@ -18,6 +18,8 @@ api_platform: extra_properties: standard_put: true rfc_7807_compliant_errors: true + denormalization_context: + allow_extra_attributes: false # change this to true if you use controllers use_symfony_listeners: false keep_legacy_inflector: false diff --git a/api/config/packages/zenstruck_foundry.yaml b/api/config/packages/zenstruck_foundry.yaml new file mode 100644 index 00000000000..0657d2c3c93 --- /dev/null +++ b/api/config/packages/zenstruck_foundry.yaml @@ -0,0 +1,7 @@ +when@dev: &dev + # See full configuration: https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#full-default-bundle-configuration + zenstruck_foundry: + # Whether to auto-refresh proxies by default (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#auto-refresh) + auto_refresh_proxies: true + +when@test: *dev diff --git a/api/migrations/Version20240708230111.php b/api/migrations/Version20240708230111.php new file mode 100644 index 00000000000..d12f1d12151 --- /dev/null +++ b/api/migrations/Version20240708230111.php @@ -0,0 +1,47 @@ +addSql('CREATE SEQUENCE main_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE sub_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE main (id INT NOT NULL, sub1_id INT DEFAULT NULL, sub2_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_BF28CD6429662E71 ON main (sub1_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_BF28CD643BD3819F ON main (sub2_id)'); + $this->addSql('CREATE TABLE sub (id INT NOT NULL, main_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_580282DC627EA78A ON sub (main_id)'); + $this->addSql('ALTER TABLE main ADD CONSTRAINT FK_BF28CD6429662E71 FOREIGN KEY (sub1_id) REFERENCES sub (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE main ADD CONSTRAINT FK_BF28CD643BD3819F FOREIGN KEY (sub2_id) REFERENCES sub (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE sub ADD CONSTRAINT FK_580282DC627EA78A FOREIGN KEY (main_id) REFERENCES main (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP SEQUENCE main_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE sub_id_seq CASCADE'); + $this->addSql('ALTER TABLE main DROP CONSTRAINT FK_BF28CD6429662E71'); + $this->addSql('ALTER TABLE main DROP CONSTRAINT FK_BF28CD643BD3819F'); + $this->addSql('ALTER TABLE sub DROP CONSTRAINT FK_580282DC627EA78A'); + $this->addSql('DROP TABLE main'); + $this->addSql('DROP TABLE sub'); + } +} diff --git a/api/src/Entity/Main.php b/api/src/Entity/Main.php new file mode 100644 index 00000000000..f394c9c025c --- /dev/null +++ b/api/src/Entity/Main.php @@ -0,0 +1,130 @@ + ['Main:write']], + denormalizationContext: [ + AbstractNormalizer::GROUPS => ['Main:write'], + ], +)] +class Main +{ + #[Groups(groups: ['Main:write'])] + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[Groups(groups: ['Main:write'])] + #[ORM\Column(length: 255)] + private ?string $name = null; + + #[Groups(groups: ['Main:write'])] + #[ORM\OneToMany(mappedBy: 'main', targetEntity: Sub::class, orphanRemoval: true, cascade: ['remove', 'persist', 'refresh', 'merge', 'detach'])] + #[ApiProperty(readableLink: true, writableLink: true)] + private Collection $subs; + + #[Groups(groups: ['Main:write'])] + #[ApiProperty(readableLink: true, writableLink: true)] + #[ORM\OneToOne(cascade: ['persist', 'remove'])] + private ?Sub $sub1 = null; + + #[Groups(groups: ['Main:write'])] + #[ApiProperty(readableLink: true, writableLink: true)] + #[ORM\OneToOne(cascade: ['persist', 'remove'])] + private ?Sub $sub2 = null; + + public function __construct() + { + $this->subs = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function setId(int $id): static + { + $this->id = $id; + + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + /** + * @return Collection + */ + public function getSubs(): Collection + { + return $this->subs; + } + + public function addSub(Sub $sub): static + { + if (!$this->subs->contains($sub)) { + $this->subs->add($sub); + $sub->setMain($this); + } + + return $this; + } + + public function removeSub(Sub $sub): static + { + if ($this->subs->removeElement($sub)) { + // set the owning side to null (unless already changed) + if ($sub->getMain() === $this) { + $sub->setMain(null); + } + } + + return $this; + } + + public function getSub1(): ?Sub + { + return $this->sub1; + } + + public function setSub1(?Sub $sub1): static + { + $this->sub1 = $sub1; + + return $this; + } + + public function getSub2(): ?Sub + { + return $this->sub2; + } + + public function setSub2(?Sub $sub2): static + { + $this->sub2 = $sub2; + + return $this; + } +} diff --git a/api/src/Entity/Sub.php b/api/src/Entity/Sub.php new file mode 100644 index 00000000000..c0ad51d6ed4 --- /dev/null +++ b/api/src/Entity/Sub.php @@ -0,0 +1,64 @@ +id; + } + + public function setId(int $id): static + { + $this->id = $id; + + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getMain(): ?Main + { + return $this->main; + } + + public function setMain(?Main $main): static + { + $this->main = $main; + + return $this; + } +} diff --git a/api/src/Repository/MainRepository.php b/api/src/Repository/MainRepository.php new file mode 100644 index 00000000000..6803407b5bf --- /dev/null +++ b/api/src/Repository/MainRepository.php @@ -0,0 +1,48 @@ + + * + * @method Main|null find($id, $lockMode = null, $lockVersion = null) + * @method Main|null findOneBy(array $criteria, array $orderBy = null) + * @method Main[] findAll() + * @method Main[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class MainRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Main::class); + } + + // /** + // * @return Main[] Returns an array of Main objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('m') + // ->andWhere('m.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('m.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Main + // { + // return $this->createQueryBuilder('m') + // ->andWhere('m.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/api/src/Repository/SubRepository.php b/api/src/Repository/SubRepository.php new file mode 100644 index 00000000000..de46fee0d93 --- /dev/null +++ b/api/src/Repository/SubRepository.php @@ -0,0 +1,48 @@ + + * + * @method Sub|null find($id, $lockMode = null, $lockVersion = null) + * @method Sub|null findOneBy(array $criteria, array $orderBy = null) + * @method Sub[] findAll() + * @method Sub[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class SubRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Sub::class); + } + + // /** + // * @return Sub[] Returns an array of Sub objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('s') + // ->andWhere('s.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('s.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Sub + // { + // return $this->createQueryBuilder('s') + // ->andWhere('s.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/api/symfony.lock b/api/symfony.lock index d9db8963b49..6dbd0c5dbc7 100644 --- a/api/symfony.lock +++ b/api/symfony.lock @@ -500,5 +500,17 @@ }, "zendframework/zend-eventmanager": { "version": "3.2.1" + }, + "zenstruck/foundry": { + "version": "2.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.10", + "ref": "37c2f894cc098ab4c08874b80cccc8e2f8de7976" + }, + "files": [ + "config/packages/zenstruck_foundry.yaml" + ] } } diff --git a/api/tests/Api/MainsTest.php b/api/tests/Api/MainsTest.php new file mode 100644 index 00000000000..42f22e92761 --- /dev/null +++ b/api/tests/Api/MainsTest.php @@ -0,0 +1,168 @@ +request('POST', '/mains', [ + 'json' => [ + 'name' => 'Main1', + 'subs' => [ + [ + 'name' => 'SubArray1', + ], + [ + 'name' => 'SubArray2', + ], + ], + 'sub1' => [ + 'name' => 'Sub1', + ], + 'sub2' => [ + 'name' => 'Sub2', + ], + ], + 'headers' => [ + 'Content-Type' => 'application/ld+json', + ], + ]); + $this->assertResponseStatusCodeSame(201); + $this->assertJsonContains([ + '@context' => '/contexts/Main', + '@id' => '/mains/1', + '@type' => 'Main', + 'id' => 1, + 'name' => 'Main1', + 'subs' => + [ + 0 => + [ + '@id' => '/subs/1', + '@type' => 'Sub', + 'id' => 1, + 'name' => 'SubArray1', + ], + 1 => + [ + '@id' => '/subs/2', + '@type' => 'Sub', + 'id' => 2, + 'name' => 'SubArray2', + ], + ], + 'sub1' => + [ + '@id' => '/subs/3', + '@type' => 'Sub', + 'id' => 3, + 'name' => 'Sub1', + ], + 'sub2' => + [ + '@id' => '/subs/4', + '@type' => 'Sub', + 'id' => 4, + 'name' => 'Sub2', + ], + ]); + + // Update the main resource and update or remove or add some sub resources. + static::createClient()->request('PUT', '/mains/1', [ + 'json' => [ + '@id' => '/mains/1', + 'name' => 'Main1Edit', + 'subs' => [ + [ + '@id' => '/subs/1', + '@type' => 'Sub', + 'name' => 'SubArray1Update', + ], + [ + 'name' => 'SubArray2Replaced', + ], + ], + 'sub1' => [ + '@id' => '/subs/3', + 'name' => 'Sub1Update', + ], + 'sub2' => [ + 'name' => 'Sub2Replaced', + ], + ], + 'headers' => [ + 'Content-Type' => 'application/ld+json', + ], + ]); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + '@context' => '/contexts/Main', + '@id' => '/mains/1', + '@type' => 'Main', + 'id' => 1, + 'name' => 'Main1Edit', + 'subs' => + [ + 0 => + [ + '@id' => '/subs/1', + '@type' => 'Sub', + 'id' => 1, + 'name' => 'SubArray1Update', + ], + 1 => + [ + '@id' => '/subs/5', + '@type' => 'Sub', + 'id' => 5, + 'name' => 'SubArray2Replaced', + ], + ], + 'sub1' => + [ + '@id' => '/subs/3', + '@type' => 'Sub', + 'id' => 3, + 'name' => 'Sub1Update', + ], + 'sub2' => + [ + '@id' => '/subs/6', + '@type' => 'Sub', + 'id' => 6, + 'name' => 'Sub2Replaced', + ], + ]); + + // Extra attributes are not allowed. + static::createClient()->request('PUT', '/mains/1', [ + 'json' => [ + '@id' => '/mains/1', + 'name' => 'Main1Edit', + 'subs' => [ + [ + '@id' => '/subs/1', + '@type' => 'Sub', + 'name' => 'SubArray1Update', + 'extra' => 'extra', + ], + ], + ], + 'headers' => [ + 'Content-Type' => 'application/ld+json', + ], + ]); + $this->assertResponseStatusCodeSame(400); + $this->assertJsonContains([ + 'detail' => 'Extra attributes are not allowed ("extra" is unknown).', + ]); + } +}