2025-09-27 11:14:43 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Tests\Feature;
|
|
|
|
|
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
use App\Models\Trip;
|
|
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
|
use Illuminate\Foundation\Testing\WithFaker;
|
|
|
|
|
use Laravel\Sanctum\Sanctum;
|
|
|
|
|
use Tests\TestCase;
|
|
|
|
|
|
|
|
|
|
class TripTest extends TestCase
|
|
|
|
|
{
|
|
|
|
|
use RefreshDatabase, WithFaker;
|
|
|
|
|
|
|
|
|
|
protected $user;
|
|
|
|
|
|
|
|
|
|
protected function setUp(): void
|
|
|
|
|
{
|
|
|
|
|
parent::setUp();
|
|
|
|
|
$this->user = User::factory()->create();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test that unauthenticated users cannot access trip endpoints.
|
|
|
|
|
*/
|
|
|
|
|
public function test_unauthenticated_user_cannot_access_trips()
|
|
|
|
|
{
|
|
|
|
|
$response = $this->getJson('/api/trips');
|
|
|
|
|
$response->assertStatus(401);
|
|
|
|
|
|
|
|
|
|
$response = $this->postJson('/api/trips', []);
|
|
|
|
|
$response->assertStatus(401);
|
|
|
|
|
|
|
|
|
|
$response = $this->putJson('/api/trips/1', []);
|
|
|
|
|
$response->assertStatus(401);
|
|
|
|
|
|
|
|
|
|
$response = $this->deleteJson('/api/trips/1');
|
|
|
|
|
$response->assertStatus(401);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test user can create a trip.
|
|
|
|
|
*/
|
|
|
|
|
public function test_user_can_create_trip()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
$tripData = [
|
|
|
|
|
'name' => 'Summer Vacation 2025',
|
|
|
|
|
'description' => 'A wonderful trip to Europe',
|
|
|
|
|
'start_date' => '2025-06-01',
|
|
|
|
|
'end_date' => '2025-06-15',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$response = $this->postJson('/api/trips', $tripData);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(201)
|
|
|
|
|
->assertJsonStructure([
|
2025-09-30 08:29:17 +02:00
|
|
|
'data' => [
|
|
|
|
|
'id',
|
|
|
|
|
'name',
|
|
|
|
|
'description',
|
|
|
|
|
'start_date',
|
|
|
|
|
'end_date',
|
|
|
|
|
'created_by_user_id',
|
|
|
|
|
'created_at',
|
|
|
|
|
'updated_at',
|
|
|
|
|
]
|
2025-09-27 11:14:43 +02:00
|
|
|
])
|
2025-09-30 08:29:17 +02:00
|
|
|
->assertJsonPath('data.name', 'Summer Vacation 2025')
|
|
|
|
|
->assertJsonPath('data.description', 'A wonderful trip to Europe')
|
|
|
|
|
->assertJsonPath('data.created_by_user_id', $this->user->id);
|
2025-09-27 11:14:43 +02:00
|
|
|
|
|
|
|
|
$this->assertDatabaseHas('trips', [
|
|
|
|
|
'name' => 'Summer Vacation 2025',
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test trip creation validation.
|
|
|
|
|
*/
|
|
|
|
|
public function test_trip_creation_validates_required_fields()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
// Test without name
|
|
|
|
|
$response = $this->postJson('/api/trips', [
|
|
|
|
|
'description' => 'A trip without a name',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(422)
|
|
|
|
|
->assertJsonValidationErrors(['name']);
|
|
|
|
|
|
|
|
|
|
// Test with empty name
|
|
|
|
|
$response = $this->postJson('/api/trips', [
|
|
|
|
|
'name' => '',
|
|
|
|
|
'description' => 'A trip with empty name',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(422)
|
|
|
|
|
->assertJsonValidationErrors(['name']);
|
|
|
|
|
|
|
|
|
|
// Test with invalid dates
|
|
|
|
|
$response = $this->postJson('/api/trips', [
|
|
|
|
|
'name' => 'Trip with invalid dates',
|
|
|
|
|
'start_date' => 'not-a-date',
|
|
|
|
|
'end_date' => '2025-13-45',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(422)
|
|
|
|
|
->assertJsonValidationErrors(['start_date', 'end_date']);
|
|
|
|
|
|
|
|
|
|
// Test with end date before start date
|
|
|
|
|
$response = $this->postJson('/api/trips', [
|
|
|
|
|
'name' => 'Trip with reversed dates',
|
|
|
|
|
'start_date' => '2025-06-15',
|
|
|
|
|
'end_date' => '2025-06-01',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(422)
|
|
|
|
|
->assertJsonValidationErrors(['end_date']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test user can list their trips.
|
|
|
|
|
*/
|
|
|
|
|
public function test_user_can_list_their_own_trips()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
// Create some trips for this user
|
|
|
|
|
$trips = Trip::factory()->count(3)->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Create trips for another user (should not be visible)
|
|
|
|
|
$otherUser = User::factory()->create();
|
|
|
|
|
Trip::factory()->count(2)->create([
|
|
|
|
|
'created_by_user_id' => $otherUser->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = $this->getJson('/api/trips');
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(200)
|
2025-09-30 08:29:17 +02:00
|
|
|
->assertJsonCount(3, 'data')
|
2025-09-27 11:14:43 +02:00
|
|
|
->assertJsonStructure([
|
2025-09-30 08:29:17 +02:00
|
|
|
'data' => [
|
|
|
|
|
'*' => [
|
|
|
|
|
'id',
|
|
|
|
|
'name',
|
|
|
|
|
'description',
|
|
|
|
|
'start_date',
|
|
|
|
|
'end_date',
|
|
|
|
|
'created_by_user_id',
|
|
|
|
|
'created_at',
|
|
|
|
|
'updated_at',
|
|
|
|
|
]
|
2025-09-27 11:14:43 +02:00
|
|
|
]
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Verify all returned trips belong to the authenticated user
|
2025-09-30 08:29:17 +02:00
|
|
|
foreach ($response->json('data') as $trip) {
|
2025-09-27 11:14:43 +02:00
|
|
|
$this->assertEquals($this->user->id, $trip['created_by_user_id']);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test user can view a specific trip.
|
|
|
|
|
*/
|
|
|
|
|
public function test_user_can_view_their_own_trip()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
$trip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'name' => 'My Special Trip',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = $this->getJson("/api/trips/{$trip->id}");
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(200)
|
2025-09-30 08:29:17 +02:00
|
|
|
->assertJsonPath('data.id', $trip->id)
|
|
|
|
|
->assertJsonPath('data.name', 'My Special Trip')
|
|
|
|
|
->assertJsonPath('data.created_by_user_id', $this->user->id);
|
2025-09-27 11:14:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test user cannot view another user's trip.
|
|
|
|
|
*/
|
|
|
|
|
public function test_user_cannot_view_another_users_trip()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
$otherUser = User::factory()->create();
|
|
|
|
|
$otherTrip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $otherUser->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = $this->getJson("/api/trips/{$otherTrip->id}");
|
|
|
|
|
|
|
|
|
|
// Controller returns 404 when trip doesn't belong to user
|
|
|
|
|
$response->assertStatus(404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test user can update their trip.
|
|
|
|
|
*/
|
|
|
|
|
public function test_user_can_update_their_own_trip()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
$trip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'name' => 'Original Name',
|
|
|
|
|
'description' => 'Original Description',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$updateData = [
|
|
|
|
|
'name' => 'Updated Trip Name',
|
|
|
|
|
'description' => 'Updated Description',
|
|
|
|
|
'start_date' => '2025-07-01',
|
|
|
|
|
'end_date' => '2025-07-15',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$response = $this->putJson("/api/trips/{$trip->id}", $updateData);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(200)
|
2025-09-30 08:29:17 +02:00
|
|
|
->assertJsonPath('data.id', $trip->id)
|
|
|
|
|
->assertJsonPath('data.name', 'Updated Trip Name')
|
|
|
|
|
->assertJsonPath('data.description', 'Updated Description');
|
2025-09-27 11:14:43 +02:00
|
|
|
|
|
|
|
|
$this->assertDatabaseHas('trips', [
|
|
|
|
|
'id' => $trip->id,
|
|
|
|
|
'name' => 'Updated Trip Name',
|
|
|
|
|
'description' => 'Updated Description',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test user cannot update another user's trip.
|
|
|
|
|
*/
|
|
|
|
|
public function test_user_cannot_update_another_users_trip()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
$otherUser = User::factory()->create();
|
|
|
|
|
$otherTrip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $otherUser->id,
|
|
|
|
|
'name' => 'Other User Trip',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = $this->putJson("/api/trips/{$otherTrip->id}", [
|
|
|
|
|
'name' => 'Trying to Update',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Controller returns 404 when trip doesn't belong to user
|
|
|
|
|
$response->assertStatus(404);
|
|
|
|
|
|
|
|
|
|
// Verify the trip wasn't updated
|
|
|
|
|
$this->assertDatabaseHas('trips', [
|
|
|
|
|
'id' => $otherTrip->id,
|
|
|
|
|
'name' => 'Other User Trip',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test user can delete their trip.
|
|
|
|
|
*/
|
|
|
|
|
public function test_user_can_delete_their_own_trip()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
$trip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = $this->deleteJson("/api/trips/{$trip->id}");
|
|
|
|
|
|
|
|
|
|
// Controller returns 200 with a message
|
|
|
|
|
$response->assertStatus(200)
|
|
|
|
|
->assertJson([
|
|
|
|
|
'message' => 'Trip deleted successfully'
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertDatabaseMissing('trips', [
|
|
|
|
|
'id' => $trip->id,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test user cannot delete another user's trip.
|
|
|
|
|
*/
|
|
|
|
|
public function test_user_cannot_delete_another_users_trip()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
$otherUser = User::factory()->create();
|
|
|
|
|
$otherTrip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $otherUser->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = $this->deleteJson("/api/trips/{$otherTrip->id}");
|
|
|
|
|
|
|
|
|
|
// Controller returns 404 when trip doesn't belong to user
|
|
|
|
|
$response->assertStatus(404);
|
|
|
|
|
|
|
|
|
|
// Verify the trip still exists
|
|
|
|
|
$this->assertDatabaseHas('trips', [
|
|
|
|
|
'id' => $otherTrip->id,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test handling non-existent trip.
|
|
|
|
|
*/
|
|
|
|
|
public function test_returns_404_for_non_existent_trip()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
$response = $this->getJson('/api/trips/99999');
|
|
|
|
|
$response->assertStatus(404);
|
|
|
|
|
|
|
|
|
|
$response = $this->putJson('/api/trips/99999', ['name' => 'Updated']);
|
|
|
|
|
$response->assertStatus(404);
|
|
|
|
|
|
|
|
|
|
$response = $this->deleteJson('/api/trips/99999');
|
|
|
|
|
$response->assertStatus(404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test trip creation with minimal data.
|
|
|
|
|
*/
|
|
|
|
|
public function test_user_can_create_trip_with_minimal_data()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
$tripData = [
|
|
|
|
|
'name' => 'Minimal Trip',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$response = $this->postJson('/api/trips', $tripData);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(201)
|
2025-09-30 08:29:17 +02:00
|
|
|
->assertJsonPath('data.name', 'Minimal Trip')
|
|
|
|
|
->assertJsonPath('data.created_by_user_id', $this->user->id)
|
2025-09-27 11:14:43 +02:00
|
|
|
->assertJsonStructure([
|
2025-09-30 08:29:17 +02:00
|
|
|
'data' => [
|
|
|
|
|
'id',
|
|
|
|
|
'name',
|
|
|
|
|
'created_by_user_id',
|
|
|
|
|
'created_at',
|
|
|
|
|
'updated_at',
|
|
|
|
|
]
|
2025-09-27 11:14:43 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertDatabaseHas('trips', [
|
|
|
|
|
'name' => 'Minimal Trip',
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'description' => null,
|
|
|
|
|
'start_date' => null,
|
|
|
|
|
'end_date' => null,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test trip name length validation.
|
|
|
|
|
*/
|
|
|
|
|
public function test_trip_name_length_validation()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
// Test with too long name (assuming max is 255)
|
|
|
|
|
$response = $this->postJson('/api/trips', [
|
|
|
|
|
'name' => str_repeat('a', 256),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(422)
|
|
|
|
|
->assertJsonValidationErrors(['name']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test trips are returned in correct order.
|
|
|
|
|
*/
|
|
|
|
|
public function test_trips_are_returned_in_descending_order()
|
|
|
|
|
{
|
|
|
|
|
Sanctum::actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
// Create trips with specific timestamps
|
|
|
|
|
$oldTrip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'name' => 'Old Trip',
|
|
|
|
|
'created_at' => now()->subDays(2),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$newTrip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'name' => 'New Trip',
|
|
|
|
|
'created_at' => now(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$middleTrip = Trip::factory()->create([
|
|
|
|
|
'created_by_user_id' => $this->user->id,
|
|
|
|
|
'name' => 'Middle Trip',
|
|
|
|
|
'created_at' => now()->subDay(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$response = $this->getJson('/api/trips');
|
|
|
|
|
|
|
|
|
|
$response->assertStatus(200);
|
|
|
|
|
|
2025-09-30 08:29:17 +02:00
|
|
|
$trips = $response->json('data');
|
2025-09-27 11:14:43 +02:00
|
|
|
|
|
|
|
|
// Verify trips are in descending order (newest first)
|
|
|
|
|
$this->assertEquals('New Trip', $trips[0]['name']);
|
|
|
|
|
$this->assertEquals('Middle Trip', $trips[1]['name']);
|
|
|
|
|
$this->assertEquals('Old Trip', $trips[2]['name']);
|
|
|
|
|
}
|
|
|
|
|
}
|