mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-29 04:52:54 +03:00
Add unit test to MatomoSendVisitsCommand
This commit is contained in:
parent
bbdbafd8db
commit
f0e62004d5
4 changed files with 183 additions and 36 deletions
|
@ -9,7 +9,6 @@ use Shlinkio\Shlink\CLI\Util\ExitCode;
|
||||||
use Shlinkio\Shlink\Core\Matomo\MatomoOptions;
|
use Shlinkio\Shlink\Core\Matomo\MatomoOptions;
|
||||||
use Shlinkio\Shlink\Core\Matomo\MatomoVisitSenderInterface;
|
use Shlinkio\Shlink\Core\Matomo\MatomoVisitSenderInterface;
|
||||||
use Shlinkio\Shlink\Core\Matomo\VisitSendingProgressTrackerInterface;
|
use Shlinkio\Shlink\Core\Matomo\VisitSendingProgressTrackerInterface;
|
||||||
use Symfony\Component\Console\Application;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
@ -18,13 +17,15 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\buildDateRange;
|
use function Shlinkio\Shlink\Common\buildDateRange;
|
||||||
|
use function Shlinkio\Shlink\Core\dateRangeToHumanFriendly;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
class MatomoSendVisitsCommand extends Command
|
class MatomoSendVisitsCommand extends Command implements VisitSendingProgressTrackerInterface
|
||||||
{
|
{
|
||||||
public const NAME = 'integration:matomo:send-visits';
|
public const NAME = 'integration:matomo:send-visits';
|
||||||
|
|
||||||
private readonly bool $matomoEnabled;
|
private readonly bool $matomoEnabled;
|
||||||
|
private SymfonyStyle $io;
|
||||||
|
|
||||||
public function __construct(MatomoOptions $matomoOptions, private readonly MatomoVisitSenderInterface $visitSender)
|
public function __construct(MatomoOptions $matomoOptions, private readonly MatomoVisitSenderInterface $visitSender)
|
||||||
{
|
{
|
||||||
|
@ -79,10 +80,10 @@ class MatomoSendVisitsCommand extends Command
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
{
|
{
|
||||||
$io = new SymfonyStyle($input, $output);
|
$this->io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
if (! $this->matomoEnabled) {
|
if (! $this->matomoEnabled) {
|
||||||
$io->warning('Matomo integration is not enabled in this Shlink instance');
|
$this->io->warning('Matomo integration is not enabled in this Shlink instance');
|
||||||
return ExitCode::EXIT_WARNING;
|
return ExitCode::EXIT_WARNING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,50 +96,45 @@ class MatomoSendVisitsCommand extends Command
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($input->isInteractive()) {
|
if ($input->isInteractive()) {
|
||||||
// TODO Display the resolved date range in case it didn't fail to parse but the value was incorrect
|
$this->io->warning([
|
||||||
$io->warning([
|
'You are about to send visits from this Shlink instance to Matomo',
|
||||||
'You are about to send visits in this Shlink instance to Matomo',
|
'Resolved date range -> ' . dateRangeToHumanFriendly($dateRange),
|
||||||
'Shlink will not check for already sent visits, which could result in some duplications. Make sure '
|
'Shlink will not check for already sent visits, which could result in some duplications. Make sure '
|
||||||
. 'you have verified only visits in the right date range are going to be sent.',
|
. 'you have verified only visits in the right date range are going to be sent.',
|
||||||
]);
|
]);
|
||||||
if (! $io->confirm('Continue?', default: false)) {
|
if (! $this->io->confirm('Continue?', default: false)) {
|
||||||
return ExitCode::EXIT_WARNING;
|
return ExitCode::EXIT_WARNING;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->visitSender->sendVisitsInDateRange(
|
$result = $this->visitSender->sendVisitsInDateRange($dateRange, $this);
|
||||||
$dateRange,
|
|
||||||
new class ($io, $this->getApplication()) implements VisitSendingProgressTrackerInterface {
|
|
||||||
public function __construct(private readonly SymfonyStyle $io, private readonly ?Application $app)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function success(int $index): void
|
|
||||||
{
|
|
||||||
$this->io->write('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function error(int $index, Throwable $e): void
|
|
||||||
{
|
|
||||||
$this->io->write('<error>E</error>');
|
|
||||||
if ($this->io->isVerbose()) {
|
|
||||||
$this->app?->renderThrowable($e, $this->io);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
match (true) {
|
match (true) {
|
||||||
$result->hasFailures() && $result->hasSuccesses() => $io->warning(
|
$result->hasFailures() && $result->hasSuccesses() => $this->io->warning(
|
||||||
sprintf('%s visits sent to Matomo. %s failed', $result->successfulVisits, $result->failedVisits),
|
sprintf('%s visits sent to Matomo. %s failed.', $result->successfulVisits, $result->failedVisits),
|
||||||
),
|
),
|
||||||
$result->hasFailures() => $io->error(
|
$result->hasFailures() => $this->io->error(
|
||||||
sprintf('%s visits failed to be sent to Matomo.', $result->failedVisits),
|
sprintf('Failed to send %s visits to Matomo.', $result->failedVisits),
|
||||||
),
|
),
|
||||||
$result->hasSuccesses() => $io->success(sprintf('%s visits sent to Matomo.', $result->successfulVisits)),
|
$result->hasSuccesses() => $this->io->success(
|
||||||
default => $io->info('There was no visits matching provided date range'),
|
sprintf('%s visits sent to Matomo.', $result->successfulVisits),
|
||||||
|
),
|
||||||
|
default => $this->io->info('There was no visits matching provided date range.'),
|
||||||
};
|
};
|
||||||
|
|
||||||
return ExitCode::EXIT_SUCCESS;
|
return ExitCode::EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function success(int $index): void
|
||||||
|
{
|
||||||
|
$this->io->write('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function error(int $index, Throwable $e): void
|
||||||
|
{
|
||||||
|
$this->io->write('<error>E</error>');
|
||||||
|
if ($this->io->isVerbose()) {
|
||||||
|
$this->getApplication()?->renderThrowable($e, $this->io);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace ShlinkioTest\Shlink\CLI\Command\Integration;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use PHPUnit\Framework\Attributes\TestWith;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Shlinkio\Shlink\CLI\Command\Integration\MatomoSendVisitsCommand;
|
||||||
|
use Shlinkio\Shlink\CLI\Util\ExitCode;
|
||||||
|
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||||
|
use Shlinkio\Shlink\Core\Matomo\MatomoOptions;
|
||||||
|
use Shlinkio\Shlink\Core\Matomo\MatomoVisitSenderInterface;
|
||||||
|
use Shlinkio\Shlink\Core\Matomo\Model\SendVisitsResult;
|
||||||
|
use ShlinkioTest\Shlink\CLI\Util\CliTestUtils;
|
||||||
|
|
||||||
|
class MatomoSendVisitsCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
private MockObject & MatomoVisitSenderInterface $visitSender;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->visitSender = $this->createMock(MatomoVisitSenderInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function warningDisplayedIfIntegrationIsNotEnabled(): void
|
||||||
|
{
|
||||||
|
[$output, $exitCode] = $this->executeCommand(matomoEnabled: false);
|
||||||
|
|
||||||
|
self::assertStringContainsString('Matomo integration is not enabled in this Shlink instance', $output);
|
||||||
|
self::assertEquals(ExitCode::EXIT_WARNING, $exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[TestWith([true])]
|
||||||
|
#[TestWith([false])]
|
||||||
|
public function warningIsOnlyDisplayedInInteractiveMode(bool $interactive): void
|
||||||
|
{
|
||||||
|
$this->visitSender->method('sendVisitsInDateRange')->willReturn(new SendVisitsResult());
|
||||||
|
|
||||||
|
[$output] = $this->executeCommand(['y'], ['interactive' => $interactive]);
|
||||||
|
|
||||||
|
if ($interactive) {
|
||||||
|
self::assertStringContainsString('You are about to send visits', $output);
|
||||||
|
} else {
|
||||||
|
self::assertStringNotContainsString('You are about to send visits', $output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[TestWith([true])]
|
||||||
|
#[TestWith([false])]
|
||||||
|
public function canCancelExecutionInInteractiveMode(bool $interactive): void
|
||||||
|
{
|
||||||
|
$this->visitSender->expects($this->exactly($interactive ? 0 : 1))->method('sendVisitsInDateRange')->willReturn(
|
||||||
|
new SendVisitsResult(),
|
||||||
|
);
|
||||||
|
$this->executeCommand(['n'], ['interactive' => $interactive]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[TestWith([new SendVisitsResult(), 'There was no visits matching provided date range'])]
|
||||||
|
#[TestWith([new SendVisitsResult(successfulVisits: 10), '10 visits sent to Matomo.'])]
|
||||||
|
#[TestWith([new SendVisitsResult(successfulVisits: 2), '2 visits sent to Matomo.'])]
|
||||||
|
#[TestWith([new SendVisitsResult(failedVisits: 238), 'Failed to send 238 visits to Matomo.'])]
|
||||||
|
#[TestWith([new SendVisitsResult(failedVisits: 18), 'Failed to send 18 visits to Matomo.'])]
|
||||||
|
#[TestWith([new SendVisitsResult(successfulVisits: 2, failedVisits: 35), '2 visits sent to Matomo. 35 failed.'])]
|
||||||
|
#[TestWith([new SendVisitsResult(successfulVisits: 81, failedVisits: 6), '81 visits sent to Matomo. 6 failed.'])]
|
||||||
|
public function expectedResultIsDisplayed(SendVisitsResult $result, string $expectedResultMessage): void
|
||||||
|
{
|
||||||
|
$this->visitSender->expects($this->once())->method('sendVisitsInDateRange')->willReturn($result);
|
||||||
|
[$output, $exitCode] = $this->executeCommand(['y']);
|
||||||
|
|
||||||
|
self::assertStringContainsString($expectedResultMessage, $output);
|
||||||
|
self::assertEquals(ExitCode::EXIT_SUCCESS, $exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function printsResultOfSendingVisits(): void
|
||||||
|
{
|
||||||
|
$this->visitSender->method('sendVisitsInDateRange')->willReturnCallback(
|
||||||
|
function (DateRange $_, MatomoSendVisitsCommand $command): SendVisitsResult {
|
||||||
|
// Call it a few times for an easier match of its result in the command putput
|
||||||
|
$command->success(0);
|
||||||
|
$command->success(1);
|
||||||
|
$command->success(2);
|
||||||
|
$command->error(3, new Exception('Error'));
|
||||||
|
$command->success(4);
|
||||||
|
$command->error(5, new Exception('Error'));
|
||||||
|
|
||||||
|
return new SendVisitsResult();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
[$output] = $this->executeCommand(['y']);
|
||||||
|
|
||||||
|
self::assertStringContainsString('...E.E', $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[TestWith([[], 'All time'])]
|
||||||
|
#[TestWith([['--since' => '2023-05-01'], 'Since 2023-05-01 00:00:00'])]
|
||||||
|
#[TestWith([['--until' => '2023-05-01'], 'Until 2023-05-01 00:00:00'])]
|
||||||
|
#[TestWith([
|
||||||
|
['--since' => '2023-05-01', '--until' => '2024-02-02 23:59:59'],
|
||||||
|
'Between 2023-05-01 00:00:00 and 2024-02-02 23:59:59',
|
||||||
|
])]
|
||||||
|
public function providedDateAreParsed(array $args, string $expectedMessage): void
|
||||||
|
{
|
||||||
|
[$output] = $this->executeCommand(['n'], args: $args);
|
||||||
|
self::assertStringContainsString('Resolved date range -> ' . $expectedMessage, $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{string, int, MatomoSendVisitsCommand}
|
||||||
|
*/
|
||||||
|
private function executeCommand(
|
||||||
|
array $input = [],
|
||||||
|
array $options = [],
|
||||||
|
array $args = [],
|
||||||
|
bool $matomoEnabled = true,
|
||||||
|
): array {
|
||||||
|
$command = new MatomoSendVisitsCommand(new MatomoOptions(enabled: $matomoEnabled), $this->visitSender);
|
||||||
|
$commandTester = CliTestUtils::testerForCommand($command);
|
||||||
|
$commandTester->setInputs($input);
|
||||||
|
$commandTester->execute($args, $options);
|
||||||
|
|
||||||
|
$output = $commandTester->getDisplay();
|
||||||
|
$exitCode = $commandTester->getStatusCode();
|
||||||
|
|
||||||
|
return [$output, $exitCode, $command];
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,6 +61,23 @@ function parseDateRangeFromQuery(array $query, string $startDateName, string $en
|
||||||
return buildDateRange($startDate, $endDate);
|
return buildDateRange($startDate, $endDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dateRangeToHumanFriendly(?DateRange $dateRange): string
|
||||||
|
{
|
||||||
|
$startDate = $dateRange?->startDate;
|
||||||
|
$endDate = $dateRange?->endDate;
|
||||||
|
|
||||||
|
return match (true) {
|
||||||
|
$startDate !== null && $endDate !== null => sprintf(
|
||||||
|
'Between %s and %s',
|
||||||
|
$startDate->toDateTimeString(),
|
||||||
|
$endDate->toDateTimeString(),
|
||||||
|
),
|
||||||
|
$startDate !== null => sprintf('Since %s', $startDate->toDateTimeString()),
|
||||||
|
$endDate !== null => sprintf('Until %s', $endDate->toDateTimeString()),
|
||||||
|
default => 'All time',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ($date is null ? null : Chronos)
|
* @return ($date is null ? null : Chronos)
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
namespace ShlinkioTest\Shlink\Core\Matomo;
|
namespace ShlinkioTest\Shlink\Core\Matomo;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Laminas\Validator\Date;
|
|
||||||
use MatomoTracker;
|
use MatomoTracker;
|
||||||
use PHPUnit\Framework\Attributes\DataProvider;
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
use PHPUnit\Framework\Attributes\Test;
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
|
Loading…
Add table
Reference in a new issue