Fix error when importing short URLs while using Postgres

This commit is contained in:
Alejandro Celaya 2023-12-16 20:21:59 +01:00
parent 3a43aa4d41
commit 1b14bb07b1
3 changed files with 27 additions and 11 deletions

View file

@ -18,7 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
* *Nothing* * *Nothing*
### Fixed ### Fixed
* *Nothing* * [#1947](https://github.com/shlinkio/shlink/issues/1947) Fix error when importing short URLs while using Postgres.
## [3.7.0] - 2023-11-25 ## [3.7.0] - 2023-11-25

View file

@ -12,9 +12,9 @@ use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit;
use function Shlinkio\Shlink\Core\normalizeDate; use function Shlinkio\Shlink\Core\normalizeDate;
use function sprintf; use function sprintf;
final class ShortUrlImporting final readonly class ShortUrlImporting
{ {
private function __construct(private readonly ShortUrl $shortUrl, private readonly bool $isNew) private function __construct(private ShortUrl $shortUrl, private bool $isNew)
{ {
} }
@ -57,11 +57,18 @@ final class ShortUrlImporting
private function resolveShortUrl(EntityManagerInterface $em): ShortUrl private function resolveShortUrl(EntityManagerInterface $em): ShortUrl
{ {
// If wrapped ShortUrl has no ID, avoid trying to query the EM, as it would fail in Postgres.
// See https://github.com/shlinkio/shlink/issues/1947
$id = $this->shortUrl->getId();
if (!$id) {
return $this->shortUrl;
}
// Instead of directly accessing wrapped ShortUrl entity, try to get it from the EM. // Instead of directly accessing wrapped ShortUrl entity, try to get it from the EM.
// With this, we will get the same entity from memory if it is known by the EM, but if it was cleared, the EM // With this, we will get the same entity from memory if it is known by the EM, but if it was cleared, the EM
// will fetch it again from the database, preventing errors at runtime. // will fetch it again from the database, preventing errors at runtime.
// However, if the EM was not flushed yet, the entity will not be found by ID, but it is known by the EM. // However, if the EM was not flushed yet, the entity will not be found by ID, but it is known by the EM.
// In that case, we fall back to wrapped ShortUrl entity directly. // In that case, we fall back to wrapped ShortUrl entity directly.
return $em->find(ShortUrl::class, $this->shortUrl->getId()) ?? $this->shortUrl; return $em->find(ShortUrl::class, $id) ?? $this->shortUrl;
} }
} }

View file

@ -232,15 +232,20 @@ class ImportedLinksProcessorTest extends TestCase
} }
#[Test, DataProvider('provideFoundShortUrls')] #[Test, DataProvider('provideFoundShortUrls')]
public function visitsArePersistedWithProperShortUrl(?ShortUrl $foundShortUrl): void public function visitsArePersistedWithProperShortUrl(ShortUrl $originalShortUrl, ?ShortUrl $foundShortUrl): void
{ {
$originalShortUrl = ShortUrl::withLongUrl('https://foo');
$this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo);
$this->repo->expects($this->once())->method('findOneByImportedUrl')->willReturn($originalShortUrl); $this->repo->expects($this->once())->method('findOneByImportedUrl')->willReturn($originalShortUrl);
$this->em->expects($this->exactly(2))->method('find')->willReturn($foundShortUrl); if (!$originalShortUrl->getId()) {
$this->em->expects($this->never())->method('find');
} else {
$this->em->expects($this->exactly(2))->method('find')->willReturn($foundShortUrl);
}
$this->em->expects($this->once())->method('persist')->willReturnCallback( $this->em->expects($this->once())->method('persist')->willReturnCallback(
static fn (Visit $visit) => Assert::assertSame($foundShortUrl ?? $originalShortUrl, $visit->getShortUrl()), static fn (Visit $visit) => Assert::assertSame(
$foundShortUrl ?? $originalShortUrl,
$visit->getShortUrl(),
),
); );
$now = Chronos::now(); $now = Chronos::now();
@ -253,8 +258,12 @@ class ImportedLinksProcessorTest extends TestCase
public static function provideFoundShortUrls(): iterable public static function provideFoundShortUrls(): iterable
{ {
yield [null]; yield 'not found new URL' => [ShortUrl::withLongUrl('https://foo')->setId('123'), null];
yield [ShortUrl::withLongUrl('https://bar')]; yield 'found new URL' => [
ShortUrl::withLongUrl('https://foo')->setId('123'),
ShortUrl::withLongUrl('https://bar'),
];
yield 'old URL without ID' => [$originalShortUrl = ShortUrl::withLongUrl('https://foo'), $originalShortUrl];
} }
/** /**