Files
myep/tests/Command/BpnSyncContingentsCommandTest.php
T

183 lines
7.0 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Command;
use App\Command\BpnSyncContingentsCommand;
use App\Entity\Groups\Accommodation;
use App\Entity\Groups\CalendarHotel;
use App\Model\ContingentHotel;
use App\Model\ContingentSyncResult;
use App\Repository\Groups\ContingentDayRepository;
use App\Repository\Groups\ContingentSyncStateRepository;
use App\Service\ContingentHotelDirectory;
use App\Service\ContingentSnapshotManager;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Tester\CommandTester;
/**
* The exit code drives zenstruck's failure mail, and this task runs every 15 minutes — so what
* counts as "failed" is a deliberate decision, not an implementation detail.
*/
class BpnSyncContingentsCommandTest extends TestCase
{
public function testTotalFailureFailsTheTask(): void
{
$tester = $this->runSync(['A', 'B'], [
ContingentSyncResult::failed('upstream down'),
ContingentSyncResult::failed('upstream down'),
]);
self::assertSame(Command::FAILURE, $tester->getStatusCode());
}
public function testASingleFailingHotelDoesNotFailTheTask(): void
{
$tester = $this->runSync(['A', 'B', 'C'], [
ContingentSyncResult::synced(true, 3, 0, 0, 0),
ContingentSyncResult::failed('unknown hotel code'),
ContingentSyncResult::synced(false, 0, 0, 0, 0),
]);
self::assertSame(Command::SUCCESS, $tester->getStatusCode());
self::assertStringContainsString('B', $tester->getDisplay());
self::assertStringContainsString('unknown hotel code', $tester->getDisplay());
}
public function testASuccessfulRunSucceeds(): void
{
$tester = $this->runSync(['A'], [ContingentSyncResult::synced(true, 3, 0, 1, 0)]);
self::assertSame(Command::SUCCESS, $tester->getStatusCode());
}
public function testARunWithNoHotelsSucceeds(): void
{
$tester = $this->runSync([], []);
self::assertSame(Command::SUCCESS, $tester->getStatusCode());
}
public function testSyncsAccommodationAndTypo3CodesAndDropsTheRest(): void
{
$manager = $this->createMock(ContingentSnapshotManager::class);
$synced = [];
$manager->expects(self::exactly(2))->method('sync')
->willReturnCallback(static function (string $code) use (&$synced): ContingentSyncResult {
$synced[] = $code;
return ContingentSyncResult::synced(false, 0, 0, 0, 0);
});
$dayRepository = $this->createMock(ContingentDayRepository::class);
$dayRepository->expects(self::once())->method('deleteExceptHotelCodes')->with(['DGS', 'DKI']);
$syncStateRepository = $this->createMock(ContingentSyncStateRepository::class);
$syncStateRepository->expects(self::once())->method('deleteExceptHotelCodes')->with(['DGS', 'DKI']);
$this->runSync(
['DGS' => 'accommodation', 'DKI' => 'typo3'],
manager: $manager,
dayRepository: $dayRepository,
syncStateRepository: $syncStateRepository,
);
self::assertSame(['DGS', 'DKI'], $synced);
}
public function testASingleHotelRunAcceptsATypo3OnlyCodeAndDropsNothing(): void
{
$manager = $this->createMock(ContingentSnapshotManager::class);
$manager->expects(self::once())->method('sync')
->with('DKI', self::anything(), self::anything())
->willReturn(ContingentSyncResult::synced(false, 0, 0, 0, 0));
$dayRepository = $this->createMock(ContingentDayRepository::class);
$dayRepository->expects(self::never())->method('deleteExceptHotelCodes');
$syncStateRepository = $this->createMock(ContingentSyncStateRepository::class);
$syncStateRepository->expects(self::never())->method('deleteExceptHotelCodes');
$tester = $this->runSync(
['DGS' => 'accommodation', 'DKI' => 'typo3'],
manager: $manager,
dayRepository: $dayRepository,
syncStateRepository: $syncStateRepository,
options: ['--hotel' => 'DKI'],
);
self::assertSame(Command::SUCCESS, $tester->getStatusCode());
}
public function testASingleHotelRunIgnoresAnUnknownCode(): void
{
$manager = $this->createMock(ContingentSnapshotManager::class);
$manager->expects(self::never())->method('sync');
$tester = $this->runSync(['DGS' => 'accommodation'], manager: $manager, options: ['--hotel' => 'NOPE']);
self::assertSame(Command::SUCCESS, $tester->getStatusCode());
self::assertStringContainsString('No accommodation calendarCode or TYPO3 calendar hotel matched', $tester->getDisplay());
}
/**
* @param array<int|string, string> $hotels codes, or code => 'accommodation'|'typo3'
* @param ContingentSyncResult[] $results
* @param array<string, string> $options
*/
private function runSync(
array $hotels,
array $results = [],
?ContingentSnapshotManager $manager = null,
?ContingentDayRepository $dayRepository = null,
?ContingentSyncStateRepository $syncStateRepository = null,
array $options = [],
): CommandTester {
if (array_is_list($hotels)) {
$hotels = array_fill_keys($hotels, 'accommodation');
}
$directoryEntries = [];
foreach ($hotels as $code => $source) {
$code = (string) $code;
$directoryEntries[$code] = 'typo3' === $source
? new ContingentHotel($code, null, (new CalendarHotel())->setCode($code)->setName($code))
: new ContingentHotel($code, (new Accommodation())->setCalendarCode($code), null);
}
$hotelDirectory = $this->createStub(ContingentHotelDirectory::class);
$hotelDirectory->method('all')->willReturn($directoryEntries);
if (null === $manager) {
$manager = $this->createStub(ContingentSnapshotManager::class);
if ([] !== $results) {
$manager->method('sync')->willReturnOnConsecutiveCalls(...$results);
}
}
$command = new BpnSyncContingentsCommand(
$hotelDirectory,
$syncStateRepository ?? $this->createStub(ContingentSyncStateRepository::class),
$dayRepository ?? $this->createStub(ContingentDayRepository::class),
$manager,
new NullLogger(),
);
$application = new Application();
$application->addCommand($command);
$tester = new CommandTester($application->find('app:bpn:sync-contingents'));
$tester->execute(['--horizon-months' => '1'] + $options);
// LockableTrait's lock lives for the life of the process, so without releasing it every
// run after the first would exit early and these assertions would pass for the wrong reason.
$release = new \ReflectionMethod($command, 'release');
$release->invoke($command);
return $tester;
}
}