Module 2 — Geographic Data: PSGC tables (regions/provinces/cities/ barangays) with native geometry(polygon|point, 4326) columns; cascading dropdown endpoints; ST_Contains-based GPS resolution; service-area CRUD with barangay attach/detach; SamplePsgcSeeder + psgc:import command. Module 3 — User Management: 5 role-specific profile tables (driver/ helper/scanner/store_partner with verification_status + rejection reason); profile auto-created on register; admin user CRUD with filters/pagination, suspend/activate, profile approve/reject; self-service /me + /me/profile with role-aware validation that blocks self-approval and resets rejected profiles to pending on resubmit. 69 feature tests passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
187 lines
6.0 KiB
PHP
187 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Api\V1\Auth;
|
|
|
|
use App\Models\DriverProfile;
|
|
use App\Models\ResidentProfile;
|
|
use App\Models\User;
|
|
use App\Services\Sms\FakeSmsService;
|
|
use App\Services\Sms\SmsService;
|
|
use Database\Seeders\RoleSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use Tests\TestCase;
|
|
|
|
class SelfServiceTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->seed(RoleSeeder::class);
|
|
$this->app->instance(SmsService::class, new FakeSmsService());
|
|
}
|
|
|
|
public function test_register_creates_matching_profile(): void
|
|
{
|
|
$payload = [
|
|
'first_name' => 'P',
|
|
'last_name' => 'D',
|
|
'email' => 'newdriver@example.com',
|
|
'phone' => '+639170111111',
|
|
'password' => 'Password123',
|
|
'password_confirmation' => 'Password123',
|
|
'role' => User::ROLE_DRIVER,
|
|
];
|
|
|
|
$response = $this->postJson('/api/v1/auth/register', $payload);
|
|
|
|
$response->assertCreated();
|
|
$user = User::where('email', 'newdriver@example.com')->firstOrFail();
|
|
$this->assertDatabaseHas('driver_profiles', ['user_id' => $user->id]);
|
|
}
|
|
|
|
public function test_user_can_update_own_basic_info(): void
|
|
{
|
|
$user = User::factory()->create(['status' => User::STATUS_ACTIVE]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->patchJson('/api/v1/me', [
|
|
'first_name' => 'Renamed',
|
|
'preferred_language' => 'tl',
|
|
]);
|
|
|
|
$response->assertOk()
|
|
->assertJsonPath('data.first_name', 'Renamed')
|
|
->assertJsonPath('data.preferred_language', 'tl');
|
|
}
|
|
|
|
public function test_user_can_get_own_profile(): void
|
|
{
|
|
$user = User::factory()->create([
|
|
'role' => User::ROLE_RESIDENT,
|
|
'status' => User::STATUS_ACTIVE,
|
|
]);
|
|
ResidentProfile::create(['user_id' => $user->id, 'occupation' => 'Engineer']);
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->getJson('/api/v1/me/profile');
|
|
|
|
$response->assertOk()
|
|
->assertJsonPath('data.role', User::ROLE_RESIDENT)
|
|
->assertJsonPath('data.profile.occupation', 'Engineer');
|
|
}
|
|
|
|
public function test_resident_can_update_own_profile(): void
|
|
{
|
|
$user = User::factory()->create([
|
|
'role' => User::ROLE_RESIDENT,
|
|
'status' => User::STATUS_ACTIVE,
|
|
]);
|
|
ResidentProfile::create(['user_id' => $user->id]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->patchJson('/api/v1/me/profile', [
|
|
'occupation' => 'Driver',
|
|
'gender' => 'male',
|
|
'emergency_contact_name' => 'Mom',
|
|
'emergency_contact_phone' => '+639170000000',
|
|
]);
|
|
|
|
$response->assertOk()
|
|
->assertJsonPath('data.profile.occupation', 'Driver')
|
|
->assertJsonPath('data.profile.gender', 'male');
|
|
}
|
|
|
|
public function test_driver_profile_update_is_role_specific(): void
|
|
{
|
|
$user = User::factory()->create([
|
|
'role' => User::ROLE_DRIVER,
|
|
'status' => User::STATUS_ACTIVE,
|
|
]);
|
|
DriverProfile::create(['user_id' => $user->id]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->patchJson('/api/v1/me/profile', [
|
|
'license_number' => 'PH-123456',
|
|
'license_class' => 'NPC',
|
|
'license_expires_at' => '2030-01-01',
|
|
'years_of_experience' => 10,
|
|
]);
|
|
|
|
$response->assertOk()
|
|
->assertJsonPath('data.profile.license_number', 'PH-123456')
|
|
->assertJsonPath('data.profile.years_of_experience', 10);
|
|
}
|
|
|
|
public function test_user_cannot_self_approve_via_profile_update(): void
|
|
{
|
|
$user = User::factory()->create([
|
|
'role' => User::ROLE_DRIVER,
|
|
'status' => User::STATUS_PENDING,
|
|
]);
|
|
DriverProfile::create([
|
|
'user_id' => $user->id,
|
|
'verification_status' => 'pending',
|
|
]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->patchJson('/api/v1/me/profile', [
|
|
'license_number' => 'X',
|
|
'verification_status' => 'approved',
|
|
]);
|
|
|
|
$response->assertOk();
|
|
$this->assertDatabaseHas('driver_profiles', [
|
|
'user_id' => $user->id,
|
|
'verification_status' => 'pending',
|
|
]);
|
|
}
|
|
|
|
public function test_resubmitting_after_rejection_resets_to_pending(): void
|
|
{
|
|
$admin = User::factory()->create(['role' => User::ROLE_ADMIN]);
|
|
$user = User::factory()->create(['role' => User::ROLE_DRIVER, 'status' => User::STATUS_ACTIVE]);
|
|
$profile = DriverProfile::create([
|
|
'user_id' => $user->id,
|
|
'verification_status' => 'rejected',
|
|
'rejection_reason' => 'Bad photo',
|
|
'verified_by_admin_id' => $admin->id,
|
|
]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->patchJson('/api/v1/me/profile', [
|
|
'license_number' => 'PH-RETRY',
|
|
]);
|
|
|
|
$response->assertOk();
|
|
$this->assertDatabaseHas('driver_profiles', [
|
|
'id' => $profile->id,
|
|
'verification_status' => 'pending',
|
|
'rejection_reason' => null,
|
|
]);
|
|
}
|
|
|
|
public function test_admin_has_no_role_profile(): void
|
|
{
|
|
$admin = User::factory()->create(['role' => User::ROLE_ADMIN]);
|
|
Sanctum::actingAs($admin);
|
|
|
|
$this->getJson('/api/v1/me/profile')->assertStatus(422);
|
|
$this->patchJson('/api/v1/me/profile', ['anything' => 'X'])->assertStatus(422);
|
|
}
|
|
|
|
public function test_self_update_validates_email_uniqueness(): void
|
|
{
|
|
$other = User::factory()->create(['email' => 'taken@example.com']);
|
|
$user = User::factory()->create(['status' => User::STATUS_ACTIVE]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->patchJson('/api/v1/me', ['email' => 'taken@example.com']);
|
|
|
|
$response->assertStatus(422)
|
|
->assertJsonValidationErrors(['email']);
|
|
}
|
|
}
|