2025-09-28 18:48:44 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Tests\Feature;
|
|
|
|
|
|
|
|
|
|
use Tests\TestCase;
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
use App\Models\Trip;
|
|
|
|
|
use App\Models\CalendarSlot;
|
|
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
|
|
|
|
|
|
class CalendarSlotTest extends TestCase
|
|
|
|
|
{
|
|
|
|
|
use RefreshDatabase;
|
|
|
|
|
|
|
|
|
|
private $user;
|
|
|
|
|
private $token;
|
|
|
|
|
|
|
|
|
|
protected function setUp(): void
|
|
|
|
|
{
|
|
|
|
|
parent::setUp();
|
|
|
|
|
|
|
|
|
|
$this->user = User::factory()->create();
|
|
|
|
|
$this->token = $this->user->createToken('test-token')->plainTextToken;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_calendar_slots_are_auto_created_when_trip_is_created()
|
|
|
|
|
{
|
|
|
|
|
$tripData = [
|
|
|
|
|
'name' => 'Test Trip',
|
|
|
|
|
'description' => 'Test Description',
|
|
|
|
|
'start_date' => '2024-01-01',
|
|
|
|
|
'end_date' => '2024-01-03'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$response = $this->withHeaders([
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
|
|
|
'Accept' => 'application/json'
|
|
|
|
|
])->postJson('/api/trips', $tripData);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(201);
|
|
|
|
|
$tripId = $response->json('data.id');
|
|
|
|
|
|
2025-09-30 08:29:17 +02:00
|
|
|
// Check that 3 calendar slots were created for this trip (Jan 1, 2, 3)
|
|
|
|
|
$this->assertEquals(3, CalendarSlot::where('trip_id', $tripId)->count());
|
2025-09-28 18:48:44 +02:00
|
|
|
|
|
|
|
|
$slots = CalendarSlot::where('trip_id', $tripId)
|
|
|
|
|
->orderBy('slot_order')
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
$this->assertEquals('Day 1', $slots[0]->name);
|
|
|
|
|
$this->assertEquals('2024-01-01', $slots[0]->slot_date->format('Y-m-d'));
|
|
|
|
|
$this->assertEquals(1, $slots[0]->slot_order);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals('Day 2', $slots[1]->name);
|
|
|
|
|
$this->assertEquals('2024-01-02', $slots[1]->slot_date->format('Y-m-d'));
|
|
|
|
|
$this->assertEquals(2, $slots[1]->slot_order);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals('Day 3', $slots[2]->name);
|
|
|
|
|
$this->assertEquals('2024-01-03', $slots[2]->slot_date->format('Y-m-d'));
|
|
|
|
|
$this->assertEquals(3, $slots[2]->slot_order);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_calendar_slots_are_updated_when_trip_dates_change()
|
|
|
|
|
{
|
|
|
|
|
$trip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'start_date' => '2024-01-01',
|
|
|
|
|
'end_date' => '2024-01-02'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Initially should have 2 slots
|
|
|
|
|
$this->assertCount(2, $trip->calendarSlots);
|
|
|
|
|
|
|
|
|
|
// Update trip dates
|
|
|
|
|
$response = $this->withHeaders([
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
|
|
|
'Accept' => 'application/json'
|
|
|
|
|
])->putJson("/api/trips/{$trip->id}", [
|
|
|
|
|
'name' => $trip->name,
|
|
|
|
|
'start_date' => '2024-02-01',
|
|
|
|
|
'end_date' => '2024-02-04'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(200);
|
|
|
|
|
|
|
|
|
|
// Should now have 4 slots with new dates
|
|
|
|
|
$trip->refresh();
|
|
|
|
|
$slots = $trip->calendarSlots()->orderBy('slot_order')->get();
|
|
|
|
|
|
|
|
|
|
$this->assertCount(4, $slots);
|
|
|
|
|
$this->assertEquals('2024-02-01', $slots[0]->slot_date->format('Y-m-d'));
|
|
|
|
|
$this->assertEquals('2024-02-02', $slots[1]->slot_date->format('Y-m-d'));
|
|
|
|
|
$this->assertEquals('2024-02-03', $slots[2]->slot_date->format('Y-m-d'));
|
|
|
|
|
$this->assertEquals('2024-02-04', $slots[3]->slot_date->format('Y-m-d'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_can_list_calendar_slots_for_trip()
|
|
|
|
|
{
|
|
|
|
|
$trip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'start_date' => '2024-01-01',
|
|
|
|
|
'end_date' => '2024-01-03'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = $this->withHeaders([
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
|
|
|
'Accept' => 'application/json'
|
|
|
|
|
])->getJson("/api/trips/{$trip->id}/calendar-slots");
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(200)
|
|
|
|
|
->assertJsonCount(3, 'data')
|
|
|
|
|
->assertJsonPath('data.0.name', 'Day 1')
|
|
|
|
|
->assertJsonPath('data.1.name', 'Day 2')
|
|
|
|
|
->assertJsonPath('data.2.name', 'Day 3');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_can_update_calendar_slot_name()
|
|
|
|
|
{
|
|
|
|
|
$trip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'start_date' => '2024-01-01',
|
|
|
|
|
'end_date' => '2024-01-01'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$slot = $trip->calendarSlots()->first();
|
|
|
|
|
|
|
|
|
|
$response = $this->withHeaders([
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
|
|
|
'Accept' => 'application/json'
|
|
|
|
|
])->putJson("/api/calendar-slots/{$slot->id}", [
|
|
|
|
|
'name' => 'Arrival Day'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(200)
|
|
|
|
|
->assertJsonPath('data.name', 'Arrival Day');
|
|
|
|
|
|
|
|
|
|
$this->assertDatabaseHas('calendar_slots', [
|
|
|
|
|
'id' => $slot->id,
|
|
|
|
|
'name' => 'Arrival Day'
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_cannot_access_calendar_slots_of_other_users_trip()
|
|
|
|
|
{
|
|
|
|
|
$otherUser = User::factory()->create();
|
|
|
|
|
$otherTrip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $otherUser->id,
|
|
|
|
|
'start_date' => '2024-01-01',
|
|
|
|
|
'end_date' => '2024-01-02'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = $this->withHeaders([
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
|
|
|
'Accept' => 'application/json'
|
|
|
|
|
])->getJson("/api/trips/{$otherTrip->id}/calendar-slots");
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_cannot_update_calendar_slot_of_other_users_trip()
|
|
|
|
|
{
|
|
|
|
|
$otherUser = User::factory()->create();
|
|
|
|
|
$otherTrip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $otherUser->id,
|
|
|
|
|
'start_date' => '2024-01-01',
|
|
|
|
|
'end_date' => '2024-01-01'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$slot = $otherTrip->calendarSlots()->first();
|
|
|
|
|
|
|
|
|
|
$response = $this->withHeaders([
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
|
|
|
'Accept' => 'application/json'
|
|
|
|
|
])->putJson("/api/calendar-slots/{$slot->id}", [
|
|
|
|
|
'name' => 'Hacked Name'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_no_slots_created_for_trip_without_dates()
|
|
|
|
|
{
|
|
|
|
|
$tripData = [
|
|
|
|
|
'name' => 'Trip without dates',
|
|
|
|
|
'description' => 'No dates set'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$response = $this->withHeaders([
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
|
|
|
'Accept' => 'application/json'
|
|
|
|
|
])->postJson('/api/trips', $tripData);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(201);
|
|
|
|
|
$tripId = $response->json('data.id');
|
|
|
|
|
|
|
|
|
|
// No slots should be created
|
|
|
|
|
$this->assertDatabaseCount('calendar_slots', 0);
|
|
|
|
|
|
|
|
|
|
$slots = CalendarSlot::where('trip_id', $tripId)->get();
|
|
|
|
|
$this->assertCount(0, $slots);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_slots_created_when_dates_added_to_trip()
|
|
|
|
|
{
|
|
|
|
|
$trip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'start_date' => null,
|
|
|
|
|
'end_date' => null
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Initially no slots
|
|
|
|
|
$this->assertCount(0, $trip->calendarSlots);
|
|
|
|
|
|
|
|
|
|
// Add dates to trip
|
|
|
|
|
$response = $this->withHeaders([
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
|
|
|
'Accept' => 'application/json'
|
|
|
|
|
])->putJson("/api/trips/{$trip->id}", [
|
|
|
|
|
'name' => $trip->name,
|
|
|
|
|
'start_date' => '2024-03-01',
|
|
|
|
|
'end_date' => '2024-03-02'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(200);
|
|
|
|
|
|
|
|
|
|
// Now should have 2 slots
|
|
|
|
|
$trip->refresh();
|
|
|
|
|
$this->assertCount(2, $trip->calendarSlots);
|
|
|
|
|
}
|
2025-10-06 14:21:47 +02:00
|
|
|
|
|
|
|
|
public function test_calendar_slots_include_planned_items_with_plannable_item()
|
|
|
|
|
{
|
|
|
|
|
$trip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'start_date' => '2024-01-01',
|
|
|
|
|
'end_date' => '2024-01-01'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Create a plannable item and schedule it
|
|
|
|
|
$plannableItem = \App\Models\PlannableItem::factory()->create([
|
|
|
|
|
'trip_id' => $trip->id,
|
|
|
|
|
'name' => 'Eiffel Tower'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = $this->withHeaders([
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
|
|
|
'Accept' => 'application/json'
|
|
|
|
|
])->postJson('/api/planned-items', [
|
|
|
|
|
'plannable_item_id' => $plannableItem->id,
|
|
|
|
|
'trip_id' => $trip->id,
|
|
|
|
|
'start_datetime' => '2024-01-01 14:00:00',
|
|
|
|
|
'end_datetime' => '2024-01-01 16:00:00',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(201);
|
|
|
|
|
|
|
|
|
|
// Fetch calendar slots and verify relationships are loaded
|
|
|
|
|
$slotsResponse = $this->withHeaders([
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
|
|
|
'Accept' => 'application/json'
|
|
|
|
|
])->getJson("/api/trips/{$trip->id}/calendar-slots");
|
|
|
|
|
|
|
|
|
|
$slotsResponse->assertStatus(200);
|
|
|
|
|
|
|
|
|
|
// Find the slot we just created
|
|
|
|
|
$slots = $slotsResponse->json('data');
|
|
|
|
|
$scheduledSlot = collect($slots)->firstWhere('name', 'Eiffel Tower');
|
|
|
|
|
|
|
|
|
|
$this->assertNotNull($scheduledSlot);
|
|
|
|
|
$this->assertArrayHasKey('planned_items', $scheduledSlot);
|
|
|
|
|
$this->assertCount(1, $scheduledSlot['planned_items']);
|
|
|
|
|
$this->assertArrayHasKey('plannable_item', $scheduledSlot['planned_items'][0]);
|
|
|
|
|
$this->assertEquals('Eiffel Tower', $scheduledSlot['planned_items'][0]['plannable_item']['name']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_calendar_slots_ordered_by_date_and_time()
|
|
|
|
|
{
|
|
|
|
|
$trip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'start_date' => '2024-01-01',
|
|
|
|
|
'end_date' => '2024-01-02'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Create plannable items
|
|
|
|
|
$item1 = \App\Models\PlannableItem::factory()->create(['trip_id' => $trip->id, 'name' => 'Breakfast']);
|
|
|
|
|
$item2 = \App\Models\PlannableItem::factory()->create(['trip_id' => $trip->id, 'name' => 'Lunch']);
|
|
|
|
|
$item3 = \App\Models\PlannableItem::factory()->create(['trip_id' => $trip->id, 'name' => 'Dinner Day 1']);
|
|
|
|
|
$item4 = \App\Models\PlannableItem::factory()->create(['trip_id' => $trip->id, 'name' => 'Breakfast Day 2']);
|
|
|
|
|
|
|
|
|
|
// Schedule in non-chronological order
|
|
|
|
|
$this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
|
|
|
'plannable_item_id' => $item3->id,
|
|
|
|
|
'trip_id' => $trip->id,
|
|
|
|
|
'start_datetime' => '2024-01-01 19:00:00',
|
|
|
|
|
'end_datetime' => '2024-01-01 21:00:00',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
|
|
|
'plannable_item_id' => $item1->id,
|
|
|
|
|
'trip_id' => $trip->id,
|
|
|
|
|
'start_datetime' => '2024-01-01 08:00:00',
|
|
|
|
|
'end_datetime' => '2024-01-01 09:00:00',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
|
|
|
'plannable_item_id' => $item4->id,
|
|
|
|
|
'trip_id' => $trip->id,
|
|
|
|
|
'start_datetime' => '2024-01-02 08:00:00',
|
|
|
|
|
'end_datetime' => '2024-01-02 09:00:00',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->actingAs($this->user)->postJson('/api/planned-items', [
|
|
|
|
|
'plannable_item_id' => $item2->id,
|
|
|
|
|
'trip_id' => $trip->id,
|
|
|
|
|
'start_datetime' => '2024-01-01 12:00:00',
|
|
|
|
|
'end_datetime' => '2024-01-01 13:00:00',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Fetch slots and verify they're ordered correctly
|
|
|
|
|
$response = $this->withHeaders([
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->token,
|
|
|
|
|
'Accept' => 'application/json'
|
|
|
|
|
])->getJson("/api/trips/{$trip->id}/calendar-slots");
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(200);
|
|
|
|
|
|
|
|
|
|
$slots = $response->json('data');
|
|
|
|
|
$scheduledSlots = collect($slots)->filter(fn($slot) => $slot['datetime_start'] !== null)->values();
|
|
|
|
|
|
|
|
|
|
// Should be ordered: Breakfast (Day 1, 8am), Lunch (Day 1, 12pm), Dinner (Day 1, 7pm), Breakfast (Day 2, 8am)
|
|
|
|
|
$this->assertEquals('Breakfast', $scheduledSlots[0]['name']);
|
|
|
|
|
$this->assertEquals('Lunch', $scheduledSlots[1]['name']);
|
|
|
|
|
$this->assertEquals('Dinner Day 1', $scheduledSlots[2]['name']);
|
|
|
|
|
$this->assertEquals('Breakfast Day 2', $scheduledSlots[3]['name']);
|
|
|
|
|
}
|
2025-09-28 18:48:44 +02:00
|
|
|
}
|