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>
156 lines
4.7 KiB
PHP
156 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Api\V1\Geo;
|
|
|
|
use App\Models\Barangay;
|
|
use App\Models\ServiceArea;
|
|
use App\Models\User;
|
|
use Database\Seeders\RoleSeeder;
|
|
use Database\Seeders\SamplePsgcSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use Tests\TestCase;
|
|
|
|
class ServiceAreaTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $admin;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->seed([RoleSeeder::class, SamplePsgcSeeder::class]);
|
|
$this->admin = User::factory()->create([
|
|
'role' => User::ROLE_ADMIN,
|
|
'status' => User::STATUS_ACTIVE,
|
|
]);
|
|
}
|
|
|
|
public function test_admin_can_create_service_area_with_barangays(): void
|
|
{
|
|
Sanctum::actingAs($this->admin);
|
|
|
|
$barangays = Barangay::limit(3)->pluck('id')->all();
|
|
|
|
$response = $this->postJson('/api/v1/service-areas', [
|
|
'name' => 'QC Pilot',
|
|
'code' => 'QC-PILOT',
|
|
'description' => 'Pilot area covering 3 QC barangays',
|
|
'barangay_ids' => $barangays,
|
|
]);
|
|
|
|
$response->assertCreated()
|
|
->assertJsonPath('data.code', 'QC-PILOT')
|
|
->assertJsonPath('data.barangay_count', 3);
|
|
|
|
$this->assertDatabaseHas('service_areas', ['code' => 'QC-PILOT']);
|
|
$this->assertDatabaseCount('service_area_barangay', 3);
|
|
}
|
|
|
|
public function test_resident_cannot_create_service_area(): void
|
|
{
|
|
$resident = User::factory()->create([
|
|
'role' => User::ROLE_RESIDENT,
|
|
'status' => User::STATUS_ACTIVE,
|
|
]);
|
|
Sanctum::actingAs($resident);
|
|
|
|
$response = $this->postJson('/api/v1/service-areas', [
|
|
'name' => 'Sneaky',
|
|
'code' => 'SNK',
|
|
]);
|
|
|
|
$response->assertStatus(403);
|
|
}
|
|
|
|
public function test_unauthenticated_blocked(): void
|
|
{
|
|
$this->getJson('/api/v1/service-areas')->assertStatus(401);
|
|
}
|
|
|
|
public function test_duplicate_code_rejected(): void
|
|
{
|
|
Sanctum::actingAs($this->admin);
|
|
ServiceArea::factory()->create(['code' => 'DUP']);
|
|
|
|
$response = $this->postJson('/api/v1/service-areas', [
|
|
'name' => 'Other',
|
|
'code' => 'DUP',
|
|
]);
|
|
|
|
$response->assertStatus(422)
|
|
->assertJsonValidationErrors(['code']);
|
|
}
|
|
|
|
public function test_admin_can_attach_barangays(): void
|
|
{
|
|
Sanctum::actingAs($this->admin);
|
|
$area = ServiceArea::factory()->create();
|
|
$barangays = Barangay::limit(2)->pluck('id')->all();
|
|
|
|
$response = $this->postJson("/api/v1/service-areas/{$area->uuid}/barangays", [
|
|
'barangay_ids' => $barangays,
|
|
]);
|
|
|
|
$response->assertOk()
|
|
->assertJsonPath('data.barangay_count', 2);
|
|
}
|
|
|
|
public function test_admin_can_detach_barangay(): void
|
|
{
|
|
Sanctum::actingAs($this->admin);
|
|
$area = ServiceArea::factory()->create();
|
|
$barangay = Barangay::first();
|
|
$area->barangays()->attach($barangay->id);
|
|
|
|
$response = $this->deleteJson("/api/v1/service-areas/{$area->uuid}/barangays/{$barangay->id}");
|
|
|
|
$response->assertOk();
|
|
$this->assertDatabaseMissing('service_area_barangay', [
|
|
'service_area_id' => $area->id,
|
|
'barangay_id' => $barangay->id,
|
|
]);
|
|
}
|
|
|
|
public function test_admin_can_update_service_area(): void
|
|
{
|
|
Sanctum::actingAs($this->admin);
|
|
$area = ServiceArea::factory()->create(['name' => 'Old']);
|
|
|
|
$response = $this->patchJson("/api/v1/service-areas/{$area->uuid}", [
|
|
'name' => 'New Name',
|
|
'status' => 'inactive',
|
|
]);
|
|
|
|
$response->assertOk()
|
|
->assertJsonPath('data.name', 'New Name')
|
|
->assertJsonPath('data.status', 'inactive');
|
|
}
|
|
|
|
public function test_admin_can_delete_service_area(): void
|
|
{
|
|
Sanctum::actingAs($this->admin);
|
|
$area = ServiceArea::factory()->create();
|
|
|
|
$response = $this->deleteJson("/api/v1/service-areas/{$area->uuid}");
|
|
|
|
$response->assertOk();
|
|
$this->assertSoftDeleted('service_areas', ['id' => $area->id]);
|
|
}
|
|
|
|
public function test_admin_can_list_with_filter(): void
|
|
{
|
|
Sanctum::actingAs($this->admin);
|
|
ServiceArea::factory()->create(['code' => 'A', 'status' => 'active']);
|
|
ServiceArea::factory()->create(['code' => 'B', 'status' => 'inactive']);
|
|
|
|
$response = $this->getJson('/api/v1/service-areas?status=active');
|
|
|
|
$response->assertOk();
|
|
$codes = collect($response->json('data'))->pluck('code')->all();
|
|
$this->assertContains('A', $codes);
|
|
$this->assertNotContains('B', $codes);
|
|
}
|
|
}
|