diff --git a/module/Core/test/Importer/ImportedLinksProcessorTest.php b/module/Core/test/Importer/ImportedLinksProcessorTest.php index 2ce93647..926901a9 100644 --- a/module/Core/test/Importer/ImportedLinksProcessorTest.php +++ b/module/Core/test/Importer/ImportedLinksProcessorTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Core\Importer; use Cake\Chronos\Chronos; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use RuntimeException; use Shlinkio\Shlink\Core\Importer\ImportedLinksProcessor; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -23,6 +21,7 @@ use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit; use Shlinkio\Shlink\Importer\Params\ImportParams; use Shlinkio\Shlink\Importer\Sources\ImportSource; +use stdClass; use Symfony\Component\Console\Style\StyleInterface; use function count; @@ -32,32 +31,30 @@ use function str_contains; class ImportedLinksProcessorTest extends TestCase { - use ProphecyTrait; - private ImportedLinksProcessor $processor; - private ObjectProphecy $em; - private ObjectProphecy $shortCodeHelper; - private ObjectProphecy $repo; - private ObjectProphecy $io; + private MockObject $em; + private MockObject $shortCodeHelper; + private MockObject $repo; + private MockObject $io; protected function setUp(): void { - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->repo = $this->prophesize(ShortUrlRepositoryInterface::class); - $this->em->getRepository(ShortUrl::class)->willReturn($this->repo->reveal()); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->repo = $this->createMock(ShortUrlRepositoryInterface::class); + $this->em->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn($this->repo); - $this->shortCodeHelper = $this->prophesize(ShortCodeUniquenessHelperInterface::class); - $batchHelper = $this->prophesize(DoctrineBatchHelperInterface::class); - $batchHelper->wrapIterable(Argument::cetera())->willReturnArgument(0); + $this->shortCodeHelper = $this->createMock(ShortCodeUniquenessHelperInterface::class); + $batchHelper = $this->createMock(DoctrineBatchHelperInterface::class); + $batchHelper->method('wrapIterable')->willReturnArgument(0); $this->processor = new ImportedLinksProcessor( - $this->em->reveal(), + $this->em, new SimpleShortUrlRelationResolver(), - $this->shortCodeHelper->reveal(), - $batchHelper->reveal(), + $this->shortCodeHelper, + $batchHelper, ); - $this->io = $this->prophesize(StyleInterface::class); + $this->io = $this->createMock(StyleInterface::class); } /** @test */ @@ -70,16 +67,16 @@ class ImportedLinksProcessorTest extends TestCase ]; $expectedCalls = count($urls); - $importedUrlExists = $this->repo->findOneByImportedUrl(Argument::cetera())->willReturn(null); - $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); - $persist = $this->em->persist(Argument::type(ShortUrl::class)); + $this->repo->expects($this->exactly($expectedCalls))->method('findOneByImportedUrl')->willReturn(null); + $this->shortCodeHelper->expects($this->exactly($expectedCalls)) + ->method('ensureShortCodeUniqueness') + ->willReturn(true); + $this->em->expects($this->exactly($expectedCalls))->method('persist')->with( + $this->isInstanceOf(ShortUrl::class), + ); + $this->io->expects($this->exactly($expectedCalls))->method('text')->with($this->isType('string')); - $this->processor->process($this->io->reveal(), $urls, $this->buildParams()); - - $importedUrlExists->shouldHaveBeenCalledTimes($expectedCalls); - $ensureUniqueness->shouldHaveBeenCalledTimes($expectedCalls); - $persist->shouldHaveBeenCalledTimes($expectedCalls); - $this->io->text(Argument::type('string'))->shouldHaveBeenCalledTimes($expectedCalls); + $this->processor->process($this->io, $urls, $this->buildParams()); } /** @test */ @@ -91,26 +88,21 @@ class ImportedLinksProcessorTest extends TestCase new ImportedShlinkUrl(ImportSource::BITLY, 'baz', [], Chronos::now(), null, 'baz', null), ]; - $importedUrlExists = $this->repo->findOneByImportedUrl(Argument::cetera())->willReturn(null); - $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); - $persist = $this->em->persist(Argument::type(ShortUrl::class))->will(function (array $args): void { - /** @var ShortUrl $shortUrl */ - [$shortUrl] = $args; - + $this->repo->expects($this->exactly(3))->method('findOneByImportedUrl')->willReturn(null); + $this->shortCodeHelper->expects($this->exactly(3))->method('ensureShortCodeUniqueness')->willReturn(true); + $this->em->expects($this->exactly(3))->method('persist')->with( + $this->isInstanceOf(ShortUrl::class), + )->willReturnCallback(function (ShortUrl $shortUrl): void { if ($shortUrl->getShortCode() === 'baz') { throw new RuntimeException('Whatever error'); } }); + $textCalls = $this->setUpIoText('Skipped. Reason: Whatever error', 'Imported'); - $this->processor->process($this->io->reveal(), $urls, $this->buildParams()); + $this->processor->process($this->io, $urls, $this->buildParams()); - $importedUrlExists->shouldHaveBeenCalledTimes(3); - $ensureUniqueness->shouldHaveBeenCalledTimes(3); - $persist->shouldHaveBeenCalledTimes(3); - $this->io->text(Argument::containingString('Imported'))->shouldHaveBeenCalledTimes(2); - $this->io->text( - Argument::containingString('Skipped. Reason: Whatever error'), - )->shouldHaveBeenCalledOnce(); + self::assertEquals(2, $textCalls->importedCount); + self::assertEquals(1, $textCalls->skippedCount); } /** @test */ @@ -124,24 +116,18 @@ class ImportedLinksProcessorTest extends TestCase new ImportedShlinkUrl(ImportSource::BITLY, 'baz3', [], Chronos::now(), null, 'baz3', null), ]; - $importedUrlExists = $this->repo->findOneByImportedUrl(Argument::cetera())->will( - function (array $args): ?ShortUrl { - /** @var ImportedShlinkUrl $url */ - [$url] = $args; - - return contains(['foo', 'baz2', 'baz3'], $url->longUrl) ? ShortUrl::fromImport($url, true) : null; - }, + $this->repo->expects($this->exactly(count($urls)))->method('findOneByImportedUrl')->willReturnCallback( + fn (ImportedShlinkUrl $url): ?ShortUrl + => contains(['foo', 'baz2', 'baz3'], $url->longUrl) ? ShortUrl::fromImport($url, true) : null, ); - $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); - $persist = $this->em->persist(Argument::type(ShortUrl::class)); + $this->shortCodeHelper->expects($this->exactly(2))->method('ensureShortCodeUniqueness')->willReturn(true); + $this->em->expects($this->exactly(2))->method('persist')->with($this->isInstanceOf(ShortUrl::class)); + $textCalls = $this->setUpIoText(); - $this->processor->process($this->io->reveal(), $urls, $this->buildParams()); + $this->processor->process($this->io, $urls, $this->buildParams()); - $importedUrlExists->shouldHaveBeenCalledTimes(count($urls)); - $ensureUniqueness->shouldHaveBeenCalledTimes(2); - $persist->shouldHaveBeenCalledTimes(2); - $this->io->text(Argument::containingString('Skipped'))->shouldHaveBeenCalledTimes(3); - $this->io->text(Argument::containingString('Imported'))->shouldHaveBeenCalledTimes(2); + self::assertEquals(2, $textCalls->importedCount); + self::assertEquals(3, $textCalls->skippedCount); } /** @test */ @@ -155,32 +141,20 @@ class ImportedLinksProcessorTest extends TestCase new ImportedShlinkUrl(ImportSource::BITLY, 'baz3', [], Chronos::now(), null, 'baz3', 'bar'), ]; - $importedUrlExists = $this->repo->findOneByImportedUrl(Argument::cetera())->willReturn(null); - $failingEnsureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness( - Argument::any(), - true, - )->willReturn(false); - $successEnsureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness( - Argument::any(), - false, - )->willReturn(true); - $choice = $this->io->choice(Argument::cetera())->will(function (array $args) { - /** @var ImportedShlinkUrl $url */ - [$question] = $args; - + $this->repo->expects($this->exactly(count($urls)))->method('findOneByImportedUrl')->willReturn(null); + $this->shortCodeHelper->expects($this->exactly(7))->method('ensureShortCodeUniqueness')->willReturnCallback( + fn ($_, bool $hasCustomSlug) => ! $hasCustomSlug, + ); + $this->em->expects($this->exactly(2))->method('persist')->with($this->isInstanceOf(ShortUrl::class)); + $this->io->expects($this->exactly(5))->method('choice')->willReturnCallback(function (string $question) { return some(['foo', 'baz2', 'baz3'], fn (string $item) => str_contains($question, $item)) ? 'Skip' : ''; }); - $persist = $this->em->persist(Argument::type(ShortUrl::class)); + $textCalls = $this->setUpIoText('Error'); - $this->processor->process($this->io->reveal(), $urls, $this->buildParams()); + $this->processor->process($this->io, $urls, $this->buildParams()); - $importedUrlExists->shouldHaveBeenCalledTimes(count($urls)); - $failingEnsureUniqueness->shouldHaveBeenCalledTimes(5); - $successEnsureUniqueness->shouldHaveBeenCalledTimes(2); - $choice->shouldHaveBeenCalledTimes(5); - $persist->shouldHaveBeenCalledTimes(2); - $this->io->text(Argument::containingString('Error'))->shouldHaveBeenCalledTimes(3); - $this->io->text(Argument::containingString('Imported'))->shouldHaveBeenCalledTimes(2); + self::assertEquals(2, $textCalls->importedCount); + self::assertEquals(3, $textCalls->skippedCount); } /** @@ -193,18 +167,16 @@ class ImportedLinksProcessorTest extends TestCase int $amountOfPersistedVisits, ?ShortUrl $foundShortUrl, ): void { - $findExisting = $this->repo->findOneByImportedUrl(Argument::cetera())->willReturn($foundShortUrl); - $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); - $persistUrl = $this->em->persist(Argument::type(ShortUrl::class)); - $persistVisits = $this->em->persist(Argument::type(Visit::class)); + $this->repo->expects($this->once())->method('findOneByImportedUrl')->willReturn($foundShortUrl); + $this->shortCodeHelper->expects($this->exactly($foundShortUrl === null ? 1 : 0)) + ->method('ensureShortCodeUniqueness') + ->willReturn(true); + $this->em->expects($this->exactly($amountOfPersistedVisits + ($foundShortUrl === null ? 1 : 0)))->method( + 'persist', + )->with($this->callback(fn (object $arg) => $arg instanceof ShortUrl || $arg instanceof Visit)); + $this->io->expects($this->once())->method('text')->with($this->stringContains($expectedOutput)); - $this->processor->process($this->io->reveal(), [$importedUrl], $this->buildParams()); - - $findExisting->shouldHaveBeenCalledOnce(); - $ensureUniqueness->shouldHaveBeenCalledTimes($foundShortUrl === null ? 1 : 0); - $persistUrl->shouldHaveBeenCalledTimes($foundShortUrl === null ? 1 : 0); - $persistVisits->shouldHaveBeenCalledTimes($amountOfPersistedVisits); - $this->io->text(Argument::containingString($expectedOutput))->shouldHaveBeenCalledOnce(); + $this->processor->process($this->io, [$importedUrl], $this->buildParams()); } public function provideUrlsWithVisits(): iterable @@ -251,4 +223,23 @@ class ImportedLinksProcessorTest extends TestCase { return ImportSource::BITLY->toParamsWithCallableMap(['import_short_codes' => static fn () => true]); } + + public function setUpIoText(string $skippedText = 'Skipped', string $importedText = 'Imported'): stdClass + { + $counts = new stdClass(); + $counts->importedCount = 0; + $counts->skippedCount = 0; + + $this->io->method('text')->willReturnCallback( + function (string $output) use ($counts, $skippedText, $importedText) { + if (str_contains($output, $skippedText)) { + $counts->skippedCount++; + } elseif (str_contains($output, $importedText)) { + $counts->importedCount++; + } + }, + ); + + return $counts; + } }