trip-planner/backend/tests/Feature/PlannedItemTest.php

322 lines
10 KiB
PHP
Raw Normal View History

<?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);
}
}