From 62eeb7542c0fb2dbd1f1e41f023dae555d739e8c Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Tue, 18 Jan 2022 16:20:39 -0500 Subject: [PATCH 01/10] [minor] run php-cs-fixer on php 7.2 (#243) --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1aad9b89b..648ee7d79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -347,6 +347,9 @@ jobs: cs-check: uses: zenstruck/.github/.github/workflows/php-cs-fixer.yml@main + with: + php: 7.2 + version: 3.4 static-analysis: name: Psalm Static Analysis From 39fa8e21ed41b8c614ccc300bb0e21b91c0e0f10 Mon Sep 17 00:00:00 2001 From: Aron Beal <48416080+abeal-hottomali@users.noreply.github.com> Date: Thu, 3 Feb 2022 08:28:12 -0800 Subject: [PATCH 02/10] [bug] ignore abstract classes in the maker (#249) --- src/Bundle/Maker/MakeFactory.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Bundle/Maker/MakeFactory.php b/src/Bundle/Maker/MakeFactory.php index 103fe48b9..03ad6eadd 100644 --- a/src/Bundle/Maker/MakeFactory.php +++ b/src/Bundle/Maker/MakeFactory.php @@ -176,6 +176,9 @@ private function entityChoices(): array foreach ($this->managerRegistry->getManagers() as $manager) { foreach ($manager->getMetadataFactory()->getAllMetadata() as $metadata) { + if ($metadata->getReflectionClass()->isAbstract()) { + continue; + } if (!\in_array($metadata->getName(), $this->entitiesWithFactories, true)) { $choices[] = $metadata->getName(); } From 01ebfab04f3192ab85f0e0f7ea7407ea7ae293b6 Mon Sep 17 00:00:00 2001 From: Aron Beal <48416080+abeal-hottomali@users.noreply.github.com> Date: Thu, 3 Feb 2022 11:56:16 -0800 Subject: [PATCH 03/10] [feature] add an 'All' option to make:factory to create all missing factories (#247) --- src/Bundle/Maker/MakeFactory.php | 27 +++++++++++++------ .../Bundle/Maker/MakeFactoryTest.php | 23 ++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/Bundle/Maker/MakeFactory.php b/src/Bundle/Maker/MakeFactory.php index 03ad6eadd..885efeb77 100644 --- a/src/Bundle/Maker/MakeFactory.php +++ b/src/Bundle/Maker/MakeFactory.php @@ -74,6 +74,11 @@ public static function getCommandDescription(): string return 'Creates a Foundry model factory for a Doctrine entity class'; } + public function configureDependencies(DependencyBuilder $dependencies): void + { + // noop + } + public function configureCommand(Command $command, InputConfiguration $inputConfig): void { $command @@ -104,15 +109,26 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma } $argument = $command->getDefinition()->getArgument('entity'); - $entity = $io->choice($argument->getDescription(), $this->entityChoices()); + $entity = $io->choice($argument->getDescription(), \array_merge($this->entityChoices(), ['All'])); $input->setArgument('entity', $entity); } public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void { - $class = $input->getArgument('entity'); + $entity = $input->getArgument('entity'); + $classes = 'All' === $entity ? $this->entityChoices() : [$entity]; + + foreach ($classes as $class) { + $this->generateFactory($class, $input, $io, $generator); + } + } + /** + * Generates a single entity factory. + */ + private function generateFactory(string $class, InputInterface $input, ConsoleStyle $io, Generator $generator) + { if (!\class_exists($class)) { $class = $generator->createClassNameDetails($class, 'Entity\\')->getFullName(); } @@ -165,11 +181,6 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen ]); } - public function configureDependencies(DependencyBuilder $dependencies): void - { - // noop - } - private function entityChoices(): array { $choices = []; @@ -188,7 +199,7 @@ private function entityChoices(): array \sort($choices); if (empty($choices)) { - throw new RuntimeCommandException('No entities or documents found.'); + throw new RuntimeCommandException('No entities or documents found, or none left to make factories for.'); } return $choices; diff --git a/tests/Functional/Bundle/Maker/MakeFactoryTest.php b/tests/Functional/Bundle/Maker/MakeFactoryTest.php index 9bde77096..a15832381 100644 --- a/tests/Functional/Bundle/Maker/MakeFactoryTest.php +++ b/tests/Functional/Bundle/Maker/MakeFactoryTest.php @@ -408,6 +408,29 @@ public function can_customize_namespace_with_test_flag_with_root_namespace_prefi $this->assertStringContainsString('namespace App\\Tests\\My\\Namespace;', \file_get_contents($expectedFile)); } + /** + * @test + */ + public function can_create_factory_with_all_interactively(): void + { + $tester = new CommandTester((new Application(self::bootKernel()))->find('make:factory')); + + $this->assertFileDoesNotExist(self::tempFile('src/Factory/CategoryFactory.php')); + $this->assertFileDoesNotExist(self::tempFile('src/Factory/PostFactory.php')); + + $tester->setInputs(['All']); + + try { + $tester->execute([]); + } catch (RuntimeCommandException $e) { + // todo find a better solution + // because we have fixtures with the same name, the maker will fail when creating the duplicate + } + + $this->assertFileExists(self::tempFile('src/Factory/CategoryFactory.php')); + $this->assertFileExists(self::tempFile('src/Factory/PostFactory.php')); + } + /** * @test * @dataProvider documentProvider From 6977f3ab4300935770892c5704227eb8c8b88401 Mon Sep 17 00:00:00 2001 From: Zairig Imad Date: Mon, 14 Feb 2022 12:13:18 +0100 Subject: [PATCH 04/10] [doc] Use `UserPasswordHasherInterface` instead of `UserPasswordEncoderInterface` (#255) --- docs/index.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 7c27f28ba..35a40178f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -735,7 +735,7 @@ Factories as Services ~~~~~~~~~~~~~~~~~~~~~ If your factories require dependencies, you can define them as a service. The following example demonstrates a very -common use-case: encoding a password with the ``UserPasswordEncoderInterface`` service. +common use-case: encoding a password with the ``UserPasswordHasherInterface`` service. .. code-block:: php @@ -744,18 +744,18 @@ common use-case: encoding a password with the ``UserPasswordEncoderInterface`` s namespace App\Factory; use App\Entity\User; - use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; + use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Zenstruck\Foundry\ModelFactory; final class UserFactory extends ModelFactory { - private $passwordEncoder; + private $passwordHasher; - public function __construct(UserPasswordEncoderInterface $passwordEncoder) + public function __construct(UserPasswordHasherInterface $passwordHasher) { parent::__construct(); - $this->passwordEncoder = $passwordEncoder; + $this->passwordHasher = $passwordHasher; } protected function getDefaults(): array @@ -770,7 +770,7 @@ common use-case: encoding a password with the ``UserPasswordEncoderInterface`` s { return $this ->afterInstantiate(function(User $user) { - $user->setPassword($this->passwordEncoder->encodePassword($user, $user->getPassword())); + $user->setPassword($this->passwordHasher->hashPassword($user, $user->getPassword())); }) ; } From 02609a9c2764627efe0087d213cf1addff0fccb3 Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 19 Feb 2022 13:11:11 +0100 Subject: [PATCH 05/10] [minor] add return type for stub command (deprecated in symfony 6) (#257) Co-authored-by: Christopher Georg --- src/Bundle/Command/StubCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/Command/StubCommand.php b/src/Bundle/Command/StubCommand.php index 93f9a897f..c6eefa8be 100644 --- a/src/Bundle/Command/StubCommand.php +++ b/src/Bundle/Command/StubCommand.php @@ -9,7 +9,7 @@ abstract class StubCommand extends Command { - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { (new SymfonyStyle($input, $output)) ->error( From 02cd0c8d07fab8e837b3d1e94b2118d66fd18c98 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Fri, 4 Mar 2022 09:47:20 -0500 Subject: [PATCH 06/10] [minor] deprecate `Story:add()` and add `Story::addState()` (#254) --- docs/index.rst | 4 ++-- src/Story.php | 29 +++++++++++++++--------- tests/Fixtures/Stories/CategoryStory.php | 4 ++-- tests/Fixtures/Stories/PostStory.php | 6 ++--- tests/Fixtures/Stories/ServiceStory.php | 2 +- tests/Fixtures/Stories/TagStory.php | 4 ++-- tests/Functional/StoryTest.php | 14 +++++++++++- 7 files changed, 41 insertions(+), 22 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 35a40178f..419356b7d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1645,10 +1645,10 @@ Another feature of *stories* is the ability for them to *remember* the objects t { public function build(): void { - $this->add('php', CategoryFactory::createOne(['name' => 'php'])); + $this->addState('php', CategoryFactory::createOne(['name' => 'php'])); // factories are created when added as state - $this->add('symfony', CategoryFactory::new(['name' => 'symfony'])); + $this->addState('symfony', CategoryFactory::new(['name' => 'symfony'])); } } diff --git a/src/Story.php b/src/Story.php index 757180615..6b03b37a9 100644 --- a/src/Story.php +++ b/src/Story.php @@ -32,6 +32,24 @@ final public static function load(): self * @return static */ final public function add(string $name, $state): self + { + trigger_deprecation('zenstruck\foundry', '1.17.0', 'Using Story::add() is deprecated, use Story::addState().'); + + return $this->addState($name, $state); + } + + final public function get(string $name): Proxy + { + if (!\array_key_exists($name, $this->state)) { + throw new \InvalidArgumentException(\sprintf('"%s" was not registered. Did you forget to call "%s::add()"?', $name, static::class)); + } + + return $this->state[$name]; + } + + abstract public function build(): void; + + final protected function addState(string $name, $state): self { if (\is_object($state)) { // ensure factories are persisted @@ -54,15 +72,4 @@ final public function add(string $name, $state): self return $this; } - - final public function get(string $name): Proxy - { - if (!\array_key_exists($name, $this->state)) { - throw new \InvalidArgumentException(\sprintf('"%s" was not registered. Did you forget to call "%s::add()"?', $name, static::class)); - } - - return $this->state[$name]; - } - - abstract public function build(): void; } diff --git a/tests/Fixtures/Stories/CategoryStory.php b/tests/Fixtures/Stories/CategoryStory.php index ea0ecf367..73d7a30ba 100644 --- a/tests/Fixtures/Stories/CategoryStory.php +++ b/tests/Fixtures/Stories/CategoryStory.php @@ -12,7 +12,7 @@ final class CategoryStory extends Story { public function build(): void { - $this->add('php', CategoryFactory::new()->create(['name' => 'php'])); - $this->add('symfony', CategoryFactory::new()->create(['name' => 'symfony'])); + $this->addState('php', CategoryFactory::new()->create(['name' => 'php'])); + $this->addState('symfony', CategoryFactory::new()->create(['name' => 'symfony'])); } } diff --git a/tests/Fixtures/Stories/PostStory.php b/tests/Fixtures/Stories/PostStory.php index 08f369f00..dfc746eea 100644 --- a/tests/Fixtures/Stories/PostStory.php +++ b/tests/Fixtures/Stories/PostStory.php @@ -12,17 +12,17 @@ final class PostStory extends Story { public function build(): void { - $this->add('postA', PostFactory::new()->create([ + $this->addState('postA', PostFactory::new()->create([ 'title' => 'Post A', 'category' => CategoryStory::php(), ])); - $this->add('postB', PostFactory::new()->create([ + $this->addState('postB', PostFactory::new()->create([ 'title' => 'Post B', 'category' => CategoryStory::php(), ])->object()); - $this->add('postC', PostFactory::new([ + $this->addState('postC', PostFactory::new([ 'title' => 'Post C', 'category' => CategoryStory::symfony(), ])); diff --git a/tests/Fixtures/Stories/ServiceStory.php b/tests/Fixtures/Stories/ServiceStory.php index 6b0d575f2..f511f9432 100644 --- a/tests/Fixtures/Stories/ServiceStory.php +++ b/tests/Fixtures/Stories/ServiceStory.php @@ -21,6 +21,6 @@ public function __construct(Service $service) public function build(): void { - $this->add('post', PostFactory::new()->create(['title' => $this->service->name])); + $this->addState('post', PostFactory::new()->create(['title' => $this->service->name])); } } diff --git a/tests/Fixtures/Stories/TagStory.php b/tests/Fixtures/Stories/TagStory.php index 18f433ccb..affb6ecbb 100644 --- a/tests/Fixtures/Stories/TagStory.php +++ b/tests/Fixtures/Stories/TagStory.php @@ -12,7 +12,7 @@ final class TagStory extends Story { public function build(): void { - $this->add('dev', TagFactory::new()->create(['name' => 'dev'])); - $this->add('design', TagFactory::new()->create(['name' => 'design'])); + $this->addState('dev', TagFactory::new()->create(['name' => 'dev'])); + $this->addState('design', TagFactory::new()->create(['name' => 'design'])); } } diff --git a/tests/Functional/StoryTest.php b/tests/Functional/StoryTest.php index abfb08466..b390cb894 100644 --- a/tests/Functional/StoryTest.php +++ b/tests/Functional/StoryTest.php @@ -2,6 +2,7 @@ namespace Zenstruck\Foundry\Tests\Functional; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; @@ -15,7 +16,7 @@ */ final class StoryTest extends KernelTestCase { - use Factories, ResetDatabase; + use ExpectDeprecationTrait, Factories, ResetDatabase; protected function setUp(): void { @@ -90,4 +91,15 @@ public function service_stories_cannot_be_used_without_the_bundle(): void ServiceStory::load(); } + + /** + * @test + * @group legacy + */ + public function calling_add_is_deprecated(): void + { + $this->expectDeprecation('Since zenstruck\foundry 1.17.0: Using Story::add() is deprecated, use Story::addState().'); + + CategoryStory::load()->add('foo', 'bar'); + } } From be6b6c8b9ac8b06724d8f3f3a7d85f44f53e9845 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Thu, 10 Feb 2022 10:34:45 -0500 Subject: [PATCH 07/10] Revert "[feature] Allow any type for Story States (#231)" This reverts commit fb7902219e3d90a7ee94f2a63b7cc98013ccbb32. --- src/Story.php | 59 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/Story.php b/src/Story.php index 6b03b37a9..c2a9bd30e 100644 --- a/src/Story.php +++ b/src/Story.php @@ -7,8 +7,8 @@ */ abstract class Story { - /** @var array */ - private $state = []; + /** @var array */ + private $objects = []; final public function __call(string $method, array $arguments) { @@ -31,44 +31,57 @@ final public static function load(): self /** * @return static */ - final public function add(string $name, $state): self + final public function add(string $name, object $object): self { trigger_deprecation('zenstruck\foundry', '1.17.0', 'Using Story::add() is deprecated, use Story::addState().'); - return $this->addState($name, $state); + return $this->addState($name, $object); } final public function get(string $name): Proxy { - if (!\array_key_exists($name, $this->state)) { + if (!\array_key_exists($name, $this->objects)) { throw new \InvalidArgumentException(\sprintf('"%s" was not registered. Did you forget to call "%s::add()"?', $name, static::class)); } - return $this->state[$name]; + return $this->objects[$name]; } abstract public function build(): void; - final protected function addState(string $name, $state): self + final protected function addState(string $name, object $object): self { - if (\is_object($state)) { - // ensure factories are persisted - if ($state instanceof Factory) { - $state = $state->create(); - } - - // ensure objects are proxied - if (!$state instanceof Proxy) { - $state = new Proxy($state); - } - - // ensure proxies are persisted - if (!$state->isPersisted()) { - $state->save(); - } + // ensure factories are persisted + if ($object instanceof Factory) { + $object = $object->create(); } - $this->state[$name] = $state; + // ensure objects are proxied + if (!$object instanceof Proxy) { + $object = new Proxy($object); + } + + // ensure proxies are persisted + if (!$object->isPersisted()) { + $object->save(); + } + + // ensure factories are persisted + if ($object instanceof Factory) { + $object = $object->create(); + } + + // ensure objects are proxied + if (!$object instanceof Proxy) { + $object = new Proxy($object); + } + + // ensure proxies are persisted + if (!$object->isPersisted()) { + $object->save(); + } + + $this->objects[$name] = $object; return $this; } From 5768345fc64b364b329a2026b7dadcd401a64c79 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Thu, 10 Feb 2022 11:26:01 -0500 Subject: [PATCH 08/10] [feature] add Story "pools" --- docs/index.rst | 44 +++++++- src/Story.php | 105 ++++++++++++++++--- tests/Fixtures/Stories/CategoryPoolStory.php | 21 ++++ tests/Functional/StoryTest.php | 93 +++++++++++++++- 4 files changed, 247 insertions(+), 16 deletions(-) create mode 100644 tests/Fixtures/Stories/CategoryPoolStory.php diff --git a/docs/index.rst b/docs/index.rst index 419356b7d..413125ccd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1656,7 +1656,9 @@ Later, you can access the story's state when creating other fixtures: .. code-block:: php - PostFactory::createOne(['category' => CategoryStory::load()->get('php')]); + PostFactory::createOne([ + 'category' => CategoryStory::load()->get('php') // Category Proxy + ]); // or use the magic method (functionally equivalent to above) PostFactory::createOne(['category' => CategoryStory::php()]); @@ -1665,6 +1667,46 @@ Later, you can access the story's state when creating other fixtures: Story state is cleared after each test (unless it is a :ref:`Global State Story `). +Story Pools +^^^^^^^^^^^ + +Stories can store (as state) *pools* of objects: + +.. code-block:: php + + // src/Story/ProvinceStory.php + + namespace App\Story; + + use App\Factory\ProvinceFactory; + use Zenstruck\Foundry\Story; + + final class ProvinceStory extends Story + { + public function build(): void + { + // add collection to a "pool" + $this->addToPool('be', ProvinceFactory::createMany(5, ['country' => 'BE'])); + + // equivalent to above + $this->addToPool('be', ProvinceFactory::new(['country' => 'BE'])->many(5)); + + // add single object to a pool + $this->addToPool('be', ProvinceFactory::createOne(['country' => 'BE'])); + + // add single object to single pool and make available as "state" + $this->addState('be-1', ProvinceFactory::createOne(['country' => 'BE']), 'be'); + } + } + +Objects can be fetched from pools in your tests, fixtures or other stories: + +.. code-block:: php + + ProvinceStory::getRandom('be'); // random Province|Proxy from "be" pool + ProvinceStory::getRandomSet('be', 3); // 3 random Province|Proxy's from "be" pool + ProvinceStory::getRandomRange('be', 1, 4); // between 1 and 4 random Province|Proxy's from "be" pool + Bundle Configuration -------------------- diff --git a/src/Story.php b/src/Story.php index c2a9bd30e..1388895c6 100644 --- a/src/Story.php +++ b/src/Story.php @@ -10,12 +10,15 @@ abstract class Story /** @var array */ private $objects = []; + /** @var array */ + private $pools = []; + final public function __call(string $method, array $arguments) { return $this->get($method); } - public static function __callStatic($name, $arguments) + final public static function __callStatic($name, $arguments) { return static::load()->get($name); } @@ -29,6 +32,57 @@ final public static function load(): self } /** + * Get a random item from a pool. + */ + final public static function getRandom(string $pool): Proxy + { + return static::getRandomSet($pool, 1)[0]; + } + + /** + * Get a random set of items from a pool. + * + * @return Proxy[] + */ + final public static function getRandomSet(string $pool, int $number): array + { + if ($number < 1) { + throw new \InvalidArgumentException(\sprintf('$number must be positive (%d given).', $number)); + } + + return static::getRandomRange($pool, $number, $number); + } + + /** + * Get a random range of items from a pool. + * + * @return Proxy[] + */ + final public static function getRandomRange(string $pool, int $min, int $max): array + { + if ($min < 0) { + throw new \InvalidArgumentException(\sprintf('$min must be zero or greater (%d given).', $min)); + } + + if ($max < $min) { + throw new \InvalidArgumentException(\sprintf('$max (%d) cannot be less than $min (%d).', $max, $min)); + } + + $story = static::load(); + $values = $story->pools[$pool] ?? []; + + \shuffle($values); + + if (\count($values) < $max) { + throw new \RuntimeException(\sprintf('At least %d items must be in pool "%s" (%d items found).', $max, $pool, \count($values))); + } + + return \array_slice($values, 0, \random_int($min, $max)); + } + + /** + * @param object|Proxy|Factory $object + * * @return static */ final public function add(string $name, object $object): self @@ -49,23 +103,48 @@ final public function get(string $name): Proxy abstract public function build(): void; - final protected function addState(string $name, object $object): self + /** + * @param object|Proxy|Factory|object[]|Proxy[]|Factory[]|FactoryCollection $objects + * + * @return static + */ + final protected function addToPool(string $pool, $objects): self { - // ensure factories are persisted - if ($object instanceof Factory) { - $object = $object->create(); + if ($objects instanceof FactoryCollection) { + $objects = $objects->create(); } - // ensure objects are proxied - if (!$object instanceof Proxy) { - $object = new Proxy($object); + if (!\is_array($objects)) { + $objects = [$objects]; } - // ensure proxies are persisted - if (!$object->isPersisted()) { - $object->save(); + foreach ($objects as $object) { + $this->pools[$pool][] = self::normalizeObject($object); } + return $this; + } + + /** + * @param object|Proxy|Factory $object + * + * @return static + */ + final protected function addState(string $name, object $object, ?string $pool = null): self + { + $proxy = self::normalizeObject($object); + + $this->objects[$name] = $proxy; + + if ($pool) { + $this->addToPool($pool, $proxy); + } + + return $this; + } + + private static function normalizeObject(object $object): Proxy + { // ensure factories are persisted if ($object instanceof Factory) { $object = $object->create(); @@ -81,8 +160,6 @@ final protected function addState(string $name, object $object): self $object->save(); } - $this->objects[$name] = $object; - - return $this; + return $object; } } diff --git a/tests/Fixtures/Stories/CategoryPoolStory.php b/tests/Fixtures/Stories/CategoryPoolStory.php new file mode 100644 index 000000000..572fa188d --- /dev/null +++ b/tests/Fixtures/Stories/CategoryPoolStory.php @@ -0,0 +1,21 @@ + + */ +final class CategoryPoolStory extends Story +{ + public function build(): void + { + $this->addToPool('pool-name', CategoryFactory::createMany(2)); + $this->addToPool('pool-name', CategoryFactory::new()->many(3)); + $this->addToPool('pool-name', CategoryFactory::createOne()); + $this->addToPool('pool-name', CategoryFactory::new()); + $this->addState('state-name', CategoryFactory::new(), 'pool-name'); + } +} diff --git a/tests/Functional/StoryTest.php b/tests/Functional/StoryTest.php index b390cb894..e896674ae 100644 --- a/tests/Functional/StoryTest.php +++ b/tests/Functional/StoryTest.php @@ -6,7 +6,9 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; +use Zenstruck\Foundry\Tests\Fixtures\Factories\CategoryFactory; use Zenstruck\Foundry\Tests\Fixtures\Factories\PostFactory; +use Zenstruck\Foundry\Tests\Fixtures\Stories\CategoryPoolStory; use Zenstruck\Foundry\Tests\Fixtures\Stories\CategoryStory; use Zenstruck\Foundry\Tests\Fixtures\Stories\PostStory; use Zenstruck\Foundry\Tests\Fixtures\Stories\ServiceStory; @@ -100,6 +102,95 @@ public function calling_add_is_deprecated(): void { $this->expectDeprecation('Since zenstruck\foundry 1.17.0: Using Story::add() is deprecated, use Story::addState().'); - CategoryStory::load()->add('foo', 'bar'); + CategoryFactory::assert()->empty(); + + CategoryStory::load()->add('foo', CategoryFactory::new()); + + CategoryFactory::assert()->count(3); + } + + /** + * @test + */ + public function can_get_random_object_from_pool(): void + { + $ids = []; + + while (5 !== \count(\array_unique($ids))) { + $ids[] = CategoryPoolStory::getRandom('pool-name')->getId(); + } + + $this->assertCount(5, \array_unique($ids)); + } + + /** + * @test + */ + public function can_get_random_object_set_from_pool(): void + { + $objects = CategoryPoolStory::getRandomSet('pool-name', 3); + + $this->assertCount(3, $objects); + $this->assertCount(3, \array_unique(\array_map(static function($category) { return $category->getId(); }, $objects))); + } + + /** + * @test + */ + public function can_get_random_object_range_from_pool(): void + { + $counts = []; + + while (4 !== \count(\array_unique($counts))) { + $counts[] = \count(CategoryPoolStory::getRandomRange('pool-name', 0, 3)); + } + + $this->assertCount(4, \array_unique($counts)); + $this->assertContains(0, $counts); + $this->assertContains(1, $counts); + $this->assertContains(2, $counts); + $this->assertContains(3, $counts); + $this->assertNotContains(4, $counts); + $this->assertNotContains(5, $counts); + } + + /** + * @test + */ + public function random_set_number_must_be_positive(): void + { + $this->expectException(\InvalidArgumentException::class); + + CategoryPoolStory::getRandomSet('pool-name', 0); + } + + /** + * @test + */ + public function random_range_min_must_be_zero_or_greater(): void + { + $this->expectException(\InvalidArgumentException::class); + + CategoryPoolStory::getRandomRange('pool-name', -1, 25); + } + + /** + * @test + */ + public function random_range_min_must_be_less_than_max(): void + { + $this->expectException(\InvalidArgumentException::class); + + CategoryPoolStory::getRandomRange('pool-name', 50, 25); + } + + /** + * @test + */ + public function random_range_more_than_available(): void + { + $this->expectException(\RuntimeException::class); + + CategoryPoolStory::getRandomRange('pool-name', 0, 100); } } From 0edbea8415dfd51e9c0d616c03a587e3a303e28b Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Mon, 7 Mar 2022 15:12:31 -0500 Subject: [PATCH 09/10] [minor] remove Symfony 5.3 from test matrix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 648ee7d79..c85110071 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: php: [7.4, 8.0, 8.1] - symfony: [4.4.*, 5.3.*, 5.4.*] + symfony: [4.4.*, 5.4.*] deps: [highest] include: - php: 7.2 From c1317159d4489626dc8fc00bdb77932ac1d38bd8 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Mon, 7 Mar 2022 15:30:02 -0500 Subject: [PATCH 10/10] [bug] fix global state with symfony/framework-bundle >= 5.4.6/6.0.6 Ref: https://github.com/symfony/symfony/pull/45595 --- src/Test/DatabaseResetter.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Test/DatabaseResetter.php b/src/Test/DatabaseResetter.php index df6ecc788..259950178 100644 --- a/src/Test/DatabaseResetter.php +++ b/src/Test/DatabaseResetter.php @@ -5,7 +5,6 @@ use DAMA\DoctrineTestBundle\Doctrine\DBAL\StaticDriver; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\HttpKernel\KernelInterface; -use Zenstruck\Foundry\Factory; /** * @internal @@ -42,7 +41,7 @@ public static function resetDatabase(KernelInterface $kernel): void $databaseResetter->resetDatabase(); - self::bootFoundry($kernel); + self::flushGlobalState($kernel); self::$hasBeenReset = true; } @@ -57,7 +56,7 @@ public static function resetSchema(KernelInterface $kernel): void return; } - self::bootFoundry($kernel); + self::flushGlobalState($kernel); } /** @retrun array */ @@ -77,11 +76,9 @@ private static function schemaResetters(KernelInterface $kernel): array return $databaseResetters; } - private static function bootFoundry(KernelInterface $kernel): void + private static function flushGlobalState(KernelInterface $kernel): void { - if (!Factory::isBooted()) { - TestState::bootFromContainer($kernel->getContainer()); - } + TestState::bootFromContainer($kernel->getContainer()); TestState::flushGlobalState(); }