shlink/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php

222 lines
8.3 KiB
PHP
Raw Normal View History

2016-07-30 07:42:09 -05:00
<?php
2019-10-05 10:26:10 -05:00
2017-10-12 03:13:20 -05:00
declare(strict_types=1);
2016-08-15 04:01:55 -05:00
namespace ShlinkioTest\Shlink\CLI\Command\Visit;
2016-07-30 07:42:09 -05:00
2017-03-24 14:34:18 -05:00
use PHPUnit\Framework\TestCase;
2016-07-30 07:42:09 -05:00
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
2019-04-14 02:10:00 -05:00
use Shlinkio\Shlink\CLI\Command\Visit\LocateVisitsCommand;
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdaterInterface;
use Shlinkio\Shlink\Common\Util\IpAddress;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
2016-07-30 07:42:09 -05:00
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Entity\VisitLocation;
use Shlinkio\Shlink\Core\Model\Visitor;
2016-07-30 07:42:09 -05:00
use Shlinkio\Shlink\Core\Service\VisitService;
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
use Shlinkio\Shlink\IpGeolocation\Model\Location;
use Shlinkio\Shlink\IpGeolocation\Resolver\IpApiLocationResolver;
2016-07-30 07:42:09 -05:00
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Output\OutputInterface;
2016-07-30 07:42:09 -05:00
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Lock;
use function array_shift;
use function sprintf;
2016-07-30 07:42:09 -05:00
2019-04-14 02:10:00 -05:00
class LocateVisitsCommandTest extends TestCase
2016-07-30 07:42:09 -05:00
{
/** @var CommandTester */
2018-11-12 13:37:30 -06:00
private $commandTester;
/** @var ObjectProphecy */
2018-11-12 13:37:30 -06:00
private $visitService;
/** @var ObjectProphecy */
2018-11-12 13:37:30 -06:00
private $ipResolver;
/** @var ObjectProphecy */
private $locker;
/** @var ObjectProphecy */
private $lock;
/** @var ObjectProphecy */
private $dbUpdater;
2016-07-30 07:42:09 -05:00
2019-02-16 03:53:45 -06:00
public function setUp(): void
2016-07-30 07:42:09 -05:00
{
$this->visitService = $this->prophesize(VisitService::class);
$this->ipResolver = $this->prophesize(IpApiLocationResolver::class);
$this->dbUpdater = $this->prophesize(GeolocationDbUpdaterInterface::class);
$this->locker = $this->prophesize(Lock\Factory::class);
$this->lock = $this->prophesize(Lock\LockInterface::class);
$this->lock->acquire(false)->willReturn(true);
$this->lock->release()->will(function () {
});
$this->locker->createLock(Argument::type('string'), 90.0, false)->willReturn($this->lock->reveal());
2019-04-14 02:10:00 -05:00
$command = new LocateVisitsCommand(
2016-07-30 07:42:09 -05:00
$this->visitService->reveal(),
$this->ipResolver->reveal(),
$this->locker->reveal(),
$this->dbUpdater->reveal()
2016-07-30 07:42:09 -05:00
);
$app = new Application();
$app->add($command);
$this->commandTester = new CommandTester($command);
}
/** @test */
public function allPendingVisitsAreProcessed(): void
2016-07-30 07:42:09 -05:00
{
$visit = new Visit(new ShortUrl(''), new Visitor('', '', '1.2.3.4'));
$location = new VisitLocation(Location::emptyInstance());
2019-02-17 06:21:07 -06:00
$locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will(
function (array $args) use ($visit, $location) {
$firstCallback = array_shift($args);
$firstCallback($visit);
2016-07-30 07:42:09 -05:00
$secondCallback = array_shift($args);
$secondCallback($location, $visit);
}
);
$resolveIpLocation = $this->ipResolver->resolveIpLocation(Argument::any())->willReturn(
Location::emptyInstance()
);
2016-07-30 07:42:09 -05:00
$this->commandTester->execute([]);
2016-07-30 07:42:09 -05:00
$output = $this->commandTester->getDisplay();
2019-02-16 03:53:45 -06:00
$this->assertStringContainsString('Processing IP 1.2.3.0', $output);
$locateVisits->shouldHaveBeenCalledOnce();
$resolveIpLocation->shouldHaveBeenCalledOnce();
2016-07-30 07:42:09 -05:00
}
/**
* @test
* @dataProvider provideIgnoredAddresses
2016-07-30 07:42:09 -05:00
*/
public function localhostAndEmptyAddressesAreIgnored(?string $address, string $message): void
2016-07-30 07:42:09 -05:00
{
$visit = new Visit(new ShortUrl(''), new Visitor('', '', $address));
$location = new VisitLocation(Location::emptyInstance());
2019-02-17 06:21:07 -06:00
$locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will(
function (array $args) use ($visit, $location) {
$firstCallback = array_shift($args);
$firstCallback($visit);
$secondCallback = array_shift($args);
$secondCallback($location, $visit);
}
);
$resolveIpLocation = $this->ipResolver->resolveIpLocation(Argument::any())->willReturn(
Location::emptyInstance()
);
$this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]);
$output = $this->commandTester->getDisplay();
$this->assertStringContainsString($message, $output);
if (empty($address)) {
$this->assertStringNotContainsString('Processing IP', $output);
} else {
$this->assertStringContainsString('Processing IP', $output);
}
$locateVisits->shouldHaveBeenCalledOnce();
$resolveIpLocation->shouldNotHaveBeenCalled();
}
public function provideIgnoredAddresses(): iterable
{
yield 'with empty address' => ['', 'Ignored visit with no IP address'];
yield 'with null address' => [null, 'Ignored visit with no IP address'];
yield 'with localhost address' => [IpAddress::LOCALHOST, 'Ignored localhost address'];
}
2016-07-30 07:42:09 -05:00
/** @test */
public function errorWhileLocatingIpIsDisplayed(): void
{
$visit = new Visit(new ShortUrl(''), new Visitor('', '', '1.2.3.4'));
$location = new VisitLocation(Location::emptyInstance());
2016-07-30 07:42:09 -05:00
2019-02-17 06:21:07 -06:00
$locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will(
function (array $args) use ($visit, $location) {
$firstCallback = array_shift($args);
$firstCallback($visit);
$secondCallback = array_shift($args);
$secondCallback($location, $visit);
}
);
$resolveIpLocation = $this->ipResolver->resolveIpLocation(Argument::any())->willThrow(WrongIpException::class);
$this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]);
$output = $this->commandTester->getDisplay();
$this->assertStringContainsString('An error occurred while locating IP. Skipped', $output);
$locateVisits->shouldHaveBeenCalledOnce();
$resolveIpLocation->shouldHaveBeenCalledOnce();
2016-07-30 07:42:09 -05:00
}
2019-02-17 13:28:34 -06:00
/** @test */
public function noActionIsPerformedIfLockIsAcquired(): void
{
$this->lock->acquire(false)->willReturn(false);
2019-02-17 06:21:07 -06:00
$locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will(function () {
});
$resolveIpLocation = $this->ipResolver->resolveIpLocation(Argument::any())->willReturn([]);
$this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]);
$output = $this->commandTester->getDisplay();
2019-02-16 03:53:45 -06:00
$this->assertStringContainsString(
sprintf('Command "%s" is already in progress. Skipping.', LocateVisitsCommand::NAME),
$output
);
$locateVisits->shouldNotHaveBeenCalled();
$resolveIpLocation->shouldNotHaveBeenCalled();
}
/**
* @test
* @dataProvider provideParams
*/
public function showsProperMessageWhenGeoLiteUpdateFails(bool $olderDbExists, string $expectedMessage): void
{
$locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will(function () {
});
$checkDbUpdate = $this->dbUpdater->checkDbUpdate(Argument::cetera())->will(
function (array $args) use ($olderDbExists) {
[$mustBeUpdated, $handleProgress] = $args;
$mustBeUpdated($olderDbExists);
$handleProgress(100, 50);
throw GeolocationDbUpdateFailedException::create($olderDbExists);
}
);
$this->commandTester->execute([]);
$output = $this->commandTester->getDisplay();
$this->assertStringContainsString(
sprintf('%s GeoLite2 database...', $olderDbExists ? 'Updating' : 'Downloading'),
$output
);
$this->assertStringContainsString($expectedMessage, $output);
$locateVisits->shouldHaveBeenCalledTimes((int) $olderDbExists);
$checkDbUpdate->shouldHaveBeenCalledOnce();
}
public function provideParams(): iterable
{
yield [true, '[Warning] GeoLite2 database update failed. Proceeding with old version.'];
yield [false, 'GeoLite2 database download failed. It is not possible to locate visits.'];
}
2016-07-30 07:42:09 -05:00
}