mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-26 23:18:37 +03:00
Separated methods to track visits and list visits
This commit is contained in:
parent
12b07bb0ac
commit
1b4e62b823
14 changed files with 220 additions and 210 deletions
|
@ -74,7 +74,7 @@ return [
|
|||
Service\ShortUrlService::class,
|
||||
ShortUrlDataTransformer::class,
|
||||
],
|
||||
Command\ShortUrl\GetVisitsCommand::class => [Service\VisitsTracker::class],
|
||||
Command\ShortUrl\GetVisitsCommand::class => [Visit\VisitsStatsHelper::class],
|
||||
Command\ShortUrl\DeleteShortUrlCommand::class => [Service\ShortUrl\DeleteShortUrlService::class],
|
||||
|
||||
Command\Visit\LocateVisitsCommand::class => [
|
||||
|
|
|
@ -11,8 +11,8 @@ use Shlinkio\Shlink\Common\Util\DateRange;
|
|||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\UnknownVisitLocation;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -27,11 +27,11 @@ class GetVisitsCommand extends AbstractWithDateRangeCommand
|
|||
{
|
||||
public const NAME = 'short-url:visits';
|
||||
|
||||
private VisitsTrackerInterface $visitsTracker;
|
||||
private VisitsStatsHelperInterface $visitsHelper;
|
||||
|
||||
public function __construct(VisitsTrackerInterface $visitsTracker)
|
||||
public function __construct(VisitsStatsHelperInterface $visitsHelper)
|
||||
{
|
||||
$this->visitsTracker = $visitsTracker;
|
||||
$this->visitsHelper = $visitsHelper;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,10 @@ class GetVisitsCommand extends AbstractWithDateRangeCommand
|
|||
$startDate = $this->getStartDateOption($input, $output);
|
||||
$endDate = $this->getEndDateOption($input, $output);
|
||||
|
||||
$paginator = $this->visitsTracker->info($identifier, new VisitsParams(new DateRange($startDate, $endDate)));
|
||||
$paginator = $this->visitsHelper->visitsForShortUrl(
|
||||
$identifier,
|
||||
new VisitsParams(new DateRange($startDate, $endDate)),
|
||||
);
|
||||
|
||||
$rows = map($paginator->getCurrentPageResults(), function (Visit $visit) {
|
||||
$rowData = $visit->jsonSerialize();
|
||||
|
|
|
@ -19,7 +19,7 @@ use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
|||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
@ -31,12 +31,12 @@ class GetVisitsCommandTest extends TestCase
|
|||
use ProphecyTrait;
|
||||
|
||||
private CommandTester $commandTester;
|
||||
private ObjectProphecy $visitsTracker;
|
||||
private ObjectProphecy $visitsHelper;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->visitsTracker = $this->prophesize(VisitsTrackerInterface::class);
|
||||
$command = new GetVisitsCommand($this->visitsTracker->reveal());
|
||||
$this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class);
|
||||
$command = new GetVisitsCommand($this->visitsHelper->reveal());
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
$this->commandTester = new CommandTester($command);
|
||||
|
@ -46,7 +46,7 @@ class GetVisitsCommandTest extends TestCase
|
|||
public function noDateFlagsTriesToListWithoutDateRange(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsTracker->info(
|
||||
$this->visitsHelper->visitsForShortUrl(
|
||||
new ShortUrlIdentifier($shortCode),
|
||||
new VisitsParams(new DateRange(null, null)),
|
||||
)
|
||||
|
@ -62,7 +62,7 @@ class GetVisitsCommandTest extends TestCase
|
|||
$shortCode = 'abc123';
|
||||
$startDate = '2016-01-01';
|
||||
$endDate = '2016-02-01';
|
||||
$this->visitsTracker->info(
|
||||
$this->visitsHelper->visitsForShortUrl(
|
||||
new ShortUrlIdentifier($shortCode),
|
||||
new VisitsParams(new DateRange(Chronos::parse($startDate), Chronos::parse($endDate))),
|
||||
)
|
||||
|
@ -81,8 +81,10 @@ class GetVisitsCommandTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$startDate = 'foo';
|
||||
$info = $this->visitsTracker->info(new ShortUrlIdentifier($shortCode), new VisitsParams(new DateRange()))
|
||||
->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
$info = $this->visitsHelper->visitsForShortUrl(
|
||||
new ShortUrlIdentifier($shortCode),
|
||||
new VisitsParams(new DateRange()),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
|
||||
$this->commandTester->execute([
|
||||
'shortCode' => $shortCode,
|
||||
|
@ -101,7 +103,7 @@ class GetVisitsCommandTest extends TestCase
|
|||
public function outputIsProperlyGenerated(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsTracker->info(new ShortUrlIdentifier($shortCode), Argument::any())->willReturn(
|
||||
$this->visitsHelper->visitsForShortUrl(new ShortUrlIdentifier($shortCode), Argument::any())->willReturn(
|
||||
new Paginator(new ArrayAdapter([
|
||||
Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('bar', 'foo', '', ''))->locate(
|
||||
new VisitLocation(new Location('', 'Spain', '', '', 0, 0, '')),
|
||||
|
|
|
@ -6,22 +6,10 @@ namespace Shlinkio\Shlink\Core\Service;
|
|||
|
||||
use Doctrine\ORM;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlVisited;
|
||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Paginator\Adapter\VisitsForTagPaginatorAdapter;
|
||||
use Shlinkio\Shlink\Core\Paginator\Adapter\VisitsPaginatorAdapter;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Repository\TagRepository;
|
||||
use Shlinkio\Shlink\Core\Repository\VisitRepositoryInterface;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
class VisitsTracker implements VisitsTrackerInterface
|
||||
{
|
||||
|
@ -48,48 +36,4 @@ class VisitsTracker implements VisitsTrackerInterface
|
|||
|
||||
$this->eventDispatcher->dispatch(new ShortUrlVisited($visit->getId(), $visitor->getRemoteAddress()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Visit[]|Paginator
|
||||
* @throws ShortUrlNotFoundException
|
||||
*/
|
||||
public function info(ShortUrlIdentifier $identifier, VisitsParams $params, ?ApiKey $apiKey = null): Paginator
|
||||
{
|
||||
$spec = $apiKey !== null ? $apiKey->spec() : null;
|
||||
|
||||
/** @var ShortUrlRepositoryInterface $repo */
|
||||
$repo = $this->em->getRepository(ShortUrl::class);
|
||||
if (! $repo->shortCodeIsInUse($identifier->shortCode(), $identifier->domain(), $spec)) {
|
||||
throw ShortUrlNotFoundException::fromNotFound($identifier);
|
||||
}
|
||||
|
||||
/** @var VisitRepositoryInterface $repo */
|
||||
$repo = $this->em->getRepository(Visit::class);
|
||||
$paginator = new Paginator(new VisitsPaginatorAdapter($repo, $identifier, $params, $spec));
|
||||
$paginator->setMaxPerPage($params->getItemsPerPage())
|
||||
->setCurrentPage($params->getPage());
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Visit[]|Paginator
|
||||
* @throws TagNotFoundException
|
||||
*/
|
||||
public function visitsForTag(string $tag, VisitsParams $params, ?ApiKey $apiKey = null): Paginator
|
||||
{
|
||||
/** @var TagRepository $tagRepo */
|
||||
$tagRepo = $this->em->getRepository(Tag::class);
|
||||
if (! $tagRepo->tagExists($tag, $apiKey)) {
|
||||
throw TagNotFoundException::fromTag($tag);
|
||||
}
|
||||
|
||||
/** @var VisitRepositoryInterface $repo */
|
||||
$repo = $this->em->getRepository(Visit::class);
|
||||
$paginator = new Paginator(new VisitsForTagPaginatorAdapter($repo, $tag, $params, $apiKey));
|
||||
$paginator->setMaxPerPage($params->getItemsPerPage())
|
||||
->setCurrentPage($params->getPage());
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,29 +4,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
interface VisitsTrackerInterface
|
||||
{
|
||||
public function track(ShortUrl $shortUrl, Visitor $visitor): void;
|
||||
|
||||
/**
|
||||
* @return Visit[]|Paginator
|
||||
* @throws ShortUrlNotFoundException
|
||||
*/
|
||||
public function info(ShortUrlIdentifier $identifier, VisitsParams $params, ?ApiKey $apiKey = null): Paginator;
|
||||
|
||||
/**
|
||||
* @return Visit[]|Paginator
|
||||
* @throws TagNotFoundException
|
||||
*/
|
||||
public function visitsForTag(string $tag, VisitsParams $params, ?ApiKey $apiKey = null): Paginator;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,20 @@ declare(strict_types=1);
|
|||
namespace Shlinkio\Shlink\Core\Visit;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Paginator\Adapter\VisitsForTagPaginatorAdapter;
|
||||
use Shlinkio\Shlink\Core\Paginator\Adapter\VisitsPaginatorAdapter;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Repository\TagRepository;
|
||||
use Shlinkio\Shlink\Core\Repository\VisitRepository;
|
||||
use Shlinkio\Shlink\Core\Repository\VisitRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsStats;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
|
@ -30,4 +42,51 @@ class VisitsStatsHelper implements VisitsStatsHelperInterface
|
|||
$visitsRepo = $this->em->getRepository(Visit::class);
|
||||
return $visitsRepo->countVisits($apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Visit[]|Paginator
|
||||
* @throws ShortUrlNotFoundException
|
||||
*/
|
||||
public function visitsForShortUrl(
|
||||
ShortUrlIdentifier $identifier,
|
||||
VisitsParams $params,
|
||||
?ApiKey $apiKey = null
|
||||
): Paginator {
|
||||
$spec = $apiKey !== null ? $apiKey->spec() : null;
|
||||
|
||||
/** @var ShortUrlRepositoryInterface $repo */
|
||||
$repo = $this->em->getRepository(ShortUrl::class);
|
||||
if (! $repo->shortCodeIsInUse($identifier->shortCode(), $identifier->domain(), $spec)) {
|
||||
throw ShortUrlNotFoundException::fromNotFound($identifier);
|
||||
}
|
||||
|
||||
/** @var VisitRepositoryInterface $repo */
|
||||
$repo = $this->em->getRepository(Visit::class);
|
||||
$paginator = new Paginator(new VisitsPaginatorAdapter($repo, $identifier, $params, $spec));
|
||||
$paginator->setMaxPerPage($params->getItemsPerPage())
|
||||
->setCurrentPage($params->getPage());
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Visit[]|Paginator
|
||||
* @throws TagNotFoundException
|
||||
*/
|
||||
public function visitsForTag(string $tag, VisitsParams $params, ?ApiKey $apiKey = null): Paginator
|
||||
{
|
||||
/** @var TagRepository $tagRepo */
|
||||
$tagRepo = $this->em->getRepository(Tag::class);
|
||||
if (! $tagRepo->tagExists($tag, $apiKey)) {
|
||||
throw TagNotFoundException::fromTag($tag);
|
||||
}
|
||||
|
||||
/** @var VisitRepositoryInterface $repo */
|
||||
$repo = $this->em->getRepository(Visit::class);
|
||||
$paginator = new Paginator(new VisitsForTagPaginatorAdapter($repo, $tag, $params, $apiKey));
|
||||
$paginator->setMaxPerPage($params->getItemsPerPage())
|
||||
->setCurrentPage($params->getPage());
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,32 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Core\Visit;
|
||||
|
||||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsStats;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
interface VisitsStatsHelperInterface
|
||||
{
|
||||
public function getVisitsStats(?ApiKey $apiKey = null): VisitsStats;
|
||||
|
||||
/**
|
||||
* @return Visit[]|Paginator
|
||||
* @throws ShortUrlNotFoundException
|
||||
*/
|
||||
public function visitsForShortUrl(
|
||||
ShortUrlIdentifier $identifier,
|
||||
VisitsParams $params,
|
||||
?ApiKey $apiKey = null
|
||||
): Paginator;
|
||||
|
||||
/**
|
||||
* @return Visit[]|Paginator
|
||||
* @throws TagNotFoundException
|
||||
*/
|
||||
public function visitsForTag(string $tag, VisitsParams $params, ?ApiKey $apiKey = null): Paginator;
|
||||
}
|
||||
|
|
|
@ -5,35 +5,19 @@ declare(strict_types=1);
|
|||
namespace ShlinkioTest\Shlink\Core\Service;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Laminas\Stdlib\ArrayUtils;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlVisited;
|
||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Repository\TagRepository;
|
||||
use Shlinkio\Shlink\Core\Repository\VisitRepository;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use ShlinkioTest\Shlink\Core\Util\ApiKeyHelpersTrait;
|
||||
|
||||
use function Functional\map;
|
||||
use function range;
|
||||
|
||||
class VisitsTrackerTest extends TestCase
|
||||
{
|
||||
use ApiKeyHelpersTrait;
|
||||
use ProphecyTrait;
|
||||
|
||||
private VisitsTracker $visitsTracker;
|
||||
|
@ -60,85 +44,4 @@ class VisitsTrackerTest extends TestCase
|
|||
|
||||
$this->eventDispatcher->dispatch(Argument::type(ShortUrlVisited::class))->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideAdminApiKeys
|
||||
*/
|
||||
public function infoReturnsVisitsForCertainShortCode(?ApiKey $apiKey): void
|
||||
{
|
||||
$shortCode = '123ABC';
|
||||
$spec = $apiKey === null ? null : $apiKey->spec();
|
||||
$repo = $this->prophesize(ShortUrlRepositoryInterface::class);
|
||||
$count = $repo->shortCodeIsInUse($shortCode, null, $spec)->willReturn(true);
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledOnce();
|
||||
|
||||
$list = map(range(0, 1), fn () => Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance()));
|
||||
$repo2 = $this->prophesize(VisitRepository::class);
|
||||
$repo2->findVisitsByShortCode($shortCode, null, Argument::type(DateRange::class), 1, 0, $spec)->willReturn(
|
||||
$list,
|
||||
);
|
||||
$repo2->countVisitsByShortCode($shortCode, null, Argument::type(DateRange::class), $spec)->willReturn(1);
|
||||
$this->em->getRepository(Visit::class)->willReturn($repo2->reveal())->shouldBeCalledOnce();
|
||||
|
||||
$paginator = $this->visitsTracker->info(new ShortUrlIdentifier($shortCode), new VisitsParams(), $apiKey);
|
||||
|
||||
self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults()));
|
||||
$count->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function throwsExceptionWhenRequestingVisitsForInvalidShortCode(): void
|
||||
{
|
||||
$shortCode = '123ABC';
|
||||
$repo = $this->prophesize(ShortUrlRepositoryInterface::class);
|
||||
$count = $repo->shortCodeIsInUse($shortCode, null, null)->willReturn(false);
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledOnce();
|
||||
|
||||
$this->expectException(ShortUrlNotFoundException::class);
|
||||
$count->shouldBeCalledOnce();
|
||||
|
||||
$this->visitsTracker->info(new ShortUrlIdentifier($shortCode), new VisitsParams());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function throwsExceptionWhenRequestingVisitsForInvalidTag(): void
|
||||
{
|
||||
$tag = 'foo';
|
||||
$apiKey = new ApiKey();
|
||||
$repo = $this->prophesize(TagRepository::class);
|
||||
$tagExists = $repo->tagExists($tag, $apiKey)->willReturn(false);
|
||||
$getRepo = $this->em->getRepository(Tag::class)->willReturn($repo->reveal());
|
||||
|
||||
$this->expectException(TagNotFoundException::class);
|
||||
$tagExists->shouldBeCalledOnce();
|
||||
$getRepo->shouldBeCalledOnce();
|
||||
|
||||
$this->visitsTracker->visitsForTag($tag, new VisitsParams(), $apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideAdminApiKeys
|
||||
*/
|
||||
public function visitsForTagAreReturnedAsExpected(?ApiKey $apiKey): void
|
||||
{
|
||||
$tag = 'foo';
|
||||
$repo = $this->prophesize(TagRepository::class);
|
||||
$tagExists = $repo->tagExists($tag, $apiKey)->willReturn(true);
|
||||
$getRepo = $this->em->getRepository(Tag::class)->willReturn($repo->reveal());
|
||||
|
||||
$spec = $apiKey === null ? null : $apiKey->spec();
|
||||
$list = map(range(0, 1), fn () => Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance()));
|
||||
$repo2 = $this->prophesize(VisitRepository::class);
|
||||
$repo2->findVisitsByTag($tag, Argument::type(DateRange::class), 1, 0, $spec)->willReturn($list);
|
||||
$repo2->countVisitsByTag($tag, Argument::type(DateRange::class), $spec)->willReturn(1);
|
||||
$this->em->getRepository(Visit::class)->willReturn($repo2->reveal())->shouldBeCalledOnce();
|
||||
|
||||
$paginator = $this->visitsTracker->visitsForTag($tag, new VisitsParams(), $apiKey);
|
||||
|
||||
self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults()));
|
||||
$tagExists->shouldHaveBeenCalledOnce();
|
||||
$getRepo->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,19 +5,34 @@ declare(strict_types=1);
|
|||
namespace ShlinkioTest\Shlink\Core\Visit;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Laminas\Stdlib\ArrayUtils;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Repository\TagRepository;
|
||||
use Shlinkio\Shlink\Core\Repository\VisitRepository;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsStats;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelper;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use ShlinkioTest\Shlink\Core\Util\ApiKeyHelpersTrait;
|
||||
|
||||
use function Functional\map;
|
||||
use function range;
|
||||
|
||||
class VisitsStatsHelperTest extends TestCase
|
||||
{
|
||||
use ApiKeyHelpersTrait;
|
||||
use ProphecyTrait;
|
||||
|
||||
private VisitsStatsHelper $helper;
|
||||
|
@ -50,4 +65,85 @@ class VisitsStatsHelperTest extends TestCase
|
|||
{
|
||||
return map(range(0, 50, 5), fn (int $value) => [$value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideAdminApiKeys
|
||||
*/
|
||||
public function infoReturnsVisitsForCertainShortCode(?ApiKey $apiKey): void
|
||||
{
|
||||
$shortCode = '123ABC';
|
||||
$spec = $apiKey === null ? null : $apiKey->spec();
|
||||
$repo = $this->prophesize(ShortUrlRepositoryInterface::class);
|
||||
$count = $repo->shortCodeIsInUse($shortCode, null, $spec)->willReturn(true);
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledOnce();
|
||||
|
||||
$list = map(range(0, 1), fn () => Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance()));
|
||||
$repo2 = $this->prophesize(VisitRepository::class);
|
||||
$repo2->findVisitsByShortCode($shortCode, null, Argument::type(DateRange::class), 1, 0, $spec)->willReturn(
|
||||
$list,
|
||||
);
|
||||
$repo2->countVisitsByShortCode($shortCode, null, Argument::type(DateRange::class), $spec)->willReturn(1);
|
||||
$this->em->getRepository(Visit::class)->willReturn($repo2->reveal())->shouldBeCalledOnce();
|
||||
|
||||
$paginator = $this->helper->visitsForShortUrl(new ShortUrlIdentifier($shortCode), new VisitsParams(), $apiKey);
|
||||
|
||||
self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults()));
|
||||
$count->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function throwsExceptionWhenRequestingVisitsForInvalidShortCode(): void
|
||||
{
|
||||
$shortCode = '123ABC';
|
||||
$repo = $this->prophesize(ShortUrlRepositoryInterface::class);
|
||||
$count = $repo->shortCodeIsInUse($shortCode, null, null)->willReturn(false);
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledOnce();
|
||||
|
||||
$this->expectException(ShortUrlNotFoundException::class);
|
||||
$count->shouldBeCalledOnce();
|
||||
|
||||
$this->helper->visitsForShortUrl(new ShortUrlIdentifier($shortCode), new VisitsParams());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function throwsExceptionWhenRequestingVisitsForInvalidTag(): void
|
||||
{
|
||||
$tag = 'foo';
|
||||
$apiKey = new ApiKey();
|
||||
$repo = $this->prophesize(TagRepository::class);
|
||||
$tagExists = $repo->tagExists($tag, $apiKey)->willReturn(false);
|
||||
$getRepo = $this->em->getRepository(Tag::class)->willReturn($repo->reveal());
|
||||
|
||||
$this->expectException(TagNotFoundException::class);
|
||||
$tagExists->shouldBeCalledOnce();
|
||||
$getRepo->shouldBeCalledOnce();
|
||||
|
||||
$this->helper->visitsForTag($tag, new VisitsParams(), $apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideAdminApiKeys
|
||||
*/
|
||||
public function visitsForTagAreReturnedAsExpected(?ApiKey $apiKey): void
|
||||
{
|
||||
$tag = 'foo';
|
||||
$repo = $this->prophesize(TagRepository::class);
|
||||
$tagExists = $repo->tagExists($tag, $apiKey)->willReturn(true);
|
||||
$getRepo = $this->em->getRepository(Tag::class)->willReturn($repo->reveal());
|
||||
|
||||
$spec = $apiKey === null ? null : $apiKey->spec();
|
||||
$list = map(range(0, 1), fn () => Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance()));
|
||||
$repo2 = $this->prophesize(VisitRepository::class);
|
||||
$repo2->findVisitsByTag($tag, Argument::type(DateRange::class), 1, 0, $spec)->willReturn($list);
|
||||
$repo2->countVisitsByTag($tag, Argument::type(DateRange::class), $spec)->willReturn(1);
|
||||
$this->em->getRepository(Visit::class)->willReturn($repo2->reveal())->shouldBeCalledOnce();
|
||||
|
||||
$paginator = $this->helper->visitsForTag($tag, new VisitsParams(), $apiKey);
|
||||
|
||||
self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults()));
|
||||
$tagExists->shouldHaveBeenCalledOnce();
|
||||
$getRepo->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,8 +66,8 @@ return [
|
|||
Service\ShortUrl\ShortUrlResolver::class,
|
||||
ShortUrlDataTransformer::class,
|
||||
],
|
||||
Action\Visit\ShortUrlVisitsAction::class => [Service\VisitsTracker::class],
|
||||
Action\Visit\TagVisitsAction::class => [Service\VisitsTracker::class],
|
||||
Action\Visit\ShortUrlVisitsAction::class => [Visit\VisitsStatsHelper::class],
|
||||
Action\Visit\TagVisitsAction::class => [Visit\VisitsStatsHelper::class],
|
||||
Action\Visit\GlobalVisitsAction::class => [Visit\VisitsStatsHelper::class],
|
||||
Action\ShortUrl\ListShortUrlsAction::class => [Service\ShortUrlService::class, ShortUrlDataTransformer::class],
|
||||
Action\ShortUrl\EditShortUrlTagsAction::class => [Service\ShortUrlService::class],
|
||||
|
|
|
@ -10,7 +10,7 @@ use Psr\Http\Message\ServerRequestInterface as Request;
|
|||
use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtilsTrait;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
|
||||
|
||||
|
@ -21,11 +21,11 @@ class ShortUrlVisitsAction extends AbstractRestAction
|
|||
protected const ROUTE_PATH = '/short-urls/{shortCode}/visits';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||
|
||||
private VisitsTrackerInterface $visitsTracker;
|
||||
private VisitsStatsHelperInterface $visitsHelper;
|
||||
|
||||
public function __construct(VisitsTrackerInterface $visitsTracker)
|
||||
public function __construct(VisitsStatsHelperInterface $visitsHelper)
|
||||
{
|
||||
$this->visitsTracker = $visitsTracker;
|
||||
$this->visitsHelper = $visitsHelper;
|
||||
}
|
||||
|
||||
public function handle(Request $request): Response
|
||||
|
@ -33,7 +33,7 @@ class ShortUrlVisitsAction extends AbstractRestAction
|
|||
$identifier = ShortUrlIdentifier::fromApiRequest($request);
|
||||
$params = VisitsParams::fromRawData($request->getQueryParams());
|
||||
$apiKey = AuthenticationMiddleware::apiKeyFromRequest($request);
|
||||
$visits = $this->visitsTracker->info($identifier, $params, $apiKey);
|
||||
$visits = $this->visitsHelper->visitsForShortUrl($identifier, $params, $apiKey);
|
||||
|
||||
return new JsonResponse([
|
||||
'visits' => $this->serializePaginator($visits),
|
||||
|
|
|
@ -9,7 +9,7 @@ use Psr\Http\Message\ResponseInterface as Response;
|
|||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtilsTrait;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
|
||||
|
||||
|
@ -20,11 +20,11 @@ class TagVisitsAction extends AbstractRestAction
|
|||
protected const ROUTE_PATH = '/tags/{tag}/visits';
|
||||
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
|
||||
|
||||
private VisitsTrackerInterface $visitsTracker;
|
||||
private VisitsStatsHelperInterface $visitsHelper;
|
||||
|
||||
public function __construct(VisitsTrackerInterface $visitsTracker)
|
||||
public function __construct(VisitsStatsHelperInterface $visitsHelper)
|
||||
{
|
||||
$this->visitsTracker = $visitsTracker;
|
||||
$this->visitsHelper = $visitsHelper;
|
||||
}
|
||||
|
||||
public function handle(Request $request): Response
|
||||
|
@ -32,7 +32,7 @@ class TagVisitsAction extends AbstractRestAction
|
|||
$tag = $request->getAttribute('tag', '');
|
||||
$params = VisitsParams::fromRawData($request->getQueryParams());
|
||||
$apiKey = AuthenticationMiddleware::apiKeyFromRequest($request);
|
||||
$visits = $this->visitsTracker->visitsForTag($tag, $params, $apiKey);
|
||||
$visits = $this->visitsHelper->visitsForTag($tag, $params, $apiKey);
|
||||
|
||||
return new JsonResponse([
|
||||
'visits' => $this->serializePaginator($visits),
|
||||
|
|
|
@ -16,7 +16,7 @@ use Shlinkio\Shlink\Common\Paginator\Paginator;
|
|||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\Visit\ShortUrlVisitsAction;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
|
@ -25,19 +25,19 @@ class ShortUrlVisitsActionTest extends TestCase
|
|||
use ProphecyTrait;
|
||||
|
||||
private ShortUrlVisitsAction $action;
|
||||
private ObjectProphecy $visitsTracker;
|
||||
private ObjectProphecy $visitsHelper;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->visitsTracker = $this->prophesize(VisitsTracker::class);
|
||||
$this->action = new ShortUrlVisitsAction($this->visitsTracker->reveal());
|
||||
$this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class);
|
||||
$this->action = new ShortUrlVisitsAction($this->visitsHelper->reveal());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function providingCorrectShortCodeReturnsVisits(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsTracker->info(
|
||||
$this->visitsHelper->visitsForShortUrl(
|
||||
new ShortUrlIdentifier($shortCode),
|
||||
Argument::type(VisitsParams::class),
|
||||
Argument::type(ApiKey::class),
|
||||
|
@ -52,7 +52,7 @@ class ShortUrlVisitsActionTest extends TestCase
|
|||
public function paramsAreReadFromQuery(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsTracker->info(new ShortUrlIdentifier($shortCode), new VisitsParams(
|
||||
$this->visitsHelper->visitsForShortUrl(new ShortUrlIdentifier($shortCode), new VisitsParams(
|
||||
new DateRange(null, Chronos::parse('2016-01-01 00:00:00')),
|
||||
3,
|
||||
10,
|
||||
|
|
|
@ -12,7 +12,7 @@ use Prophecy\PhpUnit\ProphecyTrait;
|
|||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\Visit\TagVisitsAction;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
|
@ -21,12 +21,12 @@ class TagVisitsActionTest extends TestCase
|
|||
use ProphecyTrait;
|
||||
|
||||
private TagVisitsAction $action;
|
||||
private ObjectProphecy $visitsTracker;
|
||||
private ObjectProphecy $visitsHelper;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->visitsTracker = $this->prophesize(VisitsTracker::class);
|
||||
$this->action = new TagVisitsAction($this->visitsTracker->reveal());
|
||||
$this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class);
|
||||
$this->action = new TagVisitsAction($this->visitsHelper->reveal());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
@ -34,7 +34,7 @@ class TagVisitsActionTest extends TestCase
|
|||
{
|
||||
$tag = 'foo';
|
||||
$apiKey = new ApiKey();
|
||||
$getVisits = $this->visitsTracker->visitsForTag($tag, Argument::type(VisitsParams::class), $apiKey)->willReturn(
|
||||
$getVisits = $this->visitsHelper->visitsForTag($tag, Argument::type(VisitsParams::class), $apiKey)->willReturn(
|
||||
new Paginator(new ArrayAdapter([])),
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in a new issue