Reviewed-on: https://codeberg.org/lvl0/trip-planner/pulls/34 Co-authored-by: myrmidex <myrmidex@myrmidex.net> Co-committed-by: myrmidex <myrmidex@myrmidex.net>
321 lines
10 KiB
PHP
321 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\User;
|
|
use App\Models\Trip;
|
|
use App\Models\PlannableItem;
|
|
use App\Models\CalendarSlot;
|
|
use App\Models\PlannedItem;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class PlannedItemTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $user;
|
|
private Trip $trip;
|
|
private PlannableItem $plannableItem;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->user = User::factory()->create();
|
|
$this->trip = Trip::factory()->create([
|
|
'created_by_user_id' => $this->user->id,
|
|
'start_date' => '2025-01-15',
|
|
'end_date' => '2025-01-17',
|
|
]);
|
|
$this->plannableItem = PlannableItem::factory()->create([
|
|
'trip_id' => $this->trip->id,
|
|
'name' => 'Eiffel Tower',
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_creates_calendar_slot_when_scheduling_item_with_datetime()
|
|
{
|
|
$response = $this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'trip_id' => $this->trip->id,
|
|
'start_datetime' => '2025-01-15 14:00:00',
|
|
'end_datetime' => '2025-01-15 16:00:00',
|
|
]);
|
|
|
|
$response->assertStatus(201);
|
|
$this->assertDatabaseHas('calendar_slots', [
|
|
'trip_id' => $this->trip->id,
|
|
'name' => 'Eiffel Tower',
|
|
'slot_date' => '2025-01-15',
|
|
]);
|
|
$this->assertDatabaseHas('planned_items', [
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_calculates_slot_order_correctly()
|
|
{
|
|
// Create first slot at 10:00
|
|
$this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'trip_id' => $this->trip->id,
|
|
'start_datetime' => '2025-01-15 10:00:00',
|
|
'end_datetime' => '2025-01-15 11:00:00',
|
|
]);
|
|
|
|
// Create another plannable item
|
|
$secondItem = PlannableItem::factory()->create([
|
|
'trip_id' => $this->trip->id,
|
|
'name' => 'Louvre',
|
|
]);
|
|
|
|
// Create second slot at 14:00
|
|
$this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
'plannable_item_id' => $secondItem->id,
|
|
'trip_id' => $this->trip->id,
|
|
'start_datetime' => '2025-01-15 14:00:00',
|
|
'end_datetime' => '2025-01-15 16:00:00',
|
|
]);
|
|
|
|
// Create third slot at 08:00 (should have slot_order 0 after recalculation)
|
|
$thirdItem = PlannableItem::factory()->create([
|
|
'trip_id' => $this->trip->id,
|
|
'name' => 'Breakfast',
|
|
]);
|
|
|
|
$this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
'plannable_item_id' => $thirdItem->id,
|
|
'trip_id' => $this->trip->id,
|
|
'start_datetime' => '2025-01-15 08:00:00',
|
|
'end_datetime' => '2025-01-15 09:00:00',
|
|
]);
|
|
|
|
$slots = CalendarSlot::where('trip_id', $this->trip->id)
|
|
->where('slot_date', '2025-01-15')
|
|
->orderBy('slot_order')
|
|
->get();
|
|
|
|
// After recalculation, order should be by datetime_start
|
|
$this->assertEquals('Breakfast', $slots[0]->name);
|
|
$this->assertEquals(0, $slots[0]->slot_order);
|
|
$this->assertEquals('Eiffel Tower', $slots[1]->name);
|
|
$this->assertEquals(1, $slots[1]->slot_order);
|
|
$this->assertEquals('Louvre', $slots[2]->name);
|
|
$this->assertEquals(2, $slots[2]->slot_order);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_validates_end_time_is_after_start_time()
|
|
{
|
|
$response = $this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'trip_id' => $this->trip->id,
|
|
'start_datetime' => '2025-01-15 16:00:00',
|
|
'end_datetime' => '2025-01-15 14:00:00',
|
|
]);
|
|
|
|
$response->assertStatus(422);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_requires_plannable_item_to_belong_to_trip()
|
|
{
|
|
$otherTrip = Trip::factory()->create([
|
|
'created_by_user_id' => $this->user->id,
|
|
]);
|
|
$otherItem = PlannableItem::factory()->create([
|
|
'trip_id' => $otherTrip->id,
|
|
]);
|
|
|
|
$response = $this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
'plannable_item_id' => $otherItem->id,
|
|
'trip_id' => $this->trip->id,
|
|
'start_datetime' => '2025-01-15 14:00:00',
|
|
'end_datetime' => '2025-01-15 16:00:00',
|
|
]);
|
|
|
|
$response->assertStatus(422);
|
|
$response->assertJson([
|
|
'error' => 'Plannable item does not belong to this trip'
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_supports_backward_compatible_calendar_slot_id_flow()
|
|
{
|
|
$calendarSlot = CalendarSlot::factory()->create([
|
|
'trip_id' => $this->trip->id,
|
|
]);
|
|
|
|
$response = $this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'calendar_slot_id' => $calendarSlot->id,
|
|
]);
|
|
|
|
$response->assertStatus(201);
|
|
$this->assertDatabaseHas('planned_items', [
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'calendar_slot_id' => $calendarSlot->id,
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_requires_authentication_to_create_planned_item()
|
|
{
|
|
$response = $this->postJson('/api/planned-items', [
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'trip_id' => $this->trip->id,
|
|
'start_datetime' => '2025-01-15 14:00:00',
|
|
'end_datetime' => '2025-01-15 16:00:00',
|
|
]);
|
|
|
|
$response->assertStatus(401);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_validates_required_fields()
|
|
{
|
|
$response = $this->actingAs($this->user)->postJson('/api/planned-items', []);
|
|
|
|
$response->assertStatus(422);
|
|
$response->assertJsonValidationErrors(['plannable_item_id', 'trip_id', 'start_datetime', 'end_datetime']);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_validates_plannable_item_exists()
|
|
{
|
|
$response = $this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
'plannable_item_id' => 99999,
|
|
'trip_id' => $this->trip->id,
|
|
'start_datetime' => '2025-01-15 14:00:00',
|
|
'end_datetime' => '2025-01-15 16:00:00',
|
|
]);
|
|
|
|
$response->assertStatus(422);
|
|
$response->assertJsonValidationErrors(['plannable_item_id']);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_validates_trip_exists()
|
|
{
|
|
$response = $this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'trip_id' => 99999,
|
|
'start_datetime' => '2025-01-15 14:00:00',
|
|
'end_datetime' => '2025-01-15 16:00:00',
|
|
]);
|
|
|
|
$response->assertStatus(422);
|
|
$response->assertJsonValidationErrors(['trip_id']);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_returns_calendar_slot_with_relationships()
|
|
{
|
|
$response = $this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'trip_id' => $this->trip->id,
|
|
'start_datetime' => '2025-01-15 14:00:00',
|
|
'end_datetime' => '2025-01-15 16:00:00',
|
|
]);
|
|
|
|
$response->assertStatus(201);
|
|
$response->assertJsonStructure([
|
|
'id',
|
|
'trip_id',
|
|
'name',
|
|
'datetime_start',
|
|
'datetime_end',
|
|
'slot_date',
|
|
'slot_order',
|
|
'planned_items' => [
|
|
'*' => [
|
|
'id',
|
|
'plannable_item_id',
|
|
'calendar_slot_id',
|
|
'plannable_item' => [
|
|
'id',
|
|
'name',
|
|
'trip_id',
|
|
]
|
|
]
|
|
]
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_can_update_planned_item_slot()
|
|
{
|
|
$calendarSlot1 = CalendarSlot::factory()->create(['trip_id' => $this->trip->id]);
|
|
$calendarSlot2 = CalendarSlot::factory()->create(['trip_id' => $this->trip->id]);
|
|
|
|
$plannedItem = PlannedItem::create([
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'calendar_slot_id' => $calendarSlot1->id,
|
|
'sort_order' => 0,
|
|
]);
|
|
|
|
$response = $this->actingAs($this->user)->putJson("/api/planned-items/{$plannedItem->id}", [
|
|
'calendar_slot_id' => $calendarSlot2->id,
|
|
'sort_order' => 5,
|
|
]);
|
|
|
|
$response->assertStatus(200);
|
|
$this->assertDatabaseHas('planned_items', [
|
|
'id' => $plannedItem->id,
|
|
'calendar_slot_id' => $calendarSlot2->id,
|
|
'sort_order' => 5,
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_can_delete_planned_item()
|
|
{
|
|
$calendarSlot = CalendarSlot::factory()->create(['trip_id' => $this->trip->id]);
|
|
$plannedItem = PlannedItem::create([
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'calendar_slot_id' => $calendarSlot->id,
|
|
'sort_order' => 0,
|
|
]);
|
|
|
|
$response = $this->actingAs($this->user)->deleteJson("/api/planned-items/{$plannedItem->id}");
|
|
|
|
$response->assertStatus(204);
|
|
$this->assertDatabaseMissing('planned_items', [
|
|
'id' => $plannedItem->id,
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_requires_authentication_to_update_planned_item()
|
|
{
|
|
$calendarSlot = CalendarSlot::factory()->create(['trip_id' => $this->trip->id]);
|
|
$plannedItem = PlannedItem::create([
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'calendar_slot_id' => $calendarSlot->id,
|
|
]);
|
|
|
|
$response = $this->putJson("/api/planned-items/{$plannedItem->id}", [
|
|
'sort_order' => 1,
|
|
]);
|
|
|
|
$response->assertStatus(401);
|
|
}
|
|
|
|
/** @test */
|
|
public function it_requires_authentication_to_delete_planned_item()
|
|
{
|
|
$calendarSlot = CalendarSlot::factory()->create(['trip_id' => $this->trip->id]);
|
|
$plannedItem = PlannedItem::create([
|
|
'plannable_item_id' => $this->plannableItem->id,
|
|
'calendar_slot_id' => $calendarSlot->id,
|
|
]);
|
|
|
|
$response = $this->deleteJson("/api/planned-items/{$plannedItem->id}");
|
|
|
|
$response->assertStatus(401);
|
|
}
|
|
}
|