Files
myep/tests/BusProNet/XmlParser/TravelParserTest.php
T

462 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Tests\BusProNet\XmlParser;
use App\BusProNet\XmlCrawlerFactory;
use App\BusProNet\XmlParser\TravelParser;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
class TravelParserTest extends TestCase
{
private TravelParser $parser;
protected function setUp(): void
{
$this->parser = new TravelParser();
}
public function testParseServiceDescriptions(): void
{
$xmlContent = '<?xml version="1.0" encoding="utf-8"?>
<reisen>
<reise id="1" idbuspro="2187" code="SSTMR" erstellt_am="27.08.2025 09:09:40">
<termin id="1" idbuspro="11946" idprodukt="2187" termin="01.01.2030" bis="06.01.2030" code="SSTMR010130" reiseart="F">
<text>Test Travel</text>
<abpreis>659,00</abpreis>
<lei_sonstiges>
<leistung id="1" idbuspro="183658" unterart="SON" termin="01.01.2030" bis="06.01.2030" automatisch_buchen="False" pflicht="False">
<text>Begleitperson (gemäß Gruppenregelung)</text>
<hinweis>Gilt für Betreuende, Lehrkräfte, Gruppenleitungen oder andere berechtigte Begleitpersonen laut Buchungsregelung. Bei Unklarheiten bitte zunächst ohne Angabe buchen eine spätere Ergänzung ist möglich.</hinweis>
<preis>0,00</preis>
<status>Frei</status>
</leistung>
<leistung id="2" idbuspro="183659" unterart="SON" termin="01.01.2030" bis="06.01.2030" automatisch_buchen="True" pflicht="False">
<text>Bettwäsche-Set inkl. Handtuch</text>
<hinweis>Das Bettwäsche-Set umfasst Spannbettlaken, Kissen- und Deckenbezug. Beim Handtuch handelt es sich um ein Duschtuch (ca. 70x140cm).</hinweis>
<preis>13,90</preis>
<status>Frei</status>
</leistung>
<leistung id="3" idbuspro="183656" unterart="SPA" termin="01.01.2030" bis="06.01.2030" automatisch_buchen="False" pflicht="False">
<text>Skipass 6 Tage (Erwachsene) (Jahrgang 1926-2006) inkl. Anreisetag</text>
<hinweis_stamm>JG:1926-2006</hinweis_stamm>
<preis>59,00</preis>
<status>Frei</status>
</leistung>
<leistung id="4" idbuspro="183660" unterart="VPF" termin="01.01.2030" bis="01.01.2030" automatisch_buchen="False" pflicht="False">
<text>Frühstück am Anreisetag</text>
<preis>8,90</preis>
<status>Frei</status>
</leistung>
</lei_sonstiges>
<lei_befoerderung>
<leistung id="5" idbuspro="183649" unterart="BUS" termin="30.12.2029" bis="01.01.2030" automatisch_buchen="False" pflicht="True">
<text>Bus-Hinfahrt</text>
<hinweis>Bus fährt nur bei ausreichender Teilnehmerzahl</hinweis>
<preis>0,00</preis>
<status>Frei</status>
<richtung>HIN</richtung>
</leistung>
</lei_befoerderung>
<hotel id="1" code="SSTGR" idbuspro="157047" ankunft="01.01.2030" abreise="06.01.2030">
<zimmer>
<preis zimmercode="4erDW" idbuspro_zimmer="95" zimmertext="4er Zimmer Dusche/WC" MinPax="4" MaxPax="4" naechte="5" kat="GR" vpcode="Lt. Auss." idbuspro_vp="2" vptext="Laut Ausschreibung" preis="689,00" status="Frei" verfuegbar="8" />
</zimmer>
</hotel>
</termin>
</reise>
</reisen>';
$crawler = XmlCrawlerFactory::create($xmlContent);
$travelNode = $crawler->filterXPath('//reise/termin')->first();
$travel = $this->parser->parse($travelNode);
// Test additional services descriptions
$additionalServices = $travel->additionalServices;
// Service with description (ID 183658 - Begleitperson)
$this->assertArrayHasKey(183658, $additionalServices);
$begleitpersonService = $additionalServices[183658];
$this->assertSame('Begleitperson (gemäß Gruppenregelung)', $begleitpersonService->label);
$this->assertSame(
'Gilt für Betreuende, Lehrkräfte, Gruppenleitungen oder andere berechtigte Begleitpersonen laut Buchungsregelung. Bei Unklarheiten bitte zunächst ohne Angabe buchen eine spätere Ergänzung ist möglich.',
$begleitpersonService->description
);
// Service with description (ID 183659 - Bettwäsche)
$this->assertArrayHasKey(183659, $additionalServices);
$bettwascheService = $additionalServices[183659];
$this->assertSame('Bettwäsche-Set inkl. Handtuch', $bettwascheService->label);
$this->assertSame(
'Das Bettwäsche-Set umfasst Spannbettlaken, Kissen- und Deckenbezug. Beim Handtuch handelt es sich um ein Duschtuch (ca. 70x140cm).',
$bettwascheService->description
);
$this->assertTrue($bettwascheService->autoBook);
// Service without description (ID 183656 - Skipass, has hinweis_stamm but no hinweis)
$this->assertArrayHasKey(183656, $additionalServices);
$skipassService = $additionalServices[183656];
$this->assertSame('Skipass 6 Tage (Erwachsene) (Jahrgang 1926-2006) inkl. Anreisetag', $skipassService->label);
$this->assertNull($skipassService->description); // No hinweis node for this service
$this->assertSame('JG:1926-2006', $skipassService->rawAgeConstraintData); // But has age constraints
// Service without description (ID 183660 - Frühstück)
$this->assertArrayHasKey(183660, $additionalServices);
$fruehstueckService = $additionalServices[183660];
$this->assertSame('Frühstück am Anreisetag', $fruehstueckService->label);
$this->assertNull($fruehstueckService->description); // No hinweis node
// Test transportation services descriptions
$transportationServices = $travel->transportationServices;
$this->assertArrayHasKey(183649, $transportationServices);
$busService = $transportationServices[183649];
$this->assertSame('Bus-Hinfahrt', $busService->label);
$this->assertSame('Bus fährt nur bei ausreichender Teilnehmerzahl', $busService->description);
$this->assertFalse($busService->autoBook);
$this->assertSame(2187, $travel->productId);
}
public function testProductIdIsNullWhenAttributeAbsent(): void
{
$xmlContent = '<?xml version="1.0" encoding="utf-8"?>
<reisen>
<reise id="1" idbuspro="2187" code="SSTMR">
<termin id="1" idbuspro="11946" termin="01.01.2030" bis="06.01.2030" reiseart="F">
<text>Test Travel</text>
<abpreis>659,00</abpreis>
<hotel id="1" idbuspro="157047">
<zimmer>
<preis zimmercode="4erDW" idbuspro_zimmer="95" zimmertext="4er Zimmer" MinPax="4" MaxPax="4" naechte="5" preis="689,00" status="Frei" verfuegbar="8" />
</zimmer>
</hotel>
</termin>
</reise>
</reisen>';
$crawler = XmlCrawlerFactory::create($xmlContent);
$travelNode = $crawler->filterXPath('//reise/termin')->first();
$travel = $this->parser->parse($travelNode);
$this->assertNull($travel->productId);
}
public function testParseServiceWithoutDescriptions(): void
{
$xmlContent = '<?xml version="1.0" encoding="utf-8"?>
<reisen>
<reise id="1" idbuspro="2187" code="SSTMR">
<termin id="1" idbuspro="11946" termin="01.01.2030" bis="06.01.2030" reiseart="F">
<text>Test Travel</text>
<abpreis>659,00</abpreis>
<lei_sonstiges>
<leistung id="1" idbuspro="183660" unterart="VPF">
<text>Service without description</text>
<preis>8,90</preis>
<status>Frei</status>
</leistung>
</lei_sonstiges>
<hotel id="1" idbuspro="157047">
<zimmer>
<preis zimmercode="4erDW" idbuspro_zimmer="95" zimmertext="4er Zimmer" MinPax="4" MaxPax="4" naechte="5" preis="689,00" status="Frei" verfuegbar="8" />
</zimmer>
</hotel>
</termin>
</reise>
</reisen>';
$crawler = XmlCrawlerFactory::create($xmlContent);
$travelNode = $crawler->filterXPath('//reise/termin')->first();
$travel = $this->parser->parse($travelNode);
$additionalServices = $travel->additionalServices;
$this->assertArrayHasKey(183660, $additionalServices);
$service = $additionalServices[183660];
$this->assertSame('Service without description', $service->label);
$this->assertNull($service->description); // No hinweis node should result in null description
}
public function testParseServiceWithLowerCaseAutoBookFlag(): void
{
$xmlContent = '<?xml version="1.0" encoding="utf-8"?>
<reisen>
<reise id="1" idbuspro="2187" code="SSTMR">
<termin id="1" idbuspro="11946" termin="01.01.2030" bis="06.01.2030" reiseart="F">
<text>Test Travel</text>
<abpreis>659,00</abpreis>
<lei_sonstiges>
<leistung id="1" idbuspro="183660" unterart="VPF" automatisch_buchen="true" pflicht="False">
<text>Service with auto-book</text>
<preis>8,90</preis>
<status>Frei</status>
</leistung>
</lei_sonstiges>
<hotel id="1" idbuspro="157047">
<zimmer>
<preis zimmercode="4erDW" idbuspro_zimmer="95" zimmertext="4er Zimmer" MinPax="4" MaxPax="4" naechte="5" preis="689,00" status="Frei" verfuegbar="8" />
</zimmer>
</hotel>
</termin>
</reise>
</reisen>';
$crawler = XmlCrawlerFactory::create($xmlContent);
$travelNode = $crawler->filterXPath('//reise/termin')->first();
$travel = $this->parser->parse($travelNode);
$additionalServices = $travel->additionalServices;
$this->assertArrayHasKey(183660, $additionalServices);
$this->assertTrue($additionalServices[183660]->autoBook);
}
public function testParseTravelWithoutHotelNode(): void
{
$xmlContent = '<?xml version="1.0" encoding="utf-8"?>
<reisen>
<reise id="1" idbuspro="2293" code="">
<termin id="1" idbuspro="12162" idprodukt="2293" termin="28.03.2026" bis="04.04.2026" code="PGTSGI280326" reiseart="P">
<text>PG TSG Irlich</text>
<abpreis>-50,00</abpreis>
<lei_sonstiges>
<leistung id="1" idbuspro="188858" unterart="SON" termin="28.03.2026" bis="04.04.2026" automatisch_buchen="False" pflicht="False">
<text>7 Übernachtungen im 3* Posthotel</text>
<preis>999,00</preis>
<status>Frei</status>
</leistung>
</lei_sonstiges>
<lei_befoerderung>
<leistung id="1" idbuspro="188850" unterart="BUS" termin="27.03.2026" bis="28.03.2026" automatisch_buchen="False" pflicht="False">
<text>Bus-Hinfahrt</text>
<preis>5,00</preis>
<status>Frei</status>
<richtung>HIN</richtung>
</leistung>
</lei_befoerderung>
<zustiege>
<zustieg id="1" idbuspro="873" zeit="27.03.2026 21:30:00" />
</zustiege>
</termin>
</reise>
</reisen>';
$crawler = XmlCrawlerFactory::create($xmlContent);
$travelNode = $crawler->filterXPath('//reise/termin')->first();
$travel = $this->parser->parse($travelNode);
$this->assertSame(12162, $travel->id);
$this->assertSame('PGTSGI280326', $travel->code);
$this->assertNull($travel->hotelId);
$this->assertSame([], $travel->rooms);
$this->assertArrayHasKey(188858, $travel->additionalServices);
$this->assertArrayHasKey(188850, $travel->transportationServices);
$this->assertArrayHasKey(873, $travel->pickups);
}
public function testParseAdditionalServicesUsesTerminLevelOnly(): void
{
$xmlContent = '<?xml version="1.0" encoding="utf-8"?>
<reisen>
<reise id="1" idbuspro="2187" code="SSTMR">
<termin id="1" idbuspro="11946" termin="01.01.2030" bis="06.01.2030" reiseart="F">
<text>Test Travel</text>
<abpreis>659,00</abpreis>
<lei_sonstiges>
<leistung id="1" idbuspro="183656" unterart="SPA" automatisch_buchen="True" pflicht="False">
<text>Skipass 6 Tage</text>
<preis>59,00</preis>
<status>Frei</status>
</leistung>
</lei_sonstiges>
<hotel id="1" idbuspro="157047">
<lei_sonstiges>
<leistung id="1" idbuspro="183656" unterart="SPA" automatisch_buchen="False" pflicht="False">
<text>Skipass 6 Tage Hotel</text>
<preis>59,00</preis>
<status>Frei</status>
</leistung>
</lei_sonstiges>
<zimmer>
<preis zimmercode="4erDW" idbuspro_zimmer="95" zimmertext="4er Zimmer" MinPax="4" MaxPax="4" naechte="5" preis="689,00" status="Frei" verfuegbar="8" />
</zimmer>
</hotel>
</termin>
</reise>
</reisen>';
$crawler = XmlCrawlerFactory::create($xmlContent);
$travelNode = $crawler->filterXPath('//reise/termin')->first();
$travel = $this->parser->parse($travelNode);
$additionalServices = $travel->additionalServices;
$this->assertArrayHasKey(183656, $additionalServices);
$this->assertTrue($additionalServices[183656]->autoBook);
$this->assertSame('Skipass 6 Tage', $additionalServices[183656]->label);
}
public function testParseServiceWithEmptyDescription(): void
{
$xmlContent = '<?xml version="1.0" encoding="utf-8"?>
<reisen>
<reise id="1" idbuspro="2187" code="SSTMR">
<termin id="1" idbuspro="11946" termin="01.01.2030" bis="06.01.2030" reiseart="F">
<text>Test Travel</text>
<abpreis>659,00</abpreis>
<lei_sonstiges>
<leistung id="1" idbuspro="183660" unterart="VPF">
<text>Service with empty description</text>
<hinweis></hinweis>
<preis>8,90</preis>
<status>Frei</status>
</leistung>
</lei_sonstiges>
<hotel id="1" idbuspro="157047">
<zimmer>
<preis zimmercode="4erDW" idbuspro_zimmer="95" zimmertext="4er Zimmer" MinPax="4" MaxPax="4" naechte="5" preis="689,00" status="Frei" verfuegbar="8" />
</zimmer>
</hotel>
</termin>
</reise>
</reisen>';
$crawler = XmlCrawlerFactory::create($xmlContent);
$travelNode = $crawler->filterXPath('//reise/termin')->first();
$travel = $this->parser->parse($travelNode);
$additionalServices = $travel->additionalServices;
$this->assertArrayHasKey(183660, $additionalServices);
$service = $additionalServices[183660];
$this->assertSame('Service with empty description', $service->label);
$this->assertNull($service->description); // Empty hinweis should result in null description
}
public function testParseTravelWithoutStatus(): void
{
$xmlContent = '<?xml version="1.0" encoding="utf-8"?>
<reisen>
<reise id="1" idbuspro="2187" code="SSTMR">
<termin id="1" idbuspro="11946" termin="01.01.2030" bis="06.01.2030" reiseart="F">
<text>Test Travel</text>
<abpreis>659,00</abpreis>
<hotel id="1" idbuspro="157047">
<zimmer>
<preis zimmercode="4erDW" idbuspro_zimmer="95" zimmertext="4er Zimmer" MinPax="4" MaxPax="4" naechte="5" preis="689,00" status="Frei" verfuegbar="8" />
</zimmer>
</hotel>
</termin>
</reise>
</reisen>';
$crawler = XmlCrawlerFactory::create($xmlContent);
$travelNode = $crawler->filterXPath('//reise/termin')->first();
$travel = $this->parser->parse($travelNode);
$this->assertNull($travel->status); // No status_hin node should result in null
}
public function testGetPickupsAdjustsOvernightTimes(): void
{
$xml = '<?xml version="1.0" encoding="utf-8"?>
<zustiege>
<zustieg idbuspro="1" zeit="30.12.2029 18:00" preis="0,00" />
<zustieg idbuspro="2" zeit="30.12.2029 20:30" preis="0,00" />
<zustieg idbuspro="3" zeit="23:00" preis="0,00" />
<zustieg idbuspro="4" zeit="01:00" preis="0,00" />
</zustiege>';
$crawler = XmlCrawlerFactory::create($xml);
$defaultDate = new \DateTimeImmutable('2029-12-30');
$pickups = $this->parser->getPickups(
$crawler->filterXPath('//zustiege/zustieg'),
$defaultDate,
true
);
// Full-datetime entries keep their original date
$this->assertSame('2029-12-30 18:00', $pickups[1]->time->format('Y-m-d H:i'));
$this->assertSame('2029-12-30 20:30', $pickups[2]->time->format('Y-m-d H:i'));
// Time-only "23:00" is after latest full-datetime (20:30), so stays on same day
$this->assertSame('2029-12-30 23:00', $pickups[3]->time->format('Y-m-d H:i'));
// Time-only "01:00" is before latest full-datetime (20:30), so gets +1 day
$this->assertSame('2029-12-31 01:00', $pickups[4]->time->format('Y-m-d H:i'));
}
public function testGetPickupsDoesNotAdjustWhenAllEntriesAreFullDatetime(): void
{
$xml = '<?xml version="1.0" encoding="utf-8"?>
<zustiege>
<zustieg idbuspro="1" zeit="30.12.2029 18:00" preis="0,00" />
<zustieg idbuspro="2" zeit="30.12.2029 20:30" preis="0,00" />
</zustiege>';
$crawler = XmlCrawlerFactory::create($xml);
$defaultDate = new \DateTimeImmutable('2029-12-30');
$pickups = $this->parser->getPickups(
$crawler->filterXPath('//zustiege/zustieg'),
$defaultDate,
true
);
$this->assertSame('2029-12-30 18:00', $pickups[1]->time->format('Y-m-d H:i'));
$this->assertSame('2029-12-30 20:30', $pickups[2]->time->format('Y-m-d H:i'));
}
public function testGetPickupsDoesNotAdjustWhenAllEntriesAreTimeOnly(): void
{
$xml = '<?xml version="1.0" encoding="utf-8"?>
<zustiege>
<zustieg idbuspro="1" zeit="18:00" preis="0,00" />
<zustieg idbuspro="2" zeit="01:00" preis="0,00" />
</zustiege>';
$crawler = XmlCrawlerFactory::create($xml);
$defaultDate = new \DateTimeImmutable('2029-12-30');
$pickups = $this->parser->getPickups(
$crawler->filterXPath('//zustiege/zustieg'),
$defaultDate,
true
);
// No full-datetime reference entries, so no adjustment happens
$this->assertSame('2029-12-30 18:00', $pickups[1]->time->format('Y-m-d H:i'));
$this->assertSame('2029-12-30 01:00', $pickups[2]->time->format('Y-m-d H:i'));
}
/**
* The travel export writes MinPax/MaxPax capitalized while booking responses write them lowercase.
* XmlCrawlerFactory uses addXmlContent(), which makes attribute lookups case-sensitive, so both
* spellings have to resolve.
*/
#[DataProvider('roomOccupancyAttributeProvider')]
public function testGetRoomsReadsOccupancyRegardlessOfAttributeCasing(string $attributes, ?int $expectedPax): void
{
$xml = sprintf('<?xml version="1.0" encoding="utf-8"?>
<zimmer>
<preis zimmercode="4erDW" idbuspro_zimmer="95" zimmertext="4er Zimmer Dusche/WC" %s naechte="5" idbuspro_vp="2" preis="689,00" status="Frei" verfuegbar="8" />
</zimmer>', $attributes);
$crawler = XmlCrawlerFactory::create($xml);
$rooms = $this->parser->getRooms($crawler->filterXPath('//zimmer')->first());
$this->assertArrayHasKey(95, $rooms);
$this->assertSame($expectedPax, $rooms[95]->minPax);
$this->assertSame($expectedPax, $rooms[95]->maxPax);
}
/**
* @return iterable<string, array{string, int|null}>
*/
public static function roomOccupancyAttributeProvider(): iterable
{
yield 'export casing' => ['MinPax="4" MaxPax="4"', 4];
yield 'lowercase casing' => ['minpax="4" maxpax="4"', 4];
yield 'missing stays null' => ['', null];
}
}