133 lines
5.7 KiB
PHP
133 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Api\V1\E2E;
|
|
|
|
use App\Models\DropOffPoint;
|
|
use App\Models\PartnerStore;
|
|
use App\Models\QrCode;
|
|
use App\Models\User;
|
|
use App\Models\Truck;
|
|
use Database\Seeders\RoleSeeder;
|
|
use Database\Seeders\SampleDropOffPointsSeeder;
|
|
use Database\Seeders\SamplePsgcSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use Tests\TestCase;
|
|
|
|
class GoldenPathTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_full_golden_path_e2e_flow(): void
|
|
{
|
|
// 1. Setup Personas & Environment
|
|
$this->seed([RoleSeeder::class, SamplePsgcSeeder::class, SampleDropOffPointsSeeder::class]);
|
|
|
|
$tenantId = \App\Models\Tenant::first()->id;
|
|
$admin = User::factory()->create(['role' => User::ROLE_ADMIN, 'status' => 'active', 'tenant_id' => $tenantId]);
|
|
$storeOwner = User::factory()->create(['role' => User::ROLE_STORE_PARTNER, 'status' => 'active', 'tenant_id' => $tenantId]);
|
|
$resident = User::factory()->create(['role' => User::ROLE_RESIDENT, 'status' => 'active', 'tenant_id' => $tenantId]);
|
|
$scanner = User::factory()->create(['role' => User::ROLE_SCANNER, 'status' => 'active', 'tenant_id' => $tenantId]);
|
|
$driver = User::factory()->create(['role' => User::ROLE_DRIVER, 'status' => 'active', 'tenant_id' => $tenantId]);
|
|
|
|
$dop = DropOffPoint::first();
|
|
$store = PartnerStore::factory()->create([
|
|
'owner_user_id' => $storeOwner->id,
|
|
'status' => 'active',
|
|
'commission_rate_percent' => 10,
|
|
'tenant_id' => $tenantId,
|
|
]);
|
|
|
|
$truck = Truck::create(['tenant_id' => \App\Models\Tenant::first()->id, 'plate_number' => 'ABC-1234', 'status' => 'active', 'capacity_kg' => 5000]);
|
|
|
|
// 1b. Create a Household for the batch
|
|
$household = \App\Models\Household::factory()->create();
|
|
|
|
// 2. Admin Generates Wholesale Batch
|
|
Sanctum::actingAs($admin);
|
|
$batchResponse = $this->postJson('/api/v1/admin/qr-batches', [
|
|
'purpose' => \App\Models\QrCodeBatch::PURPOSE_STORE,
|
|
'quantity' => 10,
|
|
'cost_per_code' => 50.00,
|
|
'notes' => 'E2E Golden Path Batch',
|
|
'household_id' => $household->uuid,
|
|
]);
|
|
$batchResponse->assertCreated();
|
|
$batchId = $batchResponse->json('data.id');
|
|
|
|
// 3. Admin Allocates/Issues Batch to Store
|
|
$issueResponse = $this->postJson("/api/v1/admin/partner-stores/{$store->uuid}/issue-inventory", [
|
|
'quantity' => 10,
|
|
'wholesale_price_centavos' => 800,
|
|
]);
|
|
$issueResponse->assertCreated();
|
|
$this->assertEquals(10, QrCode::where('assigned_to_store_id', $store->id)->count());
|
|
|
|
// 4. Store Sells 5 Codes to Resident
|
|
Sanctum::actingAs($storeOwner);
|
|
$saleResponse = $this->postJson("/api/v1/store/sales", [
|
|
'user_id' => $resident->id,
|
|
'quantity' => 5,
|
|
]);
|
|
$saleResponse->assertCreated();
|
|
|
|
$activeCodes = QrCode::where('assigned_to_user_id', $resident->id)
|
|
->where('status', 'active')
|
|
->get();
|
|
$this->assertCount(5, $activeCodes);
|
|
|
|
$route = \App\Models\Route::create(['tenant_id' => \App\Models\Tenant::first()->id, 'name' => 'Test Route', 'code' => 'R-TEST', 'status' => 'active']);
|
|
$team = \App\Models\CollectionTeam::create(['tenant_id' => \App\Models\Tenant::first()->id, 'name' => 'Team Alpha', 'status' => 'active', 'driver_id' => $driver->id, 'scanner_id' => $scanner->id]);
|
|
|
|
// 5. Admin Assigns a Trip to the Driver
|
|
Sanctum::actingAs($admin);
|
|
$tripResponse = $this->postJson('/api/v1/admin/trips', [
|
|
'driver_id' => $driver->id,
|
|
'truck_id' => $truck->id,
|
|
'route_id' => $route->id,
|
|
'team_id' => $team->id,
|
|
'scheduled_date' => now()->toDateString(),
|
|
'drop_off_point_ids' => [$dop->id],
|
|
]);
|
|
$tripResponse->assertCreated();
|
|
$tripId = $tripResponse->json('data.id');
|
|
|
|
// 6. Driver Starts Trip & Pings Location
|
|
Sanctum::actingAs($driver);
|
|
$this->postJson("/api/v1/driver/trips/{$tripId}/start")->assertOk();
|
|
$this->postJson("/api/v1/driver/trucks/{$truck->id}/location", [
|
|
'lat' => 14.6539,
|
|
'lng' => 121.0685,
|
|
])->assertOk();
|
|
|
|
// 7. Scanner Scans the 5 Resident Bags at the DOP
|
|
Sanctum::actingAs($scanner);
|
|
$scans = $activeCodes->map(function ($code) use ($dop) {
|
|
return [
|
|
'serial' => $code->serial,
|
|
'drop_off_point_id' => $dop->id,
|
|
'lat' => 14.6539,
|
|
'lng' => 121.0685,
|
|
];
|
|
})->toArray();
|
|
|
|
$scanResponse = $this->postJson('/api/v1/scanner/scan/bulk', [
|
|
'scans' => $scans,
|
|
]);
|
|
$scanResponse->assertOk()->assertJsonPath('data.accepted_count', 5);
|
|
|
|
// 8. Driver Arrives at DOP & Completes Trip
|
|
Sanctum::actingAs($driver);
|
|
// We need to fetch the stop id first since the route is /api/v1/driver/trips/{trip}/stops/{stop}/arrive
|
|
$stopId = \App\Models\TripStop::where('trip_id', $tripId)->where('drop_off_point_id', $dop->id)->first()->id;
|
|
$this->postJson("/api/v1/driver/trips/{$tripId}/stops/{$stopId}/arrive")->assertOk();
|
|
|
|
$this->postJson("/api/v1/driver/trips/{$tripId}/complete")->assertOk();
|
|
|
|
// 9. Final Database Assertions
|
|
$this->assertEquals(5, QrCode::where('assigned_to_user_id', $resident->id)->where('status', 'used')->count());
|
|
$this->assertDatabaseHas('trips', ['id' => $tripId, 'status' => 'completed']);
|
|
$this->assertDatabaseHas('collection_logs', ['qr_code_id' => $activeCodes->first()->id]);
|
|
}
|
|
}
|