Files
famous-ly4-ev/app-modules/booking/tests/Feature/BookingTest.php
T
Nyan Lin Paing 5b68f4fa38 Add Booking module: model, create/read/cancel API, Filament resource (T4.1-T4.7)
- Booking model with booking_vehicle_options line items (supports mixing
  vehicle options like front_seat + back_seat in one booking), price
  snapshot, status machine, and driver/car assignment fields
- BookingService: front-seat max, disabled-option toggles, duplicate-option
  and whole-vehicle-exclusivity guards
- CreateBookingAction, CancelBookingAction, AssignDriverAction
- BookingRefGenerator: sequential EVB-AAAAA1-style refs via row lock
- POST/GET/cancel booking API endpoints (Sanctum, ownership + admin policy)
- BookingPlugin + Filament BookingResource: list, detail view, Cancel and
  Assign Driver actions (shared between table and detail page)
- domain.md updated for multi-vehicle-option bookings (§2) and driver/
  vehicle assignment (§5a)
2026-08-08 21:43:15 +07:00

125 lines
4.3 KiB
PHP

<?php
use Illuminate\Database\QueryException;
use Modules\Booking\Enums\BookingChannel;
use Modules\Booking\Enums\BookingStatus;
use Modules\Booking\Models\Booking;
use Modules\Booking\Models\BookingVehicleOption;
use Modules\Catalog\Models\DepartureTimeSlot;
use Modules\Routing\Models\EvRoute;
use Modules\Routing\Models\RoutePricing;
use Modules\Shared\Enums\VehicleOption;
test('a booking belongs to a route and a time slot', function () {
$route = EvRoute::factory()->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');
});