Files
paperbenni d4eaa45baa some vibe tests
better than none
2026-10-30 10:20:59 +01:00

182 lines
9.1 KiB
PHP

<?php
namespace Tests\Feature;
use App\Booking;
use App\PersonTarget;
use App\SubBooking;
use Tests\DatabaseTestCase;
class BookingsTest extends DatabaseTestCase
{
protected function payload(array $attributes = [])
{
return array_replace([
'date' => '2020-02-15', 'reference' => 'Membership payment',
'person_or_company' => 'Test company', 'note' => 'Test note', 'amount' => '125.50',
'account_type_id' => $this->incomeAccount->id, 'project_id' => $this->project->id,
'person_target_id' => null, 'personTarget' => ['person_id' => null, 'date' => null],
], $attributes);
}
public function test_user_can_create_a_booking_with_its_account_and_project()
{
$this->actingAs($this->user)->post('/bookings', $this->payload())
->assertRedirect('/bookings/year-2020')->assertSessionHasNoErrors();
$booking = Booking::where('reference', 'Membership payment')->firstOrFail();
$this->assertSame('2020-02-15', $booking->date);
$this->assertSame('Test company', $booking->person_or_company);
$this->assertSame('Test note', $booking->note);
$this->assertCount(1, $booking->subBookings);
$this->assertDatabaseHas('sub_bookings', [
'booking_id' => $booking->id, 'amount' => 125.50, 'reference' => 'Membership payment',
'account_type_id' => $this->incomeAccount->id, 'project_id' => $this->project->id,
]);
}
public function test_booking_forms_render_with_account_project_and_target_choices()
{
$target = $this->target();
$booking = $this->booking('2020-01-15', 100, null, ['person_target_id' => $target->id]);
$this->actingAs($this->user)->get('/bookings/create')->assertStatus(200)
->assertSee($this->project->name)->assertSee($this->incomeAccount->name);
// A fully paid target must still be selectable when editing its booking.
$this->get('/bookings/' . $booking->id . '/edit')->assertStatus(200)
->assertSee($target->reference)->assertSee($booking->reference);
$this->get('/bookings/' . $booking->id . '/delete')->assertStatus(200);
}
/** @dataProvider invalidBookings */
public function test_invalid_bookings_do_not_write_any_records($field, $value)
{
$this->actingAs($this->user)->from('/bookings/create')
->post('/bookings', $this->payload([$field => $value]))
->assertRedirect('/bookings/create')->assertSessionHasErrors($field);
$this->assertSame(0, Booking::count());
$this->assertSame(0, SubBooking::count());
}
public function invalidBookings()
{
return [
['date', ''], ['reference', ''], ['person_or_company', ''],
['amount', ''], ['amount', 'not money'], ['amount', '-10'], ['account_type_id', ''],
];
}
public function test_booking_can_pay_an_existing_target()
{
$target = $this->target(125.50);
$this->actingAs($this->user)->post('/bookings', $this->payload(['person_target_id' => $target->id]))
->assertRedirect('/bookings/year-2020')->assertSessionHasNoErrors();
$this->assertDatabaseHas('sub_bookings', ['person_target_id' => $target->id, 'amount' => 125.50]);
$this->assertTrue($target->fresh()->isPaid());
$this->assertSame(1, PersonTarget::count());
}
public function test_booking_can_create_a_target_using_the_booking_date_by_default()
{
$person = $this->target()->person;
$this->actingAs($this->user)->post('/bookings', $this->payload([
'personTarget' => ['person_id' => $person->id, 'date' => null],
]))->assertRedirect('/bookings/year-2020')->assertSessionHasNoErrors();
$target = SubBooking::firstOrFail()->personTarget;
$this->assertSame($person->id, $target->person_id);
$this->assertSame('2020-02-15', $target->date);
$this->assertEquals(125.50, $target->amount);
$this->assertTrue($target->isPaid());
}
public function test_user_cannot_choose_an_existing_target_and_create_one_together()
{
$target = $this->target();
$this->actingAs($this->user)->post('/bookings', $this->payload([
'person_target_id' => $target->id,
'personTarget' => ['person_id' => $target->person_id, 'date' => '2020-01-01'],
]))->assertSessionHasErrors('person_target_id');
$this->assertSame(0, Booking::count());
$this->assertSame(1, PersonTarget::count());
}
public function test_user_can_update_booking_and_split_it_into_income_and_expense_lines()
{
$booking = $this->booking('2019-12-31', 100);
$line = $booking->subBookings->first();
$this->actingAs($this->user)->patch('/bookings/' . $booking->id, [
'booking' => ['date' => '2020-01-15', 'reference' => 'Corrected', 'person_or_company' => 'Updated company'],
'sub_booking' => [
$line->id => ['reference' => 'Income', 'amount' => '120.50', 'account_type_id' => $this->incomeAccount->id],
0 => ['reference' => 'Fee', 'amount' => '20.25', 'account_type_id' => $this->expenseAccount->id],
],
])->assertRedirect('/bookings/year-2020')->assertSessionHasNoErrors();
$booking = $booking->fresh();
$this->assertSame('Corrected', $booking->reference);
$this->assertSame('2020-01-15', $booking->date);
$this->assertSame('Updated company', $booking->person_or_company);
$this->assertCount(2, $booking->subBookings);
$this->assertEquals(100.25, $booking->signedAmount());
$this->assertDatabaseHas('sub_bookings', ['id' => $line->id, 'amount' => 120.50]);
$this->assertDatabaseHas('sub_bookings', ['booking_id' => $booking->id, 'reference' => 'Fee', 'amount' => 20.25]);
}
public function test_invalid_update_leaves_booking_and_lines_unchanged()
{
$booking = $this->booking('2020-01-15', 100);
$line = $booking->subBookings->first();
$this->actingAs($this->user)->patch('/bookings/' . $booking->id, [
'booking' => ['date' => '2020-02-01', 'reference' => 'Changed', 'person_or_company' => 'Company'],
'sub_booking' => [$line->id => ['reference' => 'Invalid', 'amount' => -1, 'account_type_id' => $this->incomeAccount->id]],
])->assertSessionHasErrors('sub_booking.' . $line->id . '.amount');
$this->assertSame('2020-01-15', $booking->fresh()->date);
$this->assertEquals(100, $line->fresh()->amount);
}
public function test_empty_line_is_removed_while_other_lines_are_kept()
{
$booking = $this->booking('2020-01-15', 100);
$line = $booking->subBookings->first();
$this->actingAs($this->user)->patch('/bookings/' . $booking->id, [
'booking' => ['date' => $booking->date, 'reference' => $booking->reference, 'person_or_company' => $booking->person_or_company],
'sub_booking' => [
$line->id => ['reference' => null, 'amount' => null, 'account_type_id' => null],
0 => ['reference' => 'Replacement', 'amount' => 75, 'account_type_id' => $this->incomeAccount->id],
],
])->assertRedirect('/bookings/year-2020')->assertSessionHasNoErrors();
$this->assertDatabaseMissing('sub_bookings', ['id' => $line->id]);
$this->assertCount(1, $booking->fresh()->subBookings);
$this->assertEquals(75, $booking->fresh()->signedAmount());
}
public function test_deleting_booking_cascades_to_its_lines_but_keeps_target_and_other_bookings()
{
$target = $this->target();
$booking = $this->booking('2020-01-15', 100, null, ['person_target_id' => $target->id]);
$other = $this->booking('2020-02-15', 50);
$this->actingAs($this->user)->delete('/bookings/' . $booking->id, ['date' => $booking->date])
->assertRedirect('/bookings/year-2020');
$this->assertDatabaseMissing('bookings', ['id' => $booking->id]);
$this->assertDatabaseMissing('sub_bookings', ['booking_id' => $booking->id]);
$this->assertDatabaseHas('bookings', ['id' => $other->id]);
$this->assertDatabaseHas('person_targets', ['id' => $target->id]);
$this->assertEquals(100, $target->fresh()->difference());
}
public function test_clearing_all_lines_redirects_to_delete_confirmation_without_deleting()
{
$booking = $this->booking('2020-01-15', 100);
$line = $booking->subBookings->first();
$this->actingAs($this->user)->patch('/bookings/' . $booking->id, [
'booking' => ['date' => $booking->date, 'reference' => $booking->reference, 'person_or_company' => $booking->person_or_company],
'sub_booking' => [$line->id => ['reference' => null, 'amount' => null, 'account_type_id' => null]],
])->assertRedirect('/bookings/' . $booking->id . '/delete')->assertSessionHasNoErrors();
$this->assertDatabaseHas('bookings', ['id' => $booking->id]);
$this->assertDatabaseHas('sub_bookings', ['id' => $line->id, 'amount' => 100]);
}
public function test_unknown_booking_returns_not_found()
{
$this->actingAs($this->user)->get('/bookings/999999/edit')->assertStatus(404);
}
}