mirror of
https://github.com/shlinkio/shlink.git
synced 2025-01-13 09:31:56 -06:00
Created ImportedLinksProcessorTest
This commit is contained in:
parent
fdcf88de67
commit
03a9697298
@ -36,6 +36,7 @@ return [
|
||||
Domain\DomainService::class => ConfigAbstractFactory::class,
|
||||
|
||||
Util\UrlValidator::class => ConfigAbstractFactory::class,
|
||||
Util\DoctrineBatchHelper::class => ConfigAbstractFactory::class,
|
||||
|
||||
Action\RedirectAction::class => ConfigAbstractFactory::class,
|
||||
Action\PixelAction::class => ConfigAbstractFactory::class,
|
||||
@ -87,6 +88,7 @@ return [
|
||||
Domain\DomainService::class => ['em'],
|
||||
|
||||
Util\UrlValidator::class => ['httpClient', Options\UrlShortenerOptions::class],
|
||||
Util\DoctrineBatchHelper::class => ['em'],
|
||||
|
||||
Action\RedirectAction::class => [
|
||||
Service\ShortUrl\ShortUrlResolver::class,
|
||||
@ -115,6 +117,7 @@ return [
|
||||
'em',
|
||||
Resolver\PersistenceDomainResolver::class,
|
||||
Service\ShortUrl\ShortCodeHelper::class,
|
||||
Util\DoctrineBatchHelper::class,
|
||||
],
|
||||
],
|
||||
|
||||
|
@ -9,7 +9,7 @@ use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface;
|
||||
use Shlinkio\Shlink\Core\Util\DoctrineBatchIterator;
|
||||
use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface;
|
||||
use Shlinkio\Shlink\Core\Util\TagManagerTrait;
|
||||
use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface;
|
||||
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
|
||||
@ -24,15 +24,18 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface
|
||||
private EntityManagerInterface $em;
|
||||
private DomainResolverInterface $domainResolver;
|
||||
private ShortCodeHelperInterface $shortCodeHelper;
|
||||
private DoctrineBatchHelperInterface $batchHelper;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
DomainResolverInterface $domainResolver,
|
||||
ShortCodeHelperInterface $shortCodeHelper
|
||||
ShortCodeHelperInterface $shortCodeHelper,
|
||||
DoctrineBatchHelperInterface $batchHelper
|
||||
) {
|
||||
$this->em = $em;
|
||||
$this->domainResolver = $domainResolver;
|
||||
$this->shortCodeHelper = $shortCodeHelper;
|
||||
$this->batchHelper = $batchHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,7 +46,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface
|
||||
/** @var ShortUrlRepositoryInterface $shortUrlRepo */
|
||||
$shortUrlRepo = $this->em->getRepository(ShortUrl::class);
|
||||
$importShortCodes = $params['import_short_codes'];
|
||||
$iterable = new DoctrineBatchIterator($shlinkUrls, $this->em, 100);
|
||||
$iterable = $this->batchHelper->wrapIterable($shlinkUrls, 100);
|
||||
|
||||
/** @var ImportedShlinkUrl $url */
|
||||
foreach ($iterable as $url) {
|
||||
@ -90,6 +93,6 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->handleShortCodeUniqueness($url, $shortUrl, $io, false);
|
||||
return $this->shortCodeHelper->ensureShortCodeUniqueness($shortUrl, false);
|
||||
}
|
||||
}
|
||||
|
@ -5,32 +5,26 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core\Util;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use IteratorAggregate;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Inspired by ocramius/doctrine-batch-utils https://github.com/Ocramius/DoctrineBatchUtils
|
||||
*/
|
||||
class DoctrineBatchIterator implements IteratorAggregate
|
||||
class DoctrineBatchHelper implements DoctrineBatchHelperInterface
|
||||
{
|
||||
private iterable $resultSet;
|
||||
private EntityManagerInterface $em;
|
||||
private int $batchSize;
|
||||
|
||||
public function __construct(iterable $resultSet, EntityManagerInterface $em, int $batchSize)
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
$this->em = $em;
|
||||
$this->batchSize = $batchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function getIterator(): iterable
|
||||
public function wrapIterable(iterable $resultSet, int $batchSize): iterable
|
||||
{
|
||||
$iteration = 0;
|
||||
$resultSet = $this->resultSet;
|
||||
|
||||
$this->em->beginTransaction();
|
||||
|
||||
@ -38,7 +32,7 @@ class DoctrineBatchIterator implements IteratorAggregate
|
||||
foreach ($resultSet as $key => $value) {
|
||||
$iteration++;
|
||||
yield $key => $value;
|
||||
$this->flushAndClearBatch($iteration);
|
||||
$this->flushAndClearBatch($iteration, $batchSize);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->em->rollback();
|
||||
@ -50,9 +44,9 @@ class DoctrineBatchIterator implements IteratorAggregate
|
||||
$this->em->commit();
|
||||
}
|
||||
|
||||
private function flushAndClearBatch(int $iteration): void
|
||||
private function flushAndClearBatch(int $iteration, int $batchSize): void
|
||||
{
|
||||
if ($iteration % $this->batchSize) {
|
||||
if ($iteration % $batchSize) {
|
||||
return;
|
||||
}
|
||||
|
10
module/Core/src/Util/DoctrineBatchHelperInterface.php
Normal file
10
module/Core/src/Util/DoctrineBatchHelperInterface.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Util;
|
||||
|
||||
interface DoctrineBatchHelperInterface
|
||||
{
|
||||
public function wrapIterable(iterable $resultSet, int $batchSize): iterable;
|
||||
}
|
145
module/Core/test/Importer/ImportedLinksProcessorTest.php
Normal file
145
module/Core/test/Importer/ImportedLinksProcessorTest.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\Importer;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Importer\ImportedLinksProcessor;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface;
|
||||
use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface;
|
||||
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
|
||||
use Symfony\Component\Console\Style\StyleInterface;
|
||||
|
||||
use function count;
|
||||
use function Functional\contains;
|
||||
use function Functional\some;
|
||||
use function str_contains;
|
||||
|
||||
class ImportedLinksProcessorTest extends TestCase
|
||||
{
|
||||
private ImportedLinksProcessor $processor;
|
||||
private ObjectProphecy $em;
|
||||
private ObjectProphecy $shortCodeHelper;
|
||||
private ObjectProphecy $repo;
|
||||
private ObjectProphecy $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->shortCodeHelper = $this->prophesize(ShortCodeHelperInterface::class);
|
||||
$batchHelper = $this->prophesize(DoctrineBatchHelperInterface::class);
|
||||
$batchHelper->wrapIterable(Argument::cetera())->willReturnArgument(0);
|
||||
|
||||
$this->processor = new ImportedLinksProcessor(
|
||||
$this->em->reveal(),
|
||||
new SimpleDomainResolver(),
|
||||
$this->shortCodeHelper->reveal(),
|
||||
$batchHelper->reveal(),
|
||||
);
|
||||
|
||||
$this->io = $this->prophesize(StyleInterface::class);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function newUrlsWithNoErrorsAreAllPersisted(): void
|
||||
{
|
||||
$urls = [
|
||||
new ImportedShlinkUrl('', 'foo', [], Chronos::now(), null, 'foo'),
|
||||
new ImportedShlinkUrl('', 'bar', [], Chronos::now(), null, 'bar'),
|
||||
new ImportedShlinkUrl('', 'baz', [], Chronos::now(), null, 'baz'),
|
||||
];
|
||||
$expectedCalls = count($urls);
|
||||
|
||||
$importedUrlExists = $this->repo->importedUrlExists(Argument::cetera())->willReturn(false);
|
||||
$ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true);
|
||||
$persist = $this->em->persist(Argument::type(ShortUrl::class));
|
||||
|
||||
$this->processor->process($this->io->reveal(), $urls, ['import_short_codes' => true]);
|
||||
|
||||
$importedUrlExists->shouldHaveBeenCalledTimes($expectedCalls);
|
||||
$ensureUniqueness->shouldHaveBeenCalledTimes($expectedCalls);
|
||||
$persist->shouldHaveBeenCalledTimes($expectedCalls);
|
||||
$this->io->text(Argument::type('string'))->shouldHaveBeenCalledTimes($expectedCalls);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function alreadyImportedUrlsAreSkipped(): void
|
||||
{
|
||||
$urls = [
|
||||
new ImportedShlinkUrl('', 'foo', [], Chronos::now(), null, 'foo'),
|
||||
new ImportedShlinkUrl('', 'bar', [], Chronos::now(), null, 'bar'),
|
||||
new ImportedShlinkUrl('', 'baz', [], Chronos::now(), null, 'baz'),
|
||||
new ImportedShlinkUrl('', 'baz2', [], Chronos::now(), null, 'baz2'),
|
||||
new ImportedShlinkUrl('', 'baz3', [], Chronos::now(), null, 'baz3'),
|
||||
];
|
||||
$contains = fn (string $needle) => fn (string $text) => str_contains($text, $needle);
|
||||
|
||||
$importedUrlExists = $this->repo->importedUrlExists(Argument::cetera())->will(function (array $args): bool {
|
||||
/** @var ImportedShlinkUrl $url */
|
||||
[$url] = $args;
|
||||
|
||||
return contains(['foo', 'baz2', 'baz3'], $url->longUrl());
|
||||
});
|
||||
$ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true);
|
||||
$persist = $this->em->persist(Argument::type(ShortUrl::class));
|
||||
|
||||
$this->processor->process($this->io->reveal(), $urls, ['import_short_codes' => true]);
|
||||
|
||||
$importedUrlExists->shouldHaveBeenCalledTimes(count($urls));
|
||||
$ensureUniqueness->shouldHaveBeenCalledTimes(2);
|
||||
$persist->shouldHaveBeenCalledTimes(2);
|
||||
$this->io->text(Argument::that($contains('Skipped')))->shouldHaveBeenCalledTimes(3);
|
||||
$this->io->text(Argument::that($contains('Imported')))->shouldHaveBeenCalledTimes(2);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function nonUniqueShortCodesAreAskedToUser(): void
|
||||
{
|
||||
$urls = [
|
||||
new ImportedShlinkUrl('', 'foo', [], Chronos::now(), null, 'foo'),
|
||||
new ImportedShlinkUrl('', 'bar', [], Chronos::now(), null, 'bar'),
|
||||
new ImportedShlinkUrl('', 'baz', [], Chronos::now(), null, 'baz'),
|
||||
new ImportedShlinkUrl('', 'baz2', [], Chronos::now(), null, 'baz2'),
|
||||
new ImportedShlinkUrl('', 'baz3', [], Chronos::now(), null, 'baz3'),
|
||||
];
|
||||
$contains = fn (string $needle) => fn (string $text) => str_contains($text, $needle);
|
||||
|
||||
$importedUrlExists = $this->repo->importedUrlExists(Argument::cetera())->willReturn(false);
|
||||
$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;
|
||||
|
||||
return some(['foo', 'baz2', 'baz3'], fn (string $item) => str_contains($question, $item)) ? 'Skip' : '';
|
||||
});
|
||||
$persist = $this->em->persist(Argument::type(ShortUrl::class));
|
||||
|
||||
$this->processor->process($this->io->reveal(), $urls, ['import_short_codes' => true]);
|
||||
|
||||
$importedUrlExists->shouldHaveBeenCalledTimes(count($urls));
|
||||
$failingEnsureUniqueness->shouldHaveBeenCalledTimes(5);
|
||||
$successEnsureUniqueness->shouldHaveBeenCalledTimes(2);
|
||||
$choice->shouldHaveBeenCalledTimes(5);
|
||||
$persist->shouldHaveBeenCalledTimes(2);
|
||||
$this->io->text(Argument::that($contains('Skipped')))->shouldHaveBeenCalledTimes(3);
|
||||
$this->io->text(Argument::that($contains('Imported')))->shouldHaveBeenCalledTimes(2);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user