108 lines
3.6 KiB
PHP
108 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Repository;
|
|
|
|
use App\Entity\StatisticsEvent;
|
|
use App\Enum\StatisticsDateBasis;
|
|
use App\Enum\StatisticsEventName;
|
|
use App\Repository\StatisticsEventRepository;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Doctrine\ORM\Query;
|
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
|
|
|
/**
|
|
* Everywhere else in this app a date range means the season an assignment runs in, and
|
|
* these events are recorded months before it - an application for the coming winter
|
|
* arrives in summer. Picking the wrong basis does not fail, it quietly answers a
|
|
* different question from the screen next to it, so the compiled DQL is pinned here.
|
|
*/
|
|
class StatisticsEventRepositoryTest extends KernelTestCase
|
|
{
|
|
public function testAPeriodMeansTheSeasonUnlessToldOtherwise(): void
|
|
{
|
|
$dql = $this->query(dateFrom: new \DateTimeImmutable('2026-07-01'))->getDQL();
|
|
|
|
$this->assertStringContainsString('destination.dateFrom >= :dateFrom', $dql);
|
|
$this->assertStringNotContainsString('event.occurredAt', $dql);
|
|
}
|
|
|
|
/**
|
|
* The season is the destination's date range, the convention shared with SeasonPeriodFilter.
|
|
*/
|
|
public function testTheSeasonIsTheDestinationDateRange(): void
|
|
{
|
|
$dql = $this->query(
|
|
dateFrom: new \DateTimeImmutable('2026-07-01'),
|
|
dateTo: new \DateTimeImmutable('2027-06-30'),
|
|
)->getDQL();
|
|
|
|
$this->assertStringContainsString('destination.dateFrom >= :dateFrom', $dql);
|
|
$this->assertStringContainsString('destination.dateTo <= :dateTo', $dql);
|
|
}
|
|
|
|
/**
|
|
* The dimension columns are plain integers, not relations, so the assignment is only
|
|
* reachable through an arbitrary join.
|
|
*/
|
|
public function testTheSeasonReachesTheAssignmentThroughAnArbitraryJoin(): void
|
|
{
|
|
$dql = $this->query(dateFrom: new \DateTimeImmutable('2026-07-01'))->getDQL();
|
|
|
|
$this->assertStringContainsString('assignment.id = event.assignmentId', $dql);
|
|
}
|
|
|
|
public function testOccurrenceAsksWhenTheEventWasRecordedAndJoinsNothing(): void
|
|
{
|
|
$dql = $this->query(
|
|
dateFrom: new \DateTimeImmutable('2026-07-01'),
|
|
dateBasis: StatisticsDateBasis::OCCURRENCE,
|
|
)->getDQL();
|
|
|
|
$this->assertStringContainsString('event.occurredAt >= :dateFrom', $dql);
|
|
$this->assertStringNotContainsString('JOIN', $dql);
|
|
}
|
|
|
|
/**
|
|
* An unfiltered count spans every season there is, so there is nothing to join for.
|
|
*/
|
|
public function testNoPeriodMeansNoJoinEitherWay(): void
|
|
{
|
|
$this->assertStringNotContainsString('JOIN', $this->query()->getDQL());
|
|
}
|
|
|
|
public function testTheGroupingColumnStaysWhitelisted(): void
|
|
{
|
|
$this->expectException(\InvalidArgumentException::class);
|
|
|
|
$this->query(dimension: 'payload');
|
|
}
|
|
|
|
private function query(
|
|
string $dimension = 'hotelCode',
|
|
?\DateTimeImmutable $dateFrom = null,
|
|
?\DateTimeImmutable $dateTo = null,
|
|
StatisticsDateBasis $dateBasis = StatisticsDateBasis::SEASON,
|
|
): Query {
|
|
return $this->repository()->getCountGroupedByQuery(
|
|
StatisticsEventName::APPLICATION_CREATED,
|
|
$dimension,
|
|
$dateFrom,
|
|
$dateTo,
|
|
$dateBasis,
|
|
);
|
|
}
|
|
|
|
private function repository(): StatisticsEventRepository
|
|
{
|
|
/** @var EntityManagerInterface $entityManager */
|
|
$entityManager = static::getContainer()->get(EntityManagerInterface::class);
|
|
|
|
/** @var StatisticsEventRepository $repository */
|
|
$repository = $entityManager->getRepository(StatisticsEvent::class);
|
|
|
|
return $repository;
|
|
}
|
|
}
|