getRowData()); $this->assertEquals(11886, $row->busProId); $this->assertEquals('LPJPGJ040426', $row->code); $this->assertEquals('Lenzerheide - Sportclub Jenatsch', $row->product); $this->assertEquals('2026-04-04', $row->dateFrom->format('Y-m-d')); $this->assertEquals('2026-04-11', $row->dateTo->format('Y-m-d')); $this->assertEquals(163959, $row->hotelBusProId); $this->assertEquals('LPJGR', $row->hotelCode); $this->assertEquals('Sportclub Jenatsch', $row->hotelName); $this->assertEquals('4711', $row->costUnit); $this->assertEquals('ch', $row->country); } public function testTheIsoCountryCodeIsStoredRatherThanTheRawToken(): void { $row = DestinationRow::fromArray($this->getRowData([ 'hotel' => ['busProId' => 1, 'country' => 'AUT', 'countryCode' => 'AT'], ])); $this->assertEquals('at', $row->country); } public function testAnUnresolvedCountryCodeIsStoredAsNull(): void { $row = DestinationRow::fromArray($this->getRowData([ 'hotel' => ['busProId' => 1, 'country' => 'SLO'], ])); $this->assertNull($row->country); } public function testTheDatesAreReadAsWholeDays(): void { $row = DestinationRow::fromArray($this->getRowData()); $this->assertEquals('00:00:00', $row->dateFrom->format('H:i:s')); $this->assertEquals('00:00:00', $row->dateTo->format('H:i:s')); } public function testPickupsKeepTheShapeTheDestinationTableStores(): void { $row = DestinationRow::fromArray($this->getRowData()); $this->assertEquals([[ 'busProId' => 4, 'city' => 'Karlsruhe', 'street' => 'A5 - Autohof Bruchsal', 'time' => '23:59', ]], $row->pickups); } public function testStoredPickupsCanBeReadBackAsModels(): void { $row = DestinationRow::fromArray($this->getRowData()); $pickup = Pickup::fromArray(json_decode(json_encode($row->pickups), true)[0]); $this->assertEquals(4, $pickup->getId()); $this->assertEquals('Karlsruhe', $pickup->getCity()); $this->assertEquals('23:59', $pickup->getTime()); } public function testPickupsWithoutAnIdAreDropped(): void { $row = DestinationRow::fromArray($this->getRowData(['pickups' => [ ['city' => 'Karlsruhe'], ['busProId' => 0, 'city' => 'Köln'], ['busProId' => 27, 'city' => 'Bonn', 'street' => 'ZOB', 'time' => '02:15'], ]])); $this->assertCount(1, $row->pickups); $this->assertEquals(27, $row->pickups[0]['busProId']); } public function testARowWithoutPickupsIsAccepted(): void { $row = DestinationRow::fromArray($this->getRowData(['pickups' => []])); $this->assertEquals([], $row->pickups); } public function testARowWithoutAHotelIsRejected(): void { $this->expectException(InvalidRowException::class); DestinationRow::fromArray($this->getRowData(['hotel' => null])); } public function testARowWithoutIdsIsRejected(): void { $this->expectException(InvalidRowException::class); DestinationRow::fromArray($this->getRowData(['busProId' => 0])); } public function testARowWithAnUnreadableDateIsRejected(): void { $this->expectException(InvalidRowException::class); DestinationRow::fromArray($this->getRowData(['dateFrom' => '04.04.2026'])); } public function testTheInsertPayloadCarriesEveryColumn(): void { $row = DestinationRow::fromArray($this->getRowData()); $payload = $row->toInsertPayload(new \DateTimeImmutable('2026-08-13 03:00:00')); $this->assertEquals([ 'code' => 'LPJPGJ040426', 'bus_pro_id' => 11886, 'date_from' => '2026-04-04', 'date_to' => '2026-04-11', 'product' => 'Lenzerheide - Sportclub Jenatsch', 'hotel' => 'Sportclub Jenatsch', 'hotel_code' => 'LPJGR', 'hotel_bus_pro_id' => 163959, 'pickups' => '[{"busProId":4,"city":"Karlsruhe","street":"A5 - Autohof Bruchsal","time":"23:59"}]', 'cost_unit' => '4711', 'country' => 'ch', 'created_at' => '2026-08-13 03:00:00', ], $payload); } public function testTheUpdatePayloadRefreshesTheHotelName(): void { // a renamed house has to reach existing rows, or it drops out of the house matching $row = DestinationRow::fromArray($this->getRowData()); $payload = $row->toUpdatePayload(new \DateTimeImmutable('2026-08-13 03:00:00')); $this->assertEquals('Sportclub Jenatsch', $payload['hotel']); $this->assertEquals('LPJGR', $payload['hotel_code']); $this->assertEquals('2026-08-13 03:00:00', $payload['updated_at']); $this->assertArrayNotHasKey('created_at', $payload); $this->assertArrayNotHasKey('bus_pro_id', $payload); $this->assertArrayNotHasKey('hotel_bus_pro_id', $payload); } private function getRowData(array $overrides = []): array { return array_merge([ 'busProId' => 11886, 'code' => 'LPJPGJ040426', 'product' => 'Lenzerheide - Sportclub Jenatsch', 'dateFrom' => '2026-04-04', 'dateTo' => '2026-04-11', 'hotel' => [ 'busProId' => 163959, 'code' => 'LPJGR', 'name' => 'Sportclub Jenatsch', 'costUnit' => '4711', 'country' => 'CH', 'countryCode' => 'ch', ], 'pickups' => [[ 'busProId' => 4, 'code' => '298', 'city' => 'Karlsruhe', 'street' => 'A5 - Autohof Bruchsal', 'time' => '23:59', 'datetime' => '2026-04-04T23:59:00+02:00', ]], ], $overrides); } }