feat: import destinations from the MyE&P travel export

This commit is contained in:
fromme committed 2026-09-29 12:57:54 +02:00
1 parent 5c7e702eab
commit 0f76c5a7b6
15 files changed
+791 -422

No files matched your search

+24 -1
View File
@@ -60,9 +60,32 @@ class DestinationRowTest extends TestCase
'city' => 'Karlsruhe',
'street' => 'A5 - Autohof Bruchsal',
'time' => '23:59',
'datetime' => '2026-04-04T23:59:00+02:00',
]], $row->pickups);
}
/**
* MyE&P sends `time` as a bare HH:MM, so this is the only field that says which calendar day
* a boarding point belongs to. Dropping it left App\Service\Assignment\PickupDepartureOrder
* with no anchor and a naive clock sort, which puts a 00:30 stop before the 22:00 departure.
*/
public function testTheResolvedDepartureTimestampIsCarried(): void
{
$row = DestinationRow::fromArray($this->getRowData());
$this->assertSame('2026-04-04T23:59:00+02:00', $row->pickups[0]['datetime']);
}
public function testAPickupWithoutAResolvedTimestampIsStillCarried(): void
{
$row = DestinationRow::fromArray($this->getRowData(['pickups' => [
['busProId' => 27, 'city' => 'Ulm', 'street' => 'Busparkplatz', 'time' => null],
]]));
$this->assertNull($row->pickups[0]['datetime']);
$this->assertCount(1, $row->pickups);
}
public function testStoredPickupsCanBeReadBackAsModels(): void
{
$row = DestinationRow::fromArray($this->getRowData());
@@ -129,7 +152,7 @@ class DestinationRowTest extends TestCase
'hotel' => 'Sportclub Jenatsch',
'hotel_code' => 'LPJGR',
'hotel_bus_pro_id' => 163959,
'pickups' => '[{"busProId":4,"city":"Karlsruhe","street":"A5 - Autohof Bruchsal","time":"23:59"}]',
'pickups' => '[{"busProId":4,"city":"Karlsruhe","street":"A5 - Autohof Bruchsal","time":"23:59","datetime":"2026-04-04T23:59:00+02:00"}]',
'cost_unit' => '4711',
'country' => 'ch',
'created_at' => '2026-08-13 03:00:00',
+65 -5
View File
@@ -21,7 +21,7 @@ class TravelExportClientTest extends TestCase
$this->page([$this->row(3, 300)], 2, 2),
]);
$rows = $this->getClientInstance($httpClient)->fetchAll();
$rows = $this->getClientInstance($httpClient)->fetchAll()->rows;
$this->assertCount(3, $rows);
$this->assertEquals([1, 2, 3], array_map(fn ($row) => $row->busProId, $rows));
@@ -62,7 +62,7 @@ class TravelExportClientTest extends TestCase
], 1, 1),
]);
$rows = $this->getClientInstance($httpClient)->fetchAll();
$rows = $this->getClientInstance($httpClient)->fetchAll()->rows;
$this->assertCount(1, $rows);
$this->assertEquals(2, $rows[0]->busProId);
@@ -72,7 +72,7 @@ class TravelExportClientTest extends TestCase
{
$httpClient = $this->mockClient([$this->page([], 1, 1)]);
$this->assertEquals([], $this->getClientInstance($httpClient)->fetchAll());
$this->assertEquals([], $this->getClientInstance($httpClient)->fetchAll()->rows);
}
public function testAPayloadWithoutADataListFails(): void
@@ -97,14 +97,71 @@ class TravelExportClientTest extends TestCase
'code' => 'AAA',
'name' => 'Haus',
'costUnit' => null,
'country' => 'ch',
'country' => 'CH',
'countryCode' => 'ch',
],
'pickups' => [],
];
}
private function page(array $rows, int $page, int $totalPages): MockResponse
/**
* Both counters are losses MyE&P made on its side - a dropped travel, a boarding point with no
* town - and the import is the only place they surface, so the client has to carry them out.
*/
public function testTheCountersMyEpReportsAreCarriedOut(): void
{
$httpClient = $this->mockClient([$this->page([$this->row(1, 100)], 1, 1, skipped: 3, pickupsWithoutMasterData: 21)]);
$export = $this->getClientInstance($httpClient)->fetchAll();
$this->assertSame(3, $export->skipped);
$this->assertSame(21, $export->pickupsWithoutMasterData);
$this->assertSame('2026-09-29T03:20:00+02:00', $export->generatedAt);
}
/**
* They describe the whole export rather than the page, so they repeat unchanged and the first
* page answers for the run. Reading the last one instead would be just as correct today and
* wrong the moment a page is served from a newer build.
*/
public function testTheCountersAreReadFromTheFirstPage(): void
{
$httpClient = $this->mockClient([
$this->page([$this->row(1, 100)], 1, 2, skipped: 3, pickupsWithoutMasterData: 21),
$this->page([$this->row(2, 200)], 2, 2, skipped: 99, pickupsWithoutMasterData: 99),
]);
$export = $this->getClientInstance($httpClient)->fetchAll();
$this->assertSame(3, $export->skipped);
$this->assertSame(21, $export->pickupsWithoutMasterData);
}
/**
* An export whose meta block says nothing must not read as one that dropped nothing by
* accident of a missing key, but zero is the only honest default available here.
*/
public function testAMetaBlockWithoutCountersReadsAsZero(): void
{
$httpClient = new MockHttpClient(
[new MockResponse(json_encode(['data' => [$this->row(1, 100)], 'meta' => ['totalPages' => 1]]))],
'https://myep.example/',
);
$export = $this->getClientInstance($httpClient)->fetchAll();
$this->assertSame(0, $export->skipped);
$this->assertSame(0, $export->pickupsWithoutMasterData);
$this->assertNull($export->generatedAt);
}
private function page(
array $rows,
int $page,
int $totalPages,
int $skipped = 0,
int $pickupsWithoutMasterData = 0,
): MockResponse {
return new MockResponse(json_encode([
'data' => $rows,
'meta' => [
@@ -112,6 +169,9 @@ class TravelExportClientTest extends TestCase
'perPage' => 500,
'total' => count($rows),
'totalPages' => $totalPages,
'skipped' => $skipped,
'pickupsWithoutMasterData' => $pickupsWithoutMasterData,
'generatedAt' => '2026-09-29T03:20:00+02:00',
],
]));
}