register(PaymentMethod::KbzMiniApp, FakeFilamentRefundGateway::class); }); test('a user with view_payments can list refunds', function () { $viewer = User::factory()->create()->givePermissionTo('view_payments'); $this->actingAs($viewer); $refunds = Refund::factory()->count(3)->create(); Livewire::test(ListRefunds::class) ->assertOk() ->assertCanSeeTableRecords($refunds); }); test('the process action is hidden from a user without process_refunds', function () { $viewer = User::factory()->create()->givePermissionTo('view_payments'); $this->actingAs($viewer); Livewire::test(ListRefunds::class) ->assertActionHidden('process'); }); test('the process action is visible to a user with process_refunds', function () { $admin = User::factory()->create()->givePermissionTo(['view_payments', 'process_refunds']); $this->actingAs($admin); Livewire::test(ListRefunds::class) ->assertActionVisible('process'); }); test('processing a refund via the action calls RefundBookingAction and cancels the booking', function () { $admin = User::factory()->create()->givePermissionTo(['view_payments', 'process_refunds']); $this->actingAs($admin); $booking = Booking::factory()->create(['status' => BookingStatus::Confirmed, 'price' => 15000]); $payment = Payment::factory()->completed()->create([ 'booking_id' => $booking->id, 'gateway' => PaymentMethod::KbzMiniApp, 'amount' => 15000, 'gateway_transaction_id' => 'EVB-FILAMENT-REFUND-1', ]); Livewire::test(ListRefunds::class) ->callAction('process', data: [ 'payment_id' => $payment->id, 'amount' => 15000, 'reason' => 'customer requested cancellation', ]) ->assertNotified(); expect($booking->refresh()->status)->toBe(BookingStatus::Cancelled) ->and(Refund::where('payment_id', $payment->id)->where('status', RefundStatus::Completed)->exists())->toBeTrue(); }); test('a non-completed payment is not offered in the process action\'s payment select', function () { $admin = User::factory()->create()->givePermissionTo(['view_payments', 'process_refunds']); $this->actingAs($admin); $booking = Booking::factory()->create(['status' => BookingStatus::PendingPayment]); $pendingPayment = Payment::factory()->create([ 'booking_id' => $booking->id, 'gateway' => PaymentMethod::KbzMiniApp, ]); // The Select itself rejects a value outside its "Completed only" // options (domain.md §6) — RefundBookingAction's own guard against a // non-confirmed booking is covered directly in RefundBookingActionTest. Livewire::test(ListRefunds::class) ->callAction('process', data: [ 'payment_id' => $pendingPayment->id, 'amount' => 1000, 'reason' => 'reason', ]) ->assertHasFormErrors(['payment_id']); expect(Refund::where('payment_id', $pendingPayment->id)->exists())->toBeFalse(); });