Files
Verde-Web/tests/Feature/Api/V1/Scan/ScanTest.php

162 lines
5.4 KiB
PHP

<?php
namespace Tests\Feature\Api\V1\Scan;
use App\Models\DropOffPoint;
use App\Models\Household;
use App\Models\QrCode;
use App\Models\QrCodeBatch;
use App\Models\User;
use App\Services\Qr\BatchGenerator;
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 ScanTest extends TestCase
{
use RefreshDatabase;
private User $scanner;
private DropOffPoint $dop;
private Household $household;
private QrCode $activeCode;
protected function setUp(): void
{
parent::setUp();
$this->seed([RoleSeeder::class, SamplePsgcSeeder::class, SampleDropOffPointsSeeder::class]);
$this->scanner = User::factory()->create(['role' => User::ROLE_SCANNER, 'status' => 'active']);
$this->dop = DropOffPoint::where('code', 'DOP-QC-DLM-01')->firstOrFail();
$this->household = Household::factory()->create(['barangay_id' => $this->dop->barangay_id]);
$batch = app(BatchGenerator::class)->generate(3, QrCodeBatch::PURPOSE_FREE);
$this->activeCode = $batch->codes()->first();
$this->activeCode->forceFill([
'assigned_to_household_id' => $this->household->id,
'status' => 'active',
'allocated_at' => now(),
'activated_at' => now(),
])->save();
}
public function test_valid_scan_at_dop_marks_code_used(): void
{
Sanctum::actingAs($this->scanner);
$response = $this->postJson('/api/v1/scanner/scan', [
'serial' => $this->activeCode->serial,
'drop_off_point_id' => $this->dop->id,
'lat' => 14.6539,
'lng' => 121.0685,
'weight_kg' => 5,
'waste_type' => 'general',
]);
$response->assertOk()
->assertJsonPath('data.status', 'used');
$this->assertDatabaseHas('collection_logs', [
'qr_code_id' => $this->activeCode->id,
'verification_status' => 'valid',
]);
}
public function test_scan_far_from_dop_rejected(): void
{
Sanctum::actingAs($this->scanner);
$response = $this->postJson('/api/v1/scanner/scan', [
'serial' => $this->activeCode->serial,
'drop_off_point_id' => $this->dop->id,
'lat' => 8.0,
'lng' => 124.0,
]);
$response->assertStatus(422);
$this->assertStringStartsWith('out_of_range', $response->json('errors.reason.0'));
}
public function test_unknown_serial_rejected(): void
{
Sanctum::actingAs($this->scanner);
$response = $this->postJson('/api/v1/scanner/scan', [
'serial' => 'PH-XX-9999-9999-999999-XX',
'drop_off_point_id' => $this->dop->id,
'lat' => 14.6539, 'lng' => 121.0685,
]);
$response->assertStatus(422)
->assertJsonPath('errors.reason.0', 'not_found');
}
public function test_already_used_serial_rejected_as_duplicate(): void
{
$this->activeCode->forceFill(['status' => 'used', 'used_at' => now()])->save();
Sanctum::actingAs($this->scanner);
$response = $this->postJson('/api/v1/scanner/scan', [
'serial' => $this->activeCode->serial,
'drop_off_point_id' => $this->dop->id,
'lat' => 14.6539, 'lng' => 121.0685,
]);
$response->assertStatus(422)
->assertJsonPath('errors.reason.0', 'duplicate');
}
public function test_unassigned_code_rejected(): void
{
$this->activeCode->forceFill(['status' => 'unassigned'])->save();
Sanctum::actingAs($this->scanner);
$response = $this->postJson('/api/v1/scanner/scan', [
'serial' => $this->activeCode->serial,
'drop_off_point_id' => $this->dop->id,
'lat' => 14.6539, 'lng' => 121.0685,
]);
$response->assertStatus(422);
}
public function test_bulk_scan_processes_each_independently(): void
{
$batch = app(BatchGenerator::class)->generate(3, QrCodeBatch::PURPOSE_FREE);
$batch->codes()->update([
'assigned_to_household_id' => $this->household->id,
'status' => 'active',
]);
$codes = $batch->codes()->limit(2)->get();
Sanctum::actingAs($this->scanner);
$response = $this->postJson('/api/v1/scanner/scan/bulk', [
'scans' => [
['serial' => $codes[0]->serial, 'drop_off_point_id' => $this->dop->id, 'lat' => 14.6539, 'lng' => 121.0685],
['serial' => $codes[1]->serial, 'drop_off_point_id' => $this->dop->id, 'lat' => 14.6539, 'lng' => 121.0685],
['serial' => 'PH-bogus', 'drop_off_point_id' => $this->dop->id, 'lat' => 14.6539, 'lng' => 121.0685],
],
]);
$response->assertOk()
->assertJsonPath('data.accepted_count', 2)
->assertJsonPath('data.rejected_count', 1);
}
public function test_resident_blocked_from_scanning(): void
{
$resident = User::factory()->create(['role' => User::ROLE_RESIDENT, 'status' => 'active']);
Sanctum::actingAs($resident);
$this->postJson('/api/v1/scanner/scan', [
'serial' => $this->activeCode->serial,
'drop_off_point_id' => $this->dop->id,
'lat' => 14.6539, 'lng' => 121.0685,
])->assertStatus(403);
}
}