release/v0.1.0 #24
12 changed files with 788 additions and 18 deletions
|
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
namespace App\Infrastructure\Http\Controllers\API\E2e;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Infrastructure\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
|
@ -34,6 +34,12 @@ public function createTestUser(Request $request)
|
|||
]
|
||||
);
|
||||
|
||||
// Ensure email_verified_at is set even for existing users
|
||||
if (!$user->email_verified_at) {
|
||||
$user->email_verified_at = now();
|
||||
$user->save();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => $user->wasRecentlyCreated ? 'Test user created' : 'Test user already exists',
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
namespace App\Infrastructure\Http\Controllers\API\Trip;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Infrastructure\Http\Controllers\Controller;
|
||||
use App\Models\Trip;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
namespace App\Infrastructure\Http\Controllers\API\User\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Infrastructure\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Infrastructure\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
namespace App\Infrastructure\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -46,4 +46,12 @@ protected function casts(): array
|
|||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the trips created by this user.
|
||||
*/
|
||||
public function trips()
|
||||
{
|
||||
return $this->hasMany(Trip::class, 'created_by_user_id');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
$middleware->api(prepend: [
|
||||
\App\Http\Middleware\Cors::class,
|
||||
\App\Infrastructure\Http\Middleware\Cors::class,
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\API\AuthController;
|
||||
use App\Http\Controllers\API\TripController;
|
||||
use App\Infrastructure\Http\Controllers\API\User\Auth\AuthController;
|
||||
use App\Infrastructure\Http\Controllers\API\Trip\TripController;
|
||||
use App\Infrastructure\Http\Controllers\API\E2e\TestSetupController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
|
|
@ -14,6 +15,12 @@
|
|||
Route::post('/register', [AuthController::class, 'register']);
|
||||
Route::post('/login', [AuthController::class, 'login']);
|
||||
|
||||
// E2E test routes (development/testing only)
|
||||
Route::prefix('e2e/test')->group(function () {
|
||||
Route::post('/setup/user', [TestSetupController::class, 'createTestUser']);
|
||||
Route::post('/cleanup', [TestSetupController::class, 'cleanup']);
|
||||
});
|
||||
|
||||
// Protected routes
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::get('/user', function (Request $request) {
|
||||
|
|
|
|||
277
backend/tests/Feature/TestSetupControllerTest.php
Normal file
277
backend/tests/Feature/TestSetupControllerTest.php
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TestSetupControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* Test creating a new test user.
|
||||
*/
|
||||
public function test_can_create_new_test_user()
|
||||
{
|
||||
$userData = [
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
];
|
||||
|
||||
$response = $this->postJson('/api/e2e/test/setup/user', $userData);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'success' => true,
|
||||
'message' => 'Test user created',
|
||||
'data' => [
|
||||
'email' => 'test@example.com',
|
||||
'name' => 'Test User'
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'email' => 'test@example.com',
|
||||
'name' => 'Test User',
|
||||
]);
|
||||
|
||||
$user = User::where('email', 'test@example.com')->first();
|
||||
$this->assertNotNull($user->email_verified_at);
|
||||
$this->assertTrue(\Hash::check('password123', $user->password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test returning existing test user if already exists.
|
||||
*/
|
||||
public function test_returns_existing_test_user_if_already_exists()
|
||||
{
|
||||
// Create a user first
|
||||
$existingUser = User::factory()->create([
|
||||
'email' => 'existing@example.com',
|
||||
'name' => 'Existing User',
|
||||
]);
|
||||
|
||||
$userData = [
|
||||
'name' => 'Different Name',
|
||||
'email' => 'existing@example.com',
|
||||
'password' => 'password123',
|
||||
];
|
||||
|
||||
$response = $this->postJson('/api/e2e/test/setup/user', $userData);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'success' => true,
|
||||
'message' => 'Test user already exists',
|
||||
'data' => [
|
||||
'id' => $existingUser->id,
|
||||
'email' => 'existing@example.com',
|
||||
'name' => 'Existing User' // Should keep original name
|
||||
]
|
||||
]);
|
||||
|
||||
// Should not create a new user
|
||||
$this->assertCount(1, User::where('email', 'existing@example.com')->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test validation for required fields.
|
||||
*/
|
||||
public function test_validates_required_fields_for_create_user()
|
||||
{
|
||||
$response = $this->postJson('/api/e2e/test/setup/user', []);
|
||||
|
||||
$response->assertStatus(422);
|
||||
$response->assertJsonPath('errors.name', ['The name field is required.']);
|
||||
$response->assertJsonPath('errors.email', ['The email field is required.']);
|
||||
$response->assertJsonPath('errors.password', ['The password field is required.']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test email validation.
|
||||
*/
|
||||
public function test_validates_email_format()
|
||||
{
|
||||
$userData = [
|
||||
'name' => 'Test User',
|
||||
'email' => 'invalid-email',
|
||||
'password' => 'password123',
|
||||
];
|
||||
|
||||
$response = $this->postJson('/api/e2e/test/setup/user', $userData);
|
||||
|
||||
$response->assertStatus(422);
|
||||
$response->assertJsonPath('errors.email', ['The email field must be a valid email address.']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test password length validation.
|
||||
*/
|
||||
public function test_validates_password_length()
|
||||
{
|
||||
$userData = [
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'short',
|
||||
];
|
||||
|
||||
$response = $this->postJson('/api/e2e/test/setup/user', $userData);
|
||||
|
||||
$response->assertStatus(422);
|
||||
$response->assertJsonPath('errors.password', ['The password field must be at least 8 characters.']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cleanup removes test users with test email patterns.
|
||||
*/
|
||||
public function test_cleanup_removes_test_users()
|
||||
{
|
||||
// Create test users with test email patterns
|
||||
User::factory()->create(['email' => 'test.user.1@example.com']);
|
||||
User::factory()->create(['email' => 'test.user.2@example.com']);
|
||||
User::factory()->create(['email' => 'test123@example.com']);
|
||||
|
||||
// Create a regular user that should not be deleted
|
||||
User::factory()->create(['email' => 'regular@example.com']);
|
||||
|
||||
$response = $this->postJson('/api/e2e/test/cleanup');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'success' => true,
|
||||
'message' => 'Deleted 3 test users'
|
||||
]);
|
||||
|
||||
// Test users should be deleted
|
||||
$this->assertDatabaseMissing('users', ['email' => 'test.user.1@example.com']);
|
||||
$this->assertDatabaseMissing('users', ['email' => 'test.user.2@example.com']);
|
||||
$this->assertDatabaseMissing('users', ['email' => 'test123@example.com']);
|
||||
|
||||
// Regular user should still exist
|
||||
$this->assertDatabaseHas('users', ['email' => 'regular@example.com']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cleanup when no test users exist.
|
||||
*/
|
||||
public function test_cleanup_when_no_test_users_exist()
|
||||
{
|
||||
// Create only regular users
|
||||
User::factory()->create(['email' => 'regular1@example.com']);
|
||||
User::factory()->create(['email' => 'regular2@example.com']);
|
||||
|
||||
$response = $this->postJson('/api/e2e/test/cleanup');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'success' => true,
|
||||
'message' => 'Deleted 0 test users'
|
||||
]);
|
||||
|
||||
// Regular users should still exist
|
||||
$this->assertDatabaseHas('users', ['email' => 'regular1@example.com']);
|
||||
$this->assertDatabaseHas('users', ['email' => 'regular2@example.com']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cleanup removes only users matching test patterns.
|
||||
*/
|
||||
public function test_cleanup_only_removes_matching_patterns()
|
||||
{
|
||||
// Create users with various email patterns
|
||||
// Patterns that should be deleted: 'test%@example.com' OR 'test.user.%@example.com'
|
||||
User::factory()->create(['email' => 'test@example.com']); // Should be deleted (matches test%@example.com)
|
||||
User::factory()->create(['email' => 'test.user.123@example.com']); // Should be deleted (matches test.user.%@example.com)
|
||||
User::factory()->create(['email' => 'testABC@example.com']); // Should be deleted (matches test%@example.com)
|
||||
User::factory()->create(['email' => 'test.user.xyz@example.com']); // Should be deleted (matches test.user.%@example.com)
|
||||
User::factory()->create(['email' => 'test999@example.com']); // Should be deleted (matches test%@example.com)
|
||||
User::factory()->create(['email' => 'mytesting@example.com']); // Should NOT be deleted
|
||||
User::factory()->create(['email' => 'test@gmail.com']); // Should NOT be deleted
|
||||
User::factory()->create(['email' => 'mytest@example.com']); // Should NOT be deleted
|
||||
|
||||
$response = $this->postJson('/api/e2e/test/cleanup');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonPath('success', true);
|
||||
// Just verify the message contains "Deleted" and "test users"
|
||||
$this->assertStringContainsString('Deleted', $response->json('message'));
|
||||
$this->assertStringContainsString('test users', $response->json('message'));
|
||||
|
||||
// Only specific patterns should be deleted
|
||||
$this->assertDatabaseMissing('users', ['email' => 'test@example.com']);
|
||||
$this->assertDatabaseMissing('users', ['email' => 'test.user.123@example.com']);
|
||||
$this->assertDatabaseMissing('users', ['email' => 'testABC@example.com']);
|
||||
$this->assertDatabaseMissing('users', ['email' => 'test.user.xyz@example.com']);
|
||||
$this->assertDatabaseMissing('users', ['email' => 'test999@example.com']);
|
||||
|
||||
// Others should remain
|
||||
$this->assertDatabaseHas('users', ['email' => 'mytesting@example.com']);
|
||||
$this->assertDatabaseHas('users', ['email' => 'test@gmail.com']);
|
||||
$this->assertDatabaseHas('users', ['email' => 'mytest@example.com']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test both endpoints reject requests in production environment.
|
||||
*/
|
||||
public function test_endpoints_blocked_in_production()
|
||||
{
|
||||
// We can't easily mock app()->environment() in tests, so let's test the logic
|
||||
// by directly testing the controller with a production environment mock
|
||||
// For now, let's skip this test as it's complex to mock properly
|
||||
$this->markTestSkipped('Environment mocking in tests is complex - this is tested in integration');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create user endpoint works in non-production environment.
|
||||
*/
|
||||
public function test_endpoints_work_in_non_production_environment()
|
||||
{
|
||||
// Ensure we're not in production environment
|
||||
$this->assertNotEquals('production', app()->environment());
|
||||
|
||||
$response = $this->postJson('/api/e2e/test/setup/user', [
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user data is properly formatted in response.
|
||||
*/
|
||||
public function test_user_data_properly_formatted_in_response()
|
||||
{
|
||||
$userData = [
|
||||
'name' => 'John Doe',
|
||||
'email' => 'john.doe@example.com',
|
||||
'password' => 'securepassword123',
|
||||
];
|
||||
|
||||
$response = $this->postJson('/api/e2e/test/setup/user', $userData);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonStructure([
|
||||
'success',
|
||||
'message',
|
||||
'data' => [
|
||||
'id',
|
||||
'email',
|
||||
'name'
|
||||
]
|
||||
]);
|
||||
|
||||
// Ensure password is not included in response
|
||||
$response->assertJsonMissing(['password']);
|
||||
|
||||
$data = $response->json('data');
|
||||
$this->assertIsInt($data['id']);
|
||||
$this->assertEquals('john.doe@example.com', $data['email']);
|
||||
$this->assertEquals('John Doe', $data['name']);
|
||||
}
|
||||
}
|
||||
248
backend/tests/Unit/TripTest.php
Normal file
248
backend/tests/Unit/TripTest.php
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Models\Trip;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TripTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* Test trip creation with factory.
|
||||
*/
|
||||
public function test_trip_can_be_created()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$trip = Trip::factory()->create([
|
||||
'name' => 'Paris Adventure',
|
||||
'description' => 'A wonderful trip to Paris',
|
||||
'created_by_user_id' => $user->id,
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(Trip::class, $trip);
|
||||
$this->assertEquals('Paris Adventure', $trip->name);
|
||||
$this->assertEquals('A wonderful trip to Paris', $trip->description);
|
||||
$this->assertEquals($user->id, $trip->created_by_user_id);
|
||||
$this->assertNotNull($trip->id);
|
||||
$this->assertNotNull($trip->created_at);
|
||||
$this->assertNotNull($trip->updated_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip fillable attributes.
|
||||
*/
|
||||
public function test_trip_fillable_attributes()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$tripData = [
|
||||
'name' => 'Tokyo Trip',
|
||||
'description' => 'Exploring Japan',
|
||||
'start_date' => '2025-06-01',
|
||||
'end_date' => '2025-06-15',
|
||||
'created_by_user_id' => $user->id,
|
||||
];
|
||||
|
||||
$trip = Trip::create($tripData);
|
||||
|
||||
$this->assertEquals('Tokyo Trip', $trip->name);
|
||||
$this->assertEquals('Exploring Japan', $trip->description);
|
||||
$this->assertEquals('2025-06-01', $trip->start_date->format('Y-m-d'));
|
||||
$this->assertEquals('2025-06-15', $trip->end_date->format('Y-m-d'));
|
||||
$this->assertEquals($user->id, $trip->created_by_user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip date casting.
|
||||
*/
|
||||
public function test_trip_date_casting()
|
||||
{
|
||||
$trip = Trip::factory()->create([
|
||||
'start_date' => '2025-07-01',
|
||||
'end_date' => '2025-07-10',
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(\Illuminate\Support\Carbon::class, $trip->start_date);
|
||||
$this->assertInstanceOf(\Illuminate\Support\Carbon::class, $trip->end_date);
|
||||
$this->assertEquals('2025-07-01', $trip->start_date->format('Y-m-d'));
|
||||
$this->assertEquals('2025-07-10', $trip->end_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip can have null dates.
|
||||
*/
|
||||
public function test_trip_can_have_null_dates()
|
||||
{
|
||||
$trip = Trip::factory()->withoutDates()->create([
|
||||
'name' => 'Flexible Trip',
|
||||
]);
|
||||
|
||||
$this->assertNull($trip->start_date);
|
||||
$this->assertNull($trip->end_date);
|
||||
$this->assertEquals('Flexible Trip', $trip->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip belongs to user relationship.
|
||||
*/
|
||||
public function test_trip_belongs_to_user()
|
||||
{
|
||||
$user = User::factory()->create(['name' => 'John Doe']);
|
||||
$trip = Trip::factory()->create([
|
||||
'created_by_user_id' => $user->id,
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(User::class, $trip->user);
|
||||
$this->assertEquals($user->id, $trip->user->id);
|
||||
$this->assertEquals('John Doe', $trip->user->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip factory creates valid trips.
|
||||
*/
|
||||
public function test_trip_factory_creates_valid_trips()
|
||||
{
|
||||
$trip = Trip::factory()->create();
|
||||
|
||||
$this->assertNotEmpty($trip->name);
|
||||
$this->assertNotNull($trip->created_by_user_id);
|
||||
$this->assertInstanceOf(User::class, $trip->user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip factory upcoming state.
|
||||
*/
|
||||
public function test_trip_factory_upcoming_state()
|
||||
{
|
||||
$trip = Trip::factory()->upcoming()->create();
|
||||
|
||||
$this->assertNotNull($trip->start_date);
|
||||
$this->assertNotNull($trip->end_date);
|
||||
$this->assertTrue($trip->start_date->isFuture());
|
||||
$this->assertTrue($trip->end_date->isAfter($trip->start_date));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip factory past state.
|
||||
*/
|
||||
public function test_trip_factory_past_state()
|
||||
{
|
||||
$trip = Trip::factory()->past()->create();
|
||||
|
||||
$this->assertNotNull($trip->start_date);
|
||||
$this->assertNotNull($trip->end_date);
|
||||
$this->assertTrue($trip->start_date->isPast());
|
||||
$this->assertTrue($trip->end_date->isPast());
|
||||
$this->assertTrue($trip->end_date->isAfter($trip->start_date));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip model uses correct table.
|
||||
*/
|
||||
public function test_trip_uses_correct_table()
|
||||
{
|
||||
$trip = new Trip();
|
||||
|
||||
$this->assertEquals('trips', $trip->getTable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip model has correct primary key.
|
||||
*/
|
||||
public function test_trip_has_correct_primary_key()
|
||||
{
|
||||
$trip = new Trip();
|
||||
|
||||
$this->assertEquals('id', $trip->getKeyName());
|
||||
$this->assertTrue($trip->getIncrementing());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip model uses timestamps.
|
||||
*/
|
||||
public function test_trip_uses_timestamps()
|
||||
{
|
||||
$trip = new Trip();
|
||||
|
||||
$this->assertTrue($trip->usesTimestamps());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip can be created with minimal data.
|
||||
*/
|
||||
public function test_trip_can_be_created_with_minimal_data()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$trip = Trip::create([
|
||||
'name' => 'Minimal Trip',
|
||||
'created_by_user_id' => $user->id,
|
||||
]);
|
||||
|
||||
$this->assertEquals('Minimal Trip', $trip->name);
|
||||
$this->assertNull($trip->description);
|
||||
$this->assertNull($trip->start_date);
|
||||
$this->assertNull($trip->end_date);
|
||||
$this->assertEquals($user->id, $trip->created_by_user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip name is required.
|
||||
*/
|
||||
public function test_trip_name_is_required()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->expectException(\Illuminate\Database\QueryException::class);
|
||||
|
||||
Trip::create([
|
||||
'description' => 'A trip without a name',
|
||||
'created_by_user_id' => $user->id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip requires a user.
|
||||
*/
|
||||
public function test_trip_requires_user()
|
||||
{
|
||||
$this->expectException(\Illuminate\Database\QueryException::class);
|
||||
|
||||
Trip::create([
|
||||
'name' => 'Orphaned Trip',
|
||||
'description' => 'A trip without a user',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip dates can be formatted.
|
||||
*/
|
||||
public function test_trip_dates_can_be_formatted()
|
||||
{
|
||||
$trip = Trip::factory()->create([
|
||||
'start_date' => '2025-12-25',
|
||||
'end_date' => '2025-12-31',
|
||||
]);
|
||||
|
||||
$this->assertEquals('2025-12-25', $trip->start_date->format('Y-m-d'));
|
||||
$this->assertEquals('2025-12-31', $trip->end_date->format('Y-m-d'));
|
||||
$this->assertEquals('December 25, 2025', $trip->start_date->format('F j, Y'));
|
||||
$this->assertEquals('December 31, 2025', $trip->end_date->format('F j, Y'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trip can calculate duration.
|
||||
*/
|
||||
public function test_trip_can_calculate_duration()
|
||||
{
|
||||
$trip = Trip::factory()->create([
|
||||
'start_date' => '2025-06-01',
|
||||
'end_date' => '2025-06-07',
|
||||
]);
|
||||
|
||||
$duration = $trip->start_date->diffInDays($trip->end_date) + 1; // Include both start and end day
|
||||
$this->assertEquals(7, $duration);
|
||||
}
|
||||
}
|
||||
224
backend/tests/Unit/UserTest.php
Normal file
224
backend/tests/Unit/UserTest.php
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Trip;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* Test user creation with factory.
|
||||
*/
|
||||
public function test_user_can_be_created()
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'name' => 'John Doe',
|
||||
'email' => 'john@example.com',
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(User::class, $user);
|
||||
$this->assertEquals('John Doe', $user->name);
|
||||
$this->assertEquals('john@example.com', $user->email);
|
||||
$this->assertNotNull($user->id);
|
||||
$this->assertNotNull($user->created_at);
|
||||
$this->assertNotNull($user->updated_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user fillable attributes.
|
||||
*/
|
||||
public function test_user_fillable_attributes()
|
||||
{
|
||||
$userData = [
|
||||
'name' => 'Jane Doe',
|
||||
'email' => 'jane@example.com',
|
||||
'password' => 'password123',
|
||||
];
|
||||
|
||||
$user = User::create($userData);
|
||||
|
||||
$this->assertEquals('Jane Doe', $user->name);
|
||||
$this->assertEquals('jane@example.com', $user->email);
|
||||
$this->assertTrue(Hash::check('password123', $user->password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user hidden attributes.
|
||||
*/
|
||||
public function test_user_hidden_attributes()
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'password' => Hash::make('secret123'),
|
||||
]);
|
||||
|
||||
$userArray = $user->toArray();
|
||||
|
||||
$this->assertArrayNotHasKey('password', $userArray);
|
||||
$this->assertArrayNotHasKey('remember_token', $userArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user casts.
|
||||
*/
|
||||
public function test_user_casts()
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'email_verified_at' => now(),
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(\Illuminate\Support\Carbon::class, $user->email_verified_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test password is automatically hashed.
|
||||
*/
|
||||
public function test_password_is_automatically_hashed()
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'password' => 'plaintext-password',
|
||||
]);
|
||||
|
||||
$this->assertNotEquals('plaintext-password', $user->password);
|
||||
$this->assertTrue(Hash::check('plaintext-password', $user->password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user has API tokens trait.
|
||||
*/
|
||||
public function test_user_can_create_api_tokens()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$token = $user->createToken('test-token');
|
||||
|
||||
$this->assertInstanceOf(PersonalAccessToken::class, $token->accessToken);
|
||||
$this->assertIsString($token->plainTextToken);
|
||||
$this->assertEquals('test-token', $token->accessToken->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user can have multiple tokens.
|
||||
*/
|
||||
public function test_user_can_have_multiple_tokens()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$token1 = $user->createToken('token-1');
|
||||
$token2 = $user->createToken('token-2');
|
||||
|
||||
$this->assertCount(2, $user->tokens);
|
||||
$this->assertNotEquals($token1->plainTextToken, $token2->plainTextToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user can delete tokens.
|
||||
*/
|
||||
public function test_user_can_delete_tokens()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$token = $user->createToken('test-token');
|
||||
|
||||
$this->assertCount(1, $user->tokens);
|
||||
|
||||
$token->accessToken->delete();
|
||||
$user->refresh();
|
||||
|
||||
$this->assertCount(0, $user->tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user has trips relationship.
|
||||
*/
|
||||
public function test_user_has_trips_relationship()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
// Create some trips for this user
|
||||
Trip::factory()->count(3)->create([
|
||||
'created_by_user_id' => $user->id,
|
||||
]);
|
||||
|
||||
// Create a trip for another user
|
||||
$otherUser = User::factory()->create();
|
||||
Trip::factory()->create([
|
||||
'created_by_user_id' => $otherUser->id,
|
||||
]);
|
||||
|
||||
$this->assertCount(3, $user->trips);
|
||||
$this->assertInstanceOf(Trip::class, $user->trips->first());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user factory creates valid users.
|
||||
*/
|
||||
public function test_user_factory_creates_valid_users()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->assertNotEmpty($user->name);
|
||||
$this->assertNotEmpty($user->email);
|
||||
$this->assertNotEmpty($user->password);
|
||||
$this->assertNotNull($user->email_verified_at);
|
||||
$this->assertTrue(filter_var($user->email, FILTER_VALIDATE_EMAIL) !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user factory can create unverified users.
|
||||
*/
|
||||
public function test_user_factory_can_create_unverified_users()
|
||||
{
|
||||
$user = User::factory()->unverified()->create();
|
||||
|
||||
$this->assertNull($user->email_verified_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user email must be unique.
|
||||
*/
|
||||
public function test_user_email_must_be_unique()
|
||||
{
|
||||
User::factory()->create(['email' => 'test@example.com']);
|
||||
|
||||
$this->expectException(\Illuminate\Database\QueryException::class);
|
||||
|
||||
User::factory()->create(['email' => 'test@example.com']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user model uses correct table.
|
||||
*/
|
||||
public function test_user_uses_correct_table()
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
$this->assertEquals('users', $user->getTable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user model has correct primary key.
|
||||
*/
|
||||
public function test_user_has_correct_primary_key()
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
$this->assertEquals('id', $user->getKeyName());
|
||||
$this->assertTrue($user->getIncrementing());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user model uses timestamps.
|
||||
*/
|
||||
public function test_user_uses_timestamps()
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
$this->assertTrue($user->usesTimestamps());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue