create(); $timeSlot = DepartureTimeSlot::factory()->create(); $booking = Booking::factory()->create([ 'ev_route_id' => $route->id, 'departure_time_slot_id' => $timeSlot->id, ]); expect($booking->route)->toBeInstanceOf(EvRoute::class) ->and($booking->route->is($route))->toBeTrue() ->and($booking->timeSlot)->toBeInstanceOf(DepartureTimeSlot::class) ->and($booking->timeSlot->is($timeSlot))->toBeTrue(); }); test('booking_ref is unique', function () { Booking::factory()->create(['booking_ref' => 'EVB-DUPLICATE']); expect(fn () => Booking::factory()->create(['booking_ref' => 'EVB-DUPLICATE'])) ->toThrow(QueryException::class); }); test('status and created_by_channel cast to their enums', function () { $booking = Booking::factory()->create([ 'status' => BookingStatus::Confirmed, 'created_by_channel' => BookingChannel::Android, ]); expect($booking->status)->toBe(BookingStatus::Confirmed) ->and($booking->created_by_channel)->toBe(BookingChannel::Android); }); test('a booking defaults to pending_payment', function () { $booking = Booking::factory()->create(); expect($booking->status)->toBe(BookingStatus::PendingPayment); }); test('a booking can have multiple vehicle option lines, e.g. front seat and back seat together', function () { $booking = Booking::factory()->create(); BookingVehicleOption::factory()->create([ 'booking_id' => $booking->id, 'vehicle_option' => VehicleOption::FrontSeat, 'passenger_count' => 1, 'unit_price' => 12000, 'line_total' => 12000, ]); BookingVehicleOption::factory()->create([ 'booking_id' => $booking->id, 'vehicle_option' => VehicleOption::BackSeat, 'passenger_count' => 2, 'unit_price' => 9000, 'line_total' => 18000, ]); expect($booking->vehicleOptions)->toHaveCount(2) ->and($booking->vehicleOptions->pluck('vehicle_option')->map(fn ($option) => $option->value)->sort()->values()->all()) ->toEqual(['back_seat', 'front_seat']); }); test('a vehicle option line cannot be duplicated on the same booking', function () { $booking = Booking::factory()->create(); BookingVehicleOption::factory()->create([ 'booking_id' => $booking->id, 'vehicle_option' => VehicleOption::BackSeat, ]); expect(fn () => BookingVehicleOption::factory()->create([ 'booking_id' => $booking->id, 'vehicle_option' => VehicleOption::BackSeat, ]))->toThrow(QueryException::class); }); test('price is snapshotted onto the booking and does not change when RoutePricing is edited later', function () { $route = EvRoute::factory()->create(); $pricing = RoutePricing::factory()->create([ 'ev_route_id' => $route->id, 'vehicle_option' => VehicleOption::BackSeat, 'price' => 15000, ]); $booking = Booking::factory()->create([ 'ev_route_id' => $route->id, 'price' => $pricing->price, ]); BookingVehicleOption::factory()->create([ 'booking_id' => $booking->id, 'vehicle_option' => VehicleOption::BackSeat, 'unit_price' => $pricing->price, 'line_total' => $pricing->price, ]); $pricing->update(['price' => 25000]); expect($booking->refresh()->price)->toEqual('15000.00') ->and($booking->vehicleOptions()->first()->unit_price)->toEqual('15000.00') ->and($pricing->refresh()->price)->toEqual('25000.00'); }); test('a booking can have a user or be guest-checked-out via mini app openid', function () { $guestBooking = Booking::factory()->create([ 'user_id' => null, 'openid' => 'mini-app-openid-123', ]); expect($guestBooking->user_id)->toBeNull() ->and($guestBooking->openid)->toBe('mini-app-openid-123'); });