shlink/module/Core/test/Visit/VisitLocatorTest.php

170 lines
6.1 KiB
PHP
Raw Normal View History

2016-07-30 16:01:07 -05:00
<?php
2019-10-05 10:26:10 -05:00
2017-10-12 03:13:20 -05:00
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Core\Visit;
2016-07-30 16:01:07 -05:00
use Doctrine\ORM\EntityManager;
2019-12-29 15:27:00 -06:00
use Exception;
use PHPUnit\Framework\Assert;
2017-03-24 14:34:18 -05:00
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\MethodProphecy;
2016-07-30 16:01:07 -05:00
use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
2016-07-30 16:01:07 -05:00
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Entity\VisitLocation;
use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Repository\VisitRepositoryInterface;
use Shlinkio\Shlink\Core\Visit\VisitGeolocationHelperInterface;
use Shlinkio\Shlink\Core\Visit\VisitLocator;
use Shlinkio\Shlink\IpGeolocation\Model\Location;
use function array_shift;
use function count;
use function floor;
use function func_get_args;
use function Functional\map;
use function range;
use function sprintf;
2016-07-30 16:01:07 -05:00
class VisitLocatorTest extends TestCase
2016-07-30 16:01:07 -05:00
{
private VisitLocator $visitService;
private ObjectProphecy $em;
private ObjectProphecy $repo;
2016-07-30 16:01:07 -05:00
2019-02-16 03:53:45 -06:00
public function setUp(): void
2016-07-30 16:01:07 -05:00
{
$this->em = $this->prophesize(EntityManager::class);
$this->repo = $this->prophesize(VisitRepositoryInterface::class);
$this->em->getRepository(Visit::class)->willReturn($this->repo->reveal());
$this->visitService = new VisitLocator($this->em->reveal());
2016-07-30 16:01:07 -05:00
}
/**
* @test
* @dataProvider provideMethodNames
*/
public function locateVisitsIteratesAndLocatesExpectedVisits(
string $serviceMethodName,
string $expectedRepoMethodName
): void {
$unlocatedVisits = map(
range(1, 200),
2020-01-01 13:48:31 -06:00
fn (int $i) => new Visit(new ShortUrl(sprintf('short_code_%s', $i)), Visitor::emptyInstance()),
);
$findVisits = $this->mockRepoMethod($expectedRepoMethodName)->willReturn($unlocatedVisits);
2020-01-01 13:48:31 -06:00
$persist = $this->em->persist(Argument::type(Visit::class))->will(function (): void {
});
2020-01-01 13:48:31 -06:00
$flush = $this->em->flush()->will(function (): void {
});
2020-01-01 13:48:31 -06:00
$clear = $this->em->clear()->will(function (): void {
});
$this->visitService->{$serviceMethodName}(new class implements VisitGeolocationHelperInterface {
public function geolocateVisit(Visit $visit): Location
{
return Location::emptyInstance();
}
public function onVisitLocated(VisitLocation $visitLocation, Visit $visit): void
{
$args = func_get_args();
Assert::assertInstanceOf(VisitLocation::class, array_shift($args));
Assert::assertInstanceOf(Visit::class, array_shift($args));
}
});
$findVisits->shouldHaveBeenCalledOnce();
$persist->shouldHaveBeenCalledTimes(count($unlocatedVisits));
$flush->shouldHaveBeenCalledTimes(floor(count($unlocatedVisits) / 200) + 1);
$clear->shouldHaveBeenCalledTimes(floor(count($unlocatedVisits) / 200) + 1);
2016-07-30 16:01:07 -05:00
}
public function provideMethodNames(): iterable
{
yield 'locateUnlocatedVisits' => ['locateUnlocatedVisits', 'findUnlocatedVisits'];
yield 'locateVisitsWithEmptyLocation' => ['locateVisitsWithEmptyLocation', 'findVisitsWithEmptyLocation'];
yield 'locateAllVisits' => ['locateAllVisits', 'findAllVisits'];
}
/**
* @test
* @dataProvider provideIsNonLocatableAddress
*/
public function visitsWhichCannotBeLocatedAreIgnoredOrLocatedAsEmpty(
string $serviceMethodName,
string $expectedRepoMethodName,
bool $isNonLocatableAddress
): void {
$unlocatedVisits = [
new Visit(new ShortUrl('foo'), Visitor::emptyInstance()),
];
$findVisits = $this->mockRepoMethod($expectedRepoMethodName)->willReturn($unlocatedVisits);
2020-01-01 13:48:31 -06:00
$persist = $this->em->persist(Argument::type(Visit::class))->will(function (): void {
});
2020-01-01 13:48:31 -06:00
$flush = $this->em->flush()->will(function (): void {
});
2020-01-01 13:48:31 -06:00
$clear = $this->em->clear()->will(function (): void {
});
$this->visitService->{$serviceMethodName}(
new class ($isNonLocatableAddress) implements VisitGeolocationHelperInterface {
private bool $isNonLocatableAddress;
public function __construct(bool $isNonLocatableAddress)
{
$this->isNonLocatableAddress = $isNonLocatableAddress;
}
public function geolocateVisit(Visit $visit): Location
{
throw $this->isNonLocatableAddress
? new IpCannotBeLocatedException('Cannot be located')
: IpCannotBeLocatedException::forError(new Exception(''));
}
public function onVisitLocated(VisitLocation $visitLocation, Visit $visit): void
{
}
},
);
$findVisits->shouldHaveBeenCalledOnce();
$persist->shouldHaveBeenCalledTimes($isNonLocatableAddress ? 1 : 0);
$flush->shouldHaveBeenCalledOnce();
$clear->shouldHaveBeenCalledOnce();
2016-07-30 16:01:07 -05:00
}
public function provideIsNonLocatableAddress(): iterable
{
yield 'locateUnlocatedVisits - locatable address' => ['locateUnlocatedVisits', 'findUnlocatedVisits', false];
yield 'locateUnlocatedVisits - non-locatable address' => ['locateUnlocatedVisits', 'findUnlocatedVisits', true];
yield 'locateVisitsWithEmptyLocation - locatable address' => [
'locateVisitsWithEmptyLocation',
'findVisitsWithEmptyLocation',
false,
];
yield 'locateVisitsWithEmptyLocation - non-locatable address' => [
'locateVisitsWithEmptyLocation',
'findVisitsWithEmptyLocation',
true,
];
yield 'locateAllVisits - locatable address' => ['locateAllVisits', 'findAllVisits', false];
yield 'locateAllVisits - non-locatable address' => ['locateAllVisits', 'findAllVisits', true];
}
private function mockRepoMethod(string $methodName): MethodProphecy
{
return (new MethodProphecy($this->repo, $methodName, new Argument\ArgumentsWildcard([])));
}
2016-07-30 16:01:07 -05:00
}