mirror of
https://github.com/shlinkio/shlink.git
synced 2024-12-24 08:00:13 -06:00
Merge pull request #2255 from acelaya-forks/feature/expose-tracked-visits
Return `Visit` object created when tracking a visit successfully
This commit is contained in:
commit
59a4704658
@ -107,7 +107,7 @@ class LocateVisitsCommandTest extends TestCase
|
||||
#[Test, DataProvider('provideIgnoredAddresses')]
|
||||
public function localhostAndEmptyAddressesAreIgnored(IpCannotBeLocatedException $e, string $message): void
|
||||
{
|
||||
$visit = Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance());
|
||||
$visit = Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::empty());
|
||||
$location = VisitLocation::fromGeolocation(Location::emptyInstance());
|
||||
|
||||
$this->lock->method('acquire')->with($this->isFalse())->willReturn(true);
|
||||
|
@ -51,7 +51,7 @@ final class Visitor
|
||||
);
|
||||
}
|
||||
|
||||
public static function emptyInstance(): self
|
||||
public static function empty(): self
|
||||
{
|
||||
return new self('', '', null, '');
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidIpFormatException;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Util\IpAddressUtils;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||
|
||||
use function Shlinkio\Shlink\Core\ipAddressFromRequest;
|
||||
@ -22,24 +23,26 @@ readonly class RequestTracker implements RequestTrackerInterface, RequestMethodI
|
||||
{
|
||||
}
|
||||
|
||||
public function trackIfApplicable(ShortUrl $shortUrl, ServerRequestInterface $request): void
|
||||
{
|
||||
if ($this->shouldTrackRequest($request)) {
|
||||
$this->visitsTracker->track($shortUrl, Visitor::fromRequest($request));
|
||||
}
|
||||
}
|
||||
|
||||
public function trackNotFoundIfApplicable(ServerRequestInterface $request): void
|
||||
public function trackIfApplicable(ShortUrl $shortUrl, ServerRequestInterface $request): Visit|null
|
||||
{
|
||||
if (! $this->shouldTrackRequest($request)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->visitsTracker->track($shortUrl, Visitor::fromRequest($request));
|
||||
}
|
||||
|
||||
public function trackNotFoundIfApplicable(ServerRequestInterface $request): Visit|null
|
||||
{
|
||||
if (! $this->shouldTrackRequest($request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var NotFoundType|null $notFoundType */
|
||||
$notFoundType = $request->getAttribute(NotFoundType::class);
|
||||
$visitor = Visitor::fromRequest($request);
|
||||
|
||||
match (true) {
|
||||
return match (true) {
|
||||
$notFoundType?->isBaseUrl() => $this->visitsTracker->trackBaseUrlVisit($visitor),
|
||||
$notFoundType?->isRegularNotFound() => $this->visitsTracker->trackRegularNotFoundVisit($visitor),
|
||||
$notFoundType?->isInvalidShortUrl() => $this->visitsTracker->trackInvalidShortUrlVisit($visitor),
|
||||
|
@ -6,10 +6,11 @@ namespace Shlinkio\Shlink\Core\Visit;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
|
||||
interface RequestTrackerInterface
|
||||
{
|
||||
public function trackIfApplicable(ShortUrl $shortUrl, ServerRequestInterface $request): void;
|
||||
public function trackIfApplicable(ShortUrl $shortUrl, ServerRequestInterface $request): Visit|null;
|
||||
|
||||
public function trackNotFoundIfApplicable(ServerRequestInterface $request): void;
|
||||
public function trackNotFoundIfApplicable(ServerRequestInterface $request): Visit|null;
|
||||
}
|
||||
|
@ -21,65 +21,63 @@ readonly class VisitsTracker implements VisitsTrackerInterface
|
||||
) {
|
||||
}
|
||||
|
||||
public function track(ShortUrl $shortUrl, Visitor $visitor): void
|
||||
public function track(ShortUrl $shortUrl, Visitor $visitor): Visit|null
|
||||
{
|
||||
$this->trackVisit(
|
||||
return $this->trackVisit(
|
||||
fn (Visitor $v) => Visit::forValidShortUrl($shortUrl, $v, $this->options->anonymizeRemoteAddr),
|
||||
$visitor,
|
||||
);
|
||||
}
|
||||
|
||||
public function trackInvalidShortUrlVisit(Visitor $visitor): void
|
||||
public function trackInvalidShortUrlVisit(Visitor $visitor): Visit|null
|
||||
{
|
||||
$this->trackOrphanVisit(
|
||||
return $this->trackOrphanVisit(
|
||||
fn (Visitor $v) => Visit::forInvalidShortUrl($v, $this->options->anonymizeRemoteAddr),
|
||||
$visitor,
|
||||
);
|
||||
}
|
||||
|
||||
public function trackBaseUrlVisit(Visitor $visitor): void
|
||||
public function trackBaseUrlVisit(Visitor $visitor): Visit|null
|
||||
{
|
||||
$this->trackOrphanVisit(
|
||||
return $this->trackOrphanVisit(
|
||||
fn (Visitor $v) => Visit::forBasePath($v, $this->options->anonymizeRemoteAddr),
|
||||
$visitor,
|
||||
);
|
||||
}
|
||||
|
||||
public function trackRegularNotFoundVisit(Visitor $visitor): void
|
||||
public function trackRegularNotFoundVisit(Visitor $visitor): Visit|null
|
||||
{
|
||||
$this->trackOrphanVisit(
|
||||
return $this->trackOrphanVisit(
|
||||
fn (Visitor $v) => Visit::forRegularNotFound($v, $this->options->anonymizeRemoteAddr),
|
||||
$visitor,
|
||||
);
|
||||
}
|
||||
|
||||
private function trackOrphanVisit(callable $createVisit, Visitor $visitor): void
|
||||
private function trackOrphanVisit(callable $createVisit, Visitor $visitor): Visit|null
|
||||
{
|
||||
if (! $this->options->trackOrphanVisits) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->trackVisit($createVisit, $visitor);
|
||||
return $this->trackVisit($createVisit, $visitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(Visitor $visitor): Visit $createVisit
|
||||
*/
|
||||
private function trackVisit(callable $createVisit, Visitor $visitor): void
|
||||
private function trackVisit(callable $createVisit, Visitor $visitor): Visit|null
|
||||
{
|
||||
if ($this->options->disableTracking) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
$visit = $createVisit($visitor->normalizeForTrackingOptions($this->options));
|
||||
|
||||
// Wrap persisting and flushing the visit in a transaction, so that the ShortUrlVisitsCountTracker performs
|
||||
// changes inside that very same transaction atomically
|
||||
$this->em->wrapInTransaction(function () use ($visit): void {
|
||||
$this->em->persist($visit);
|
||||
$this->em->flush();
|
||||
});
|
||||
|
||||
// Wrap persisting the visit in a transaction, so that the ShortUrlVisitsCountTracker performs changes inside
|
||||
// that very same transaction atomically
|
||||
$this->em->wrapInTransaction(fn () => $this->em->persist($visit));
|
||||
$this->eventDispatcher->dispatch(new UrlVisited($visit->getId(), $visitor->remoteAddress));
|
||||
|
||||
return $visit;
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,16 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core\Visit;
|
||||
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||
|
||||
interface VisitsTrackerInterface
|
||||
{
|
||||
public function track(ShortUrl $shortUrl, Visitor $visitor): void;
|
||||
public function track(ShortUrl $shortUrl, Visitor $visitor): Visit|null;
|
||||
|
||||
public function trackInvalidShortUrlVisit(Visitor $visitor): void;
|
||||
public function trackInvalidShortUrlVisit(Visitor $visitor): Visit|null;
|
||||
|
||||
public function trackBaseUrlVisit(Visitor $visitor): void;
|
||||
public function trackBaseUrlVisit(Visitor $visitor): Visit|null;
|
||||
|
||||
public function trackRegularNotFoundVisit(Visitor $visitor): void;
|
||||
public function trackRegularNotFoundVisit(Visitor $visitor): Visit|null;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ class DeleteExpiredShortUrlsRepositoryTest extends DatabaseTestCase
|
||||
$this->getEntityManager()->persist($shortUrl);
|
||||
|
||||
for ($j = 0; $j < $visitsPerShortUrl; $j++) {
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl, Visitor::empty()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase
|
||||
|
||||
$foo2 = ShortUrl::withLongUrl('https://foo_2');
|
||||
$visits2 = array_map(function () use ($foo2) {
|
||||
$visit = Visit::forValidShortUrl($foo2, Visitor::emptyInstance());
|
||||
$visit = Visit::forValidShortUrl($foo2, Visitor::empty());
|
||||
$this->getEntityManager()->persist($visit);
|
||||
|
||||
return $visit;
|
||||
@ -304,9 +304,9 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase
|
||||
'maxVisits' => 3,
|
||||
]), $this->relationResolver);
|
||||
$this->getEntityManager()->persist($shortUrl4);
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::empty()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::empty()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::empty()));
|
||||
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
|
@ -79,13 +79,13 @@ class TagRepositoryTest extends DatabaseTestCase
|
||||
|
||||
$shortUrl = ShortUrl::create($metaWithTags($firstUrlTags, $apiKey), $this->relationResolver);
|
||||
$this->getEntityManager()->persist($shortUrl);
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl, Visitor::empty()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl, Visitor::empty()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl, Visitor::botInstance()));
|
||||
|
||||
$shortUrl2 = ShortUrl::create($metaWithTags($secondUrlTags, null), $this->relationResolver);
|
||||
$this->getEntityManager()->persist($shortUrl2);
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl2, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl2, Visitor::empty()));
|
||||
|
||||
// One of the tags has two extra short URLs, but with no visits
|
||||
$this->getEntityManager()->persist(
|
||||
|
@ -26,7 +26,7 @@ class OrphanVisitsCountTrackerTest extends DatabaseTestCase
|
||||
#[Test]
|
||||
public function createsNewEntriesWhenNoneExist(): void
|
||||
{
|
||||
$visit = Visit::forBasePath(Visitor::emptyInstance());
|
||||
$visit = Visit::forBasePath(Visitor::empty());
|
||||
$this->getEntityManager()->persist($visit);
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
@ -47,7 +47,7 @@ class OrphanVisitsCountTrackerTest extends DatabaseTestCase
|
||||
}
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
$visit = Visit::forRegularNotFound(Visitor::emptyInstance());
|
||||
$visit = Visit::forRegularNotFound(Visitor::empty());
|
||||
$this->getEntityManager()->persist($visit);
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
|
@ -30,7 +30,7 @@ class ShortUrlVisitsCountTrackerTest extends DatabaseTestCase
|
||||
$shortUrl = ShortUrl::createFake();
|
||||
$this->getEntityManager()->persist($shortUrl);
|
||||
|
||||
$visit = Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance());
|
||||
$visit = Visit::forValidShortUrl($shortUrl, Visitor::empty());
|
||||
$this->getEntityManager()->persist($visit);
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
@ -54,7 +54,7 @@ class ShortUrlVisitsCountTrackerTest extends DatabaseTestCase
|
||||
}
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
$visit = Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance());
|
||||
$visit = Visit::forValidShortUrl($shortUrl, Visitor::empty());
|
||||
$this->getEntityManager()->persist($visit);
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
|
@ -28,8 +28,8 @@ class VisitDeleterRepositoryTest extends DatabaseTestCase
|
||||
{
|
||||
$shortUrl1 = ShortUrl::withLongUrl('https://foo.com');
|
||||
$this->getEntityManager()->persist($shortUrl1);
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl1, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl1, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl1, Visitor::empty()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl1, Visitor::empty()));
|
||||
|
||||
$shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([
|
||||
ShortUrlInputFilter::LONG_URL => 'https://foo.com',
|
||||
@ -37,17 +37,17 @@ class VisitDeleterRepositoryTest extends DatabaseTestCase
|
||||
ShortUrlInputFilter::CUSTOM_SLUG => 'foo',
|
||||
]), new PersistenceShortUrlRelationResolver($this->getEntityManager()));
|
||||
$this->getEntityManager()->persist($shortUrl2);
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl2, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl2, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl2, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl2, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl2, Visitor::empty()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl2, Visitor::empty()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl2, Visitor::empty()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl2, Visitor::empty()));
|
||||
|
||||
$shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([
|
||||
ShortUrlInputFilter::LONG_URL => 'https://foo.com',
|
||||
ShortUrlInputFilter::CUSTOM_SLUG => 'foo',
|
||||
]), new PersistenceShortUrlRelationResolver($this->getEntityManager()));
|
||||
$this->getEntityManager()->persist($shortUrl3);
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl3, Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl3, Visitor::empty()));
|
||||
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
@ -62,7 +62,7 @@ class VisitDeleterRepositoryTest extends DatabaseTestCase
|
||||
#[Test]
|
||||
public function deletesExpectedOrphanVisits(): void
|
||||
{
|
||||
$visitor = Visitor::emptyInstance();
|
||||
$visitor = Visitor::empty();
|
||||
$this->getEntityManager()->persist(Visit::forBasePath($visitor));
|
||||
$this->getEntityManager()->persist(Visit::forInvalidShortUrl($visitor));
|
||||
$this->getEntityManager()->persist(Visit::forRegularNotFound($visitor));
|
||||
|
@ -37,7 +37,7 @@ class VisitIterationRepositoryTest extends DatabaseTestCase
|
||||
$unmodifiedDate = Chronos::now();
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
Chronos::setTestNow($unmodifiedDate->subDays($i)); // Enforce a different day for every visit
|
||||
$visit = Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance());
|
||||
$visit = Visit::forValidShortUrl($shortUrl, Visitor::empty());
|
||||
|
||||
if ($i >= 2) {
|
||||
$location = VisitLocation::fromGeolocation(Location::emptyInstance());
|
||||
|
@ -311,9 +311,9 @@ class VisitRepositoryTest extends DatabaseTestCase
|
||||
$this->getEntityManager()->persist($domainApiKey);
|
||||
|
||||
// Visits not linked to any short URL
|
||||
$this->getEntityManager()->persist(Visit::forBasePath(Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forInvalidShortUrl(Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forRegularNotFound(Visitor::emptyInstance()));
|
||||
$this->getEntityManager()->persist(Visit::forBasePath(Visitor::empty()));
|
||||
$this->getEntityManager()->persist(Visit::forInvalidShortUrl(Visitor::empty()));
|
||||
$this->getEntityManager()->persist(Visit::forRegularNotFound(Visitor::empty()));
|
||||
$this->getEntityManager()->persist(Visit::forRegularNotFound(Visitor::botInstance()));
|
||||
|
||||
$this->getEntityManager()->flush();
|
||||
@ -370,15 +370,15 @@ class VisitRepositoryTest extends DatabaseTestCase
|
||||
$botsCount = 3;
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$this->getEntityManager()->persist($this->setDateOnVisit(
|
||||
fn () => Visit::forBasePath($botsCount < 1 ? Visitor::emptyInstance() : Visitor::botInstance()),
|
||||
fn () => Visit::forBasePath($botsCount < 1 ? Visitor::empty() : Visitor::botInstance()),
|
||||
Chronos::parse(sprintf('2020-01-0%s', $i + 1)),
|
||||
));
|
||||
$this->getEntityManager()->persist($this->setDateOnVisit(
|
||||
fn () => Visit::forInvalidShortUrl(Visitor::emptyInstance()),
|
||||
fn () => Visit::forInvalidShortUrl(Visitor::empty()),
|
||||
Chronos::parse(sprintf('2020-01-0%s', $i + 1)),
|
||||
));
|
||||
$this->getEntityManager()->persist($this->setDateOnVisit(
|
||||
fn () => Visit::forRegularNotFound(Visitor::emptyInstance()),
|
||||
fn () => Visit::forRegularNotFound(Visitor::empty()),
|
||||
Chronos::parse(sprintf('2020-01-0%s', $i + 1)),
|
||||
));
|
||||
|
||||
@ -428,15 +428,15 @@ class VisitRepositoryTest extends DatabaseTestCase
|
||||
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$this->getEntityManager()->persist($this->setDateOnVisit(
|
||||
fn () => Visit::forBasePath(Visitor::emptyInstance()),
|
||||
fn () => Visit::forBasePath(Visitor::empty()),
|
||||
Chronos::parse(sprintf('2020-01-0%s', $i + 1)),
|
||||
));
|
||||
$this->getEntityManager()->persist($this->setDateOnVisit(
|
||||
fn () => Visit::forInvalidShortUrl(Visitor::emptyInstance()),
|
||||
fn () => Visit::forInvalidShortUrl(Visitor::empty()),
|
||||
Chronos::parse(sprintf('2020-01-0%s', $i + 1)),
|
||||
));
|
||||
$this->getEntityManager()->persist($this->setDateOnVisit(
|
||||
fn () => Visit::forRegularNotFound(Visitor::emptyInstance()),
|
||||
fn () => Visit::forRegularNotFound(Visitor::empty()),
|
||||
Chronos::parse(sprintf('2020-01-0%s', $i + 1)),
|
||||
));
|
||||
}
|
||||
@ -515,7 +515,7 @@ class VisitRepositoryTest extends DatabaseTestCase
|
||||
{
|
||||
$this->assertNull($this->repo->findMostRecentOrphanVisit());
|
||||
|
||||
$lastVisit = Visit::forBasePath(Visitor::emptyInstance());
|
||||
$lastVisit = Visit::forBasePath(Visitor::empty());
|
||||
$this->getEntityManager()->persist($lastVisit);
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
@ -567,7 +567,7 @@ class VisitRepositoryTest extends DatabaseTestCase
|
||||
$visit = $this->setDateOnVisit(
|
||||
fn () => Visit::forValidShortUrl(
|
||||
$shortUrl,
|
||||
$botsAmount < 1 ? Visitor::emptyInstance() : Visitor::botInstance(),
|
||||
$botsAmount < 1 ? Visitor::empty() : Visitor::botInstance(),
|
||||
),
|
||||
Chronos::parse(sprintf('2016-01-%s', str_pad((string) ($i + 1), 2, '0', STR_PAD_LEFT)))->startOfDay(),
|
||||
);
|
||||
|
@ -39,7 +39,7 @@ class LocateUnlocatedVisitsTest extends TestCase
|
||||
#[Test]
|
||||
public function visitToLocationHelperIsCalledToGeolocateVisits(): void
|
||||
{
|
||||
$visit = Visit::forBasePath(Visitor::emptyInstance());
|
||||
$visit = Visit::forBasePath(Visitor::empty());
|
||||
$location = Location::emptyInstance();
|
||||
|
||||
$this->visitToLocation->expects($this->once())->method('resolveVisitLocation')->with($visit)->willReturn(
|
||||
|
@ -60,7 +60,7 @@ class SendVisitToMatomoTest extends TestCase
|
||||
public function visitIsSentWhenItExists(string|null $originalIpAddress): void
|
||||
{
|
||||
$visitId = '123';
|
||||
$visit = Visit::forBasePath(Visitor::emptyInstance());
|
||||
$visit = Visit::forBasePath(Visitor::empty());
|
||||
|
||||
$this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn($visit);
|
||||
$this->visitSender->expects($this->once())->method('sendVisit')->with($visit, $originalIpAddress);
|
||||
|
@ -61,7 +61,7 @@ class NotifyVisitToMercureTest extends TestCase
|
||||
public function notificationsAreSentWhenVisitIsFound(): void
|
||||
{
|
||||
$visitId = '123';
|
||||
$visit = Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance());
|
||||
$visit = Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::empty());
|
||||
$update = Update::forTopicAndPayload('', []);
|
||||
|
||||
$this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn($visit);
|
||||
@ -81,7 +81,7 @@ class NotifyVisitToMercureTest extends TestCase
|
||||
public function debugIsLoggedWhenExceptionIsThrown(): void
|
||||
{
|
||||
$visitId = '123';
|
||||
$visit = Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance());
|
||||
$visit = Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::empty());
|
||||
$update = Update::forTopicAndPayload('', []);
|
||||
$e = new RuntimeException('Error');
|
||||
|
||||
@ -122,7 +122,7 @@ class NotifyVisitToMercureTest extends TestCase
|
||||
|
||||
public static function provideOrphanVisits(): iterable
|
||||
{
|
||||
$visitor = Visitor::emptyInstance();
|
||||
$visitor = Visitor::empty();
|
||||
|
||||
yield VisitType::REGULAR_404->value => [Visit::forRegularNotFound($visitor)];
|
||||
yield VisitType::INVALID_SHORT_URL->value => [Visit::forInvalidShortUrl($visitor)];
|
||||
|
@ -48,7 +48,7 @@ class PublishingUpdatesGeneratorTest extends TestCase
|
||||
'longUrl' => 'https://longUrl',
|
||||
'title' => $title,
|
||||
]));
|
||||
$visit = Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance());
|
||||
$visit = Visit::forValidShortUrl($shortUrl, Visitor::empty());
|
||||
|
||||
/** @var Update $update */
|
||||
$update = $this->generator->{$method}($visit);
|
||||
@ -111,7 +111,7 @@ class PublishingUpdatesGeneratorTest extends TestCase
|
||||
|
||||
public static function provideOrphanVisits(): iterable
|
||||
{
|
||||
$visitor = Visitor::emptyInstance();
|
||||
$visitor = Visitor::empty();
|
||||
|
||||
yield VisitType::REGULAR_404->value => [Visit::forRegularNotFound($visitor)];
|
||||
yield VisitType::INVALID_SHORT_URL->value => [Visit::forInvalidShortUrl($visitor)];
|
||||
|
@ -90,7 +90,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
|
||||
|
||||
public static function provideVisits(): iterable
|
||||
{
|
||||
$visitor = Visitor::emptyInstance();
|
||||
$visitor = Visitor::empty();
|
||||
|
||||
yield 'orphan visit' => [Visit::forBasePath($visitor), ['newOrphanVisitUpdate']];
|
||||
yield 'non-orphan visit' => [
|
||||
@ -110,7 +110,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
|
||||
{
|
||||
$visitId = '123';
|
||||
$this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn(
|
||||
Visit::forBasePath(Visitor::emptyInstance()),
|
||||
Visit::forBasePath(Visitor::empty()),
|
||||
);
|
||||
$this->updatesGenerator->expects($this->once())->method('newOrphanVisitUpdate')->with(
|
||||
$this->isInstanceOf(Visit::class),
|
||||
@ -152,7 +152,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
|
||||
$never = static fn () => $exactly(0);
|
||||
|
||||
yield 'non-orphan visit' => [
|
||||
Visit::forValidShortUrl(ShortUrl::withLongUrl('https://longUrl'), Visitor::emptyInstance()),
|
||||
Visit::forValidShortUrl(ShortUrl::withLongUrl('https://longUrl'), Visitor::empty()),
|
||||
function (MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator) use ($once, $never): void {
|
||||
$update = Update::forTopicAndPayload('', []);
|
||||
$updatesGenerator->expects($never())->method('newOrphanVisitUpdate');
|
||||
@ -166,7 +166,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
|
||||
},
|
||||
];
|
||||
yield 'orphan visit' => [
|
||||
Visit::forBasePath(Visitor::emptyInstance()),
|
||||
Visit::forBasePath(Visitor::empty()),
|
||||
function (MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator) use ($once, $never): void {
|
||||
$update = Update::forTopicAndPayload('', []);
|
||||
$updatesGenerator->expects($once())->method('newOrphanVisitUpdate')->willReturn($update);
|
||||
|
@ -53,7 +53,7 @@ class NotifyVisitToRedisTest extends TestCase
|
||||
{
|
||||
$visitId = '123';
|
||||
$this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn(
|
||||
Visit::forBasePath(Visitor::emptyInstance()),
|
||||
Visit::forBasePath(Visitor::empty()),
|
||||
);
|
||||
$this->updatesGenerator->expects($this->once())->method('newOrphanVisitUpdate')->with(
|
||||
$this->isInstanceOf(Visit::class),
|
||||
|
@ -77,9 +77,9 @@ class MatomoVisitSenderTest extends TestCase
|
||||
|
||||
public static function provideTrackerMethods(): iterable
|
||||
{
|
||||
yield 'unlocated orphan visit' => [Visit::forBasePath(Visitor::emptyInstance()), null, []];
|
||||
yield 'unlocated orphan visit' => [Visit::forBasePath(Visitor::empty()), null, []];
|
||||
yield 'located regular visit' => [
|
||||
Visit::forValidShortUrl(ShortUrl::withLongUrl('https://shlink.io'), Visitor::emptyInstance())
|
||||
Visit::forValidShortUrl(ShortUrl::withLongUrl('https://shlink.io'), Visitor::empty())
|
||||
->locate(VisitLocation::fromGeolocation(new Location(
|
||||
countryCode: 'countryCode',
|
||||
countryName: 'countryName',
|
||||
@ -115,7 +115,7 @@ class MatomoVisitSenderTest extends TestCase
|
||||
|
||||
public static function provideUrlsToTrack(): iterable
|
||||
{
|
||||
yield 'orphan visit without visited URL' => [Visit::forBasePath(Visitor::emptyInstance()), ''];
|
||||
yield 'orphan visit without visited URL' => [Visit::forBasePath(Visitor::empty()), ''];
|
||||
yield 'orphan visit with visited URL' => [
|
||||
Visit::forBasePath(new Visitor('', '', null, 'https://s.test/foo')),
|
||||
'https://s.test/foo',
|
||||
@ -126,7 +126,7 @@ class MatomoVisitSenderTest extends TestCase
|
||||
ShortUrlInputFilter::LONG_URL => 'https://shlink.io',
|
||||
ShortUrlInputFilter::CUSTOM_SLUG => 'bar',
|
||||
]),
|
||||
), Visitor::emptyInstance()),
|
||||
), Visitor::empty()),
|
||||
'http://s2.test/bar',
|
||||
];
|
||||
}
|
||||
@ -135,7 +135,7 @@ class MatomoVisitSenderTest extends TestCase
|
||||
public function multipleVisitsCanBeSent(): void
|
||||
{
|
||||
$dateRange = DateRange::allTime();
|
||||
$visitor = Visitor::emptyInstance();
|
||||
$visitor = Visitor::empty();
|
||||
$bot = Visitor::botInstance();
|
||||
|
||||
$this->visitIterationRepository->expects($this->once())->method('findAllVisits')->with($dateRange)->willReturn([
|
||||
|
@ -34,7 +34,7 @@ class DeleteShortUrlServiceTest extends TestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
$shortUrl = ShortUrl::createFake()->setVisits(new ArrayCollection(
|
||||
array_map(fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()), range(0, 10)),
|
||||
array_map(fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::empty()), range(0, 10)),
|
||||
));
|
||||
$this->shortCode = $shortUrl->getShortCode();
|
||||
|
||||
|
@ -128,7 +128,7 @@ class ShortUrlResolverTest extends TestCase
|
||||
ShortUrlCreation::fromRawData(['maxVisits' => 3, 'longUrl' => 'https://longUrl']),
|
||||
);
|
||||
$shortUrl->setVisits(new ArrayCollection(array_map(
|
||||
fn () => Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance()),
|
||||
fn () => Visit::forValidShortUrl($shortUrl, Visitor::empty()),
|
||||
range(0, 4),
|
||||
)));
|
||||
|
||||
@ -147,7 +147,7 @@ class ShortUrlResolverTest extends TestCase
|
||||
'longUrl' => 'https://longUrl',
|
||||
]));
|
||||
$shortUrl->setVisits(new ArrayCollection(array_map(
|
||||
fn () => Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance()),
|
||||
fn () => Visit::forValidShortUrl($shortUrl, Visitor::empty()),
|
||||
range(0, 4),
|
||||
)));
|
||||
|
||||
|
@ -55,7 +55,7 @@ class VisitTest extends TestCase
|
||||
public static function provideOrphanVisits(): iterable
|
||||
{
|
||||
yield 'base path visit' => [
|
||||
$visit = Visit::forBasePath(Visitor::emptyInstance()),
|
||||
$visit = Visit::forBasePath(Visitor::empty()),
|
||||
[
|
||||
'referer' => '',
|
||||
'date' => $visit->date->toAtomString(),
|
||||
|
@ -48,7 +48,7 @@ class VisitLocatorTest extends TestCase
|
||||
$unlocatedVisits = array_map(
|
||||
fn (int $i) => Visit::forValidShortUrl(
|
||||
ShortUrl::withLongUrl(sprintf('https://short_code_%s', $i)),
|
||||
Visitor::emptyInstance(),
|
||||
Visitor::empty(),
|
||||
),
|
||||
range(1, 200),
|
||||
);
|
||||
@ -87,7 +87,7 @@ class VisitLocatorTest extends TestCase
|
||||
bool $isNonLocatableAddress,
|
||||
): void {
|
||||
$unlocatedVisits = [
|
||||
Visit::forValidShortUrl(ShortUrl::withLongUrl('https://foo'), Visitor::emptyInstance()),
|
||||
Visit::forValidShortUrl(ShortUrl::withLongUrl('https://foo'), Visitor::empty()),
|
||||
];
|
||||
|
||||
$this->repo->expects($this->once())->method($expectedRepoMethodName)->willReturn($unlocatedVisits);
|
||||
|
@ -40,7 +40,7 @@ class VisitToLocationHelperTest extends TestCase
|
||||
|
||||
public static function provideNonLocatableVisits(): iterable
|
||||
{
|
||||
yield [Visit::forBasePath(Visitor::emptyInstance()), IpCannotBeLocatedException::forEmptyAddress()];
|
||||
yield [Visit::forBasePath(Visitor::empty()), IpCannotBeLocatedException::forEmptyAddress()];
|
||||
yield [
|
||||
Visit::forBasePath(new Visitor('foo', 'bar', IpAddress::LOCALHOST, '')),
|
||||
IpCannotBeLocatedException::forLocalhost(),
|
||||
|
@ -53,7 +53,7 @@ class NonOrphanVisitsPaginatorAdapterTest extends TestCase
|
||||
#[Test, DataProvider('provideLimitAndOffset')]
|
||||
public function getSliceDelegatesToRepository(int $limit, int $offset): void
|
||||
{
|
||||
$visitor = Visitor::emptyInstance();
|
||||
$visitor = Visitor::empty();
|
||||
$list = [Visit::forRegularNotFound($visitor), Visit::forInvalidShortUrl($visitor)];
|
||||
$this->repo->expects($this->once())->method('findNonOrphanVisits')->with(new VisitsListFiltering(
|
||||
$this->params->dateRange,
|
||||
|
@ -53,7 +53,7 @@ class OrphanVisitsPaginatorAdapterTest extends TestCase
|
||||
#[Test, DataProvider('provideLimitAndOffset')]
|
||||
public function getSliceDelegatesToRepository(int $limit, int $offset): void
|
||||
{
|
||||
$visitor = Visitor::emptyInstance();
|
||||
$visitor = Visitor::empty();
|
||||
$list = [Visit::forRegularNotFound($visitor), Visit::forInvalidShortUrl($visitor)];
|
||||
$this->repo->expects($this->once())->method('findOrphanVisits')->with(new OrphanVisitsListFiltering(
|
||||
dateRange: $this->params->dateRange,
|
||||
|
@ -104,7 +104,7 @@ class VisitsStatsHelperTest extends TestCase
|
||||
$repo->expects($this->once())->method('shortCodeIsInUse')->with($identifier, $spec)->willReturn(true);
|
||||
|
||||
$list = array_map(
|
||||
static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()),
|
||||
static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::empty()),
|
||||
range(0, 1),
|
||||
);
|
||||
$repo2 = $this->createMock(VisitRepository::class);
|
||||
@ -164,7 +164,7 @@ class VisitsStatsHelperTest extends TestCase
|
||||
$repo->expects($this->once())->method('tagExists')->with($tag, $apiKey)->willReturn(true);
|
||||
|
||||
$list = array_map(
|
||||
static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()),
|
||||
static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::empty()),
|
||||
range(0, 1),
|
||||
);
|
||||
$repo2 = $this->createMock(VisitRepository::class);
|
||||
@ -205,7 +205,7 @@ class VisitsStatsHelperTest extends TestCase
|
||||
$repo->expects($this->once())->method('domainExists')->with($domain, $apiKey)->willReturn(true);
|
||||
|
||||
$list = array_map(
|
||||
static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()),
|
||||
static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::empty()),
|
||||
range(0, 1),
|
||||
);
|
||||
$repo2 = $this->createMock(VisitRepository::class);
|
||||
@ -235,7 +235,7 @@ class VisitsStatsHelperTest extends TestCase
|
||||
$repo->expects($this->never())->method('domainExists');
|
||||
|
||||
$list = array_map(
|
||||
static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()),
|
||||
static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::empty()),
|
||||
range(0, 1),
|
||||
);
|
||||
$repo2 = $this->createMock(VisitRepository::class);
|
||||
@ -261,7 +261,7 @@ class VisitsStatsHelperTest extends TestCase
|
||||
#[Test]
|
||||
public function orphanVisitsAreReturnedAsExpected(): void
|
||||
{
|
||||
$list = array_map(static fn () => Visit::forBasePath(Visitor::emptyInstance()), range(0, 3));
|
||||
$list = array_map(static fn () => Visit::forBasePath(Visitor::empty()), range(0, 3));
|
||||
$repo = $this->createMock(VisitRepository::class);
|
||||
$repo->expects($this->once())->method('countOrphanVisits')->with(
|
||||
$this->isInstanceOf(OrphanVisitsCountFiltering::class),
|
||||
@ -280,7 +280,7 @@ class VisitsStatsHelperTest extends TestCase
|
||||
public function nonOrphanVisitsAreReturnedAsExpected(): void
|
||||
{
|
||||
$list = array_map(
|
||||
static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()),
|
||||
static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::empty()),
|
||||
range(0, 3),
|
||||
);
|
||||
$repo = $this->createMock(VisitRepository::class);
|
||||
|
@ -33,43 +33,44 @@ class VisitsTrackerTest extends TestCase
|
||||
#[Test, DataProvider('provideTrackingMethodNames')]
|
||||
public function trackPersistsVisitAndDispatchesEvent(string $method, array $args): void
|
||||
{
|
||||
$this->em->expects($this->once())->method('persist')->with(
|
||||
$this->callback(fn (Visit $visit) => $visit->setId('1') !== null),
|
||||
);
|
||||
$this->em->expects($this->once())->method('flush');
|
||||
$this->em->expects($this->once())->method('persist')->with($this->isInstanceOf(Visit::class));
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatch')->with(
|
||||
$this->isInstanceOf(UrlVisited::class),
|
||||
);
|
||||
|
||||
$this->visitsTracker()->{$method}(...$args);
|
||||
$result = $this->visitsTracker()->{$method}(...$args);
|
||||
|
||||
self::assertInstanceOf(Visit::class, $result);
|
||||
}
|
||||
|
||||
#[Test, DataProvider('provideTrackingMethodNames')]
|
||||
public function trackingIsSkippedCompletelyWhenDisabledFromOptions(string $method, array $args): void
|
||||
{
|
||||
$this->em->expects($this->never())->method('persist');
|
||||
$this->em->expects($this->never())->method('flush');
|
||||
$this->eventDispatcher->expects($this->never())->method('dispatch');
|
||||
|
||||
$this->visitsTracker(new TrackingOptions(disableTracking: true))->{$method}(...$args);
|
||||
$result = $this->visitsTracker(new TrackingOptions(disableTracking: true))->{$method}(...$args);
|
||||
|
||||
self::assertNull($result);
|
||||
}
|
||||
|
||||
public static function provideTrackingMethodNames(): iterable
|
||||
{
|
||||
yield 'track' => ['track', [ShortUrl::createFake(), Visitor::emptyInstance()]];
|
||||
yield 'trackInvalidShortUrlVisit' => ['trackInvalidShortUrlVisit', [Visitor::emptyInstance()]];
|
||||
yield 'trackBaseUrlVisit' => ['trackBaseUrlVisit', [Visitor::emptyInstance()]];
|
||||
yield 'trackRegularNotFoundVisit' => ['trackRegularNotFoundVisit', [Visitor::emptyInstance()]];
|
||||
yield 'track' => ['track', [ShortUrl::createFake(), Visitor::empty()]];
|
||||
yield 'trackInvalidShortUrlVisit' => ['trackInvalidShortUrlVisit', [Visitor::empty()]];
|
||||
yield 'trackBaseUrlVisit' => ['trackBaseUrlVisit', [Visitor::empty()]];
|
||||
yield 'trackRegularNotFoundVisit' => ['trackRegularNotFoundVisit', [Visitor::empty()]];
|
||||
}
|
||||
|
||||
#[Test, DataProvider('provideOrphanTrackingMethodNames')]
|
||||
public function orphanVisitsAreNotTrackedWhenDisabled(string $method): void
|
||||
{
|
||||
$this->em->expects($this->never())->method('persist');
|
||||
$this->em->expects($this->never())->method('flush');
|
||||
$this->eventDispatcher->expects($this->never())->method('dispatch');
|
||||
|
||||
$this->visitsTracker(new TrackingOptions(trackOrphanVisits: false))->{$method}(Visitor::emptyInstance());
|
||||
$result = $this->visitsTracker(new TrackingOptions(trackOrphanVisits: false))->{$method}(Visitor::empty());
|
||||
|
||||
self::assertNull($result);
|
||||
}
|
||||
|
||||
public static function provideOrphanTrackingMethodNames(): iterable
|
||||
|
@ -35,7 +35,7 @@ class OrphanVisitsActionTest extends TestCase
|
||||
#[Test]
|
||||
public function requestIsHandled(): void
|
||||
{
|
||||
$visitor = Visitor::emptyInstance();
|
||||
$visitor = Visitor::empty();
|
||||
$visits = [Visit::forInvalidShortUrl($visitor), Visit::forRegularNotFound($visitor)];
|
||||
$this->visitsHelper->expects($this->once())->method('orphanVisits')->with(
|
||||
$this->isInstanceOf(OrphanVisitsParams::class),
|
||||
|
Loading…
Reference in New Issue
Block a user