148 lines
5.1 KiB
PHP
148 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Command;
|
|
|
|
use App\BusProNet\DataProvider\HotelDataProvider;
|
|
use App\BusProNet\DataProvider\PickupDataProvider;
|
|
use App\BusProNet\Model\Hotel;
|
|
use App\Command\BpnImportCommand;
|
|
use Doctrine\DBAL\Connection;
|
|
use League\Flysystem\FilesystemOperator;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Psr\Log\NullLogger;
|
|
use Symfony\Component\Console\Command\Command;
|
|
use Symfony\Component\Console\Tester\CommandTester;
|
|
|
|
/**
|
|
* The import writes a destination's cost unit and country from the hotel base data, on
|
|
* update as well as on insert, so it can only run once MyE&P reports both fields. Until it
|
|
* does, a run would blank them on the 2,206 destinations that carry a cost unit and the
|
|
* 2,259 that carry a country - silently, because nothing reads them back until an invoice
|
|
* is stamped. These tests pin the refusal, and that it happens before any write.
|
|
*/
|
|
class BpnImportCommandTest extends TestCase
|
|
{
|
|
public function testItRefusesToRunWhenNoHotelReportsACostUnit(): void
|
|
{
|
|
$tester = $this->runImport([$this->hotel(costUnit: null, countryCode: 'ch')]);
|
|
|
|
$this->assertSame(Command::FAILURE, $tester->getStatusCode());
|
|
$this->assertStringContainsString('Kostenstellen', $tester->getDisplay());
|
|
$this->assertStringNotContainsString('Länderkennungen', $tester->getDisplay());
|
|
}
|
|
|
|
public function testItRefusesToRunWhenNoHotelReportsACountryCode(): void
|
|
{
|
|
$tester = $this->runImport([$this->hotel(costUnit: '4711', countryCode: null)]);
|
|
|
|
$this->assertSame(Command::FAILURE, $tester->getStatusCode());
|
|
$this->assertStringContainsString('Länderkennungen', $tester->getDisplay());
|
|
}
|
|
|
|
public function testItNamesBothFieldsWhenNeitherIsReported(): void
|
|
{
|
|
$tester = $this->runImport([$this->hotel(costUnit: null, countryCode: null)]);
|
|
|
|
$this->assertSame(Command::FAILURE, $tester->getStatusCode());
|
|
$this->assertStringContainsString('Kostenstellen und Länderkennungen', $tester->getDisplay());
|
|
}
|
|
|
|
public function testAnEmptyStringDoesNotCountAsAReportedField(): void
|
|
{
|
|
$tester = $this->runImport([$this->hotel(costUnit: '', countryCode: '')]);
|
|
|
|
$this->assertSame(Command::FAILURE, $tester->getStatusCode());
|
|
$this->assertStringContainsString('Kostenstellen und Länderkennungen', $tester->getDisplay());
|
|
}
|
|
|
|
public function testOneHotelCarryingAFieldIsEnoughToPassTheGuard(): void
|
|
{
|
|
// most hotels legitimately have no kostenstelle upstream, so the guard asks whether
|
|
// the field is reported at all, not whether every hotel fills it
|
|
$tester = $this->runImport([
|
|
$this->hotel(costUnit: null, countryCode: null),
|
|
$this->hotel(costUnit: '4711', countryCode: 'ch'),
|
|
]);
|
|
|
|
$display = $tester->getDisplay();
|
|
|
|
$this->assertStringNotContainsString('Kostenstellen', $display);
|
|
$this->assertStringNotContainsString('Länderkennungen', $display);
|
|
}
|
|
|
|
public function testItRefusesToRunWithoutAnyHotelBaseData(): void
|
|
{
|
|
$tester = $this->runImport([]);
|
|
|
|
$this->assertSame(Command::FAILURE, $tester->getStatusCode());
|
|
$this->assertStringContainsString('Hotel-Stammdaten', $tester->getDisplay());
|
|
}
|
|
|
|
/**
|
|
* @param list<Hotel> $hotels
|
|
*/
|
|
private function runImport(array $hotels): CommandTester
|
|
{
|
|
$keyed = [];
|
|
|
|
foreach ($hotels as $index => $hotel) {
|
|
$keyed[$index + 1] = $hotel;
|
|
}
|
|
|
|
$tester = new CommandTester(new BpnImportCommand(
|
|
$this->refusingConnection(),
|
|
$this->provider(HotelDataProvider::class, $keyed),
|
|
$this->provider(PickupDataProvider::class, []),
|
|
new NullLogger(),
|
|
// an auto stubbed listing, so it yields no files. The guards under test all run
|
|
// before it, and the one case that gets past them stops there without writing.
|
|
$this->createMock(FilesystemOperator::class),
|
|
));
|
|
|
|
$tester->execute([]);
|
|
|
|
return $tester;
|
|
}
|
|
|
|
/**
|
|
* @template T of HotelDataProvider|PickupDataProvider
|
|
*
|
|
* @param class-string<T> $class
|
|
*
|
|
* @return T&MockObject
|
|
*/
|
|
private function provider(string $class, array $all): object
|
|
{
|
|
$provider = $this->createMock($class);
|
|
$provider->method('getAll')->willReturn($all);
|
|
|
|
return $provider;
|
|
}
|
|
|
|
/**
|
|
* Any query at all means the guard let a write through, which is the whole failure this
|
|
* covers, so the connection fails the test rather than the command.
|
|
*/
|
|
private function refusingConnection(): Connection&MockObject
|
|
{
|
|
$connection = $this->createMock(Connection::class);
|
|
$connection->expects($this->never())->method($this->anything());
|
|
|
|
return $connection;
|
|
}
|
|
|
|
private function hotel(?string $costUnit, ?string $countryCode): Hotel
|
|
{
|
|
return (new Hotel())
|
|
->setBusProId(163959)
|
|
->setCode('LPJGR')
|
|
->setName('Sportclub Jenatsch')
|
|
->setCostUnit($costUnit)
|
|
->setCountryCode($countryCode)
|
|
;
|
|
}
|
|
}
|