Files
Verde-Web/tests/Feature/Api/V1/Customer/CustomerEndpointsTest.php

176 lines
6.8 KiB
PHP

<?php
namespace Tests\Feature\Api\V1\Customer;
use App\Models\CollectionLog;
use App\Models\CollectionTeam;
use App\Models\DropOffPoint;
use App\Models\Household;
use App\Models\PartnerStore;
use App\Models\QrCodeBatch;
use App\Models\Route;
use App\Models\RouteStop;
use App\Models\Trip;
use App\Models\User;
use App\Notifications\HouseholdApproved;
use App\Services\Qr\BatchGenerator;
use Database\Seeders\RoleSeeder;
use Database\Seeders\SampleDropOffPointsSeeder;
use Database\Seeders\SamplePsgcSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Notification;
use Laravel\Sanctum\Sanctum;
use MatanYadaev\EloquentSpatial\Objects\Point;
use Tests\TestCase;
class CustomerEndpointsTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->seed([RoleSeeder::class, SamplePsgcSeeder::class, SampleDropOffPointsSeeder::class]);
}
public function test_partner_stores_nearby_returns_active_stores_sorted_by_distance(): void
{
$closer = PartnerStore::factory()->create([
'business_name' => 'Closer',
'status' => 'active',
'coordinates' => new Point(14.6539, 121.0685, 4326),
]);
$farther = PartnerStore::factory()->create([
'business_name' => 'Farther',
'status' => 'active',
'coordinates' => new Point(14.7, 121.1, 4326),
]);
PartnerStore::factory()->create([
'business_name' => 'Suspended',
'status' => 'suspended',
'coordinates' => new Point(14.6539, 121.0685, 4326),
]);
$response = $this->getJson('/api/v1/partner-stores/nearby?lat=14.6539&lng=121.0685&radius_km=10');
$response->assertOk();
$names = collect($response->json('data'))->pluck('business_name')->all();
$this->assertSame(['Closer', 'Farther'], $names);
$this->assertNotContains('Suspended', $names);
}
public function test_me_collections_returns_only_my_household_logs(): void
{
$resident = User::factory()->create(['role' => User::ROLE_RESIDENT, 'status' => 'active']);
$myHousehold = Household::factory()->create(['head_user_id' => $resident->id]);
$otherHousehold = Household::factory()->create();
$dop = DropOffPoint::first();
$batch = app(BatchGenerator::class)->generate(2, QrCodeBatch::PURPOSE_FREE);
$codes = $batch->codes()->get();
CollectionLog::create([
'qr_code_id' => $codes[0]->id,
'household_id' => $myHousehold->id,
'drop_off_point_id' => $dop->id,
'scanned_at' => now()->subHour(),
'coordinates_at_scan' => new Point(14.6, 121.0, 4326),
'weight_kg' => 5,
'verification_status' => 'valid',
]);
CollectionLog::create([
'qr_code_id' => $codes[1]->id,
'household_id' => $otherHousehold->id,
'drop_off_point_id' => $dop->id,
'scanned_at' => now(),
'coordinates_at_scan' => new Point(14.6, 121.0, 4326),
'weight_kg' => 7,
'verification_status' => 'valid',
]);
Sanctum::actingAs($resident);
$response = $this->getJson('/api/v1/me/collections');
$response->assertOk();
$items = $response->json('data');
$this->assertCount(1, $items);
$this->assertSame(5, $items[0]['weight_kg']);
}
public function test_upcoming_pickups_finds_trips_for_my_dop(): void
{
$resident = User::factory()->create(['role' => User::ROLE_RESIDENT, 'status' => 'active']);
$dop = DropOffPoint::where('code', 'DOP-QC-DLM-01')->firstOrFail();
Household::factory()->create([
'head_user_id' => $resident->id,
'assigned_drop_off_point_id' => $dop->id,
]);
$driver = User::factory()->create(['role' => User::ROLE_DRIVER]);
$team = CollectionTeam::create(['name' => 'T', 'driver_id' => $driver->id, 'status' => 'active']);
$route = Route::create(['name' => 'R', 'code' => 'RT-UC', 'status' => 'active']);
RouteStop::create(['route_id' => $route->id, 'drop_off_point_id' => $dop->id, 'sequence' => 1]);
// Tomorrow trip — should appear
Trip::create([
'trip_number' => 'TRIP-T1', 'route_id' => $route->id, 'team_id' => $team->id,
'scheduled_date' => now()->addDay()->toDateString(), 'status' => 'scheduled',
]);
// Past completed — should not
Trip::create([
'trip_number' => 'TRIP-T0', 'route_id' => $route->id, 'team_id' => $team->id,
'scheduled_date' => now()->subDay()->toDateString(), 'status' => 'completed',
]);
Sanctum::actingAs($resident);
$response = $this->getJson('/api/v1/me/upcoming-pickups');
$response->assertOk()
->assertJsonPath('data.household_assigned', true)
->assertJsonPath('data.drop_off_point.name', $dop->name);
$this->assertCount(1, $response->json('data.pickups'));
}
public function test_upcoming_pickups_empty_when_no_household(): void
{
$resident = User::factory()->create(['role' => User::ROLE_RESIDENT, 'status' => 'active']);
Sanctum::actingAs($resident);
$this->getJson('/api/v1/me/upcoming-pickups')
->assertOk()
->assertJsonPath('data.household_assigned', false);
}
public function test_my_notifications_inbox_paginates_and_marks_read(): void
{
$resident = User::factory()->create(['role' => User::ROLE_RESIDENT, 'status' => 'active']);
$h = Household::factory()->create(['head_user_id' => $resident->id]);
Notification::send($resident, new HouseholdApproved($h, 10));
Sanctum::actingAs($resident);
$list = $this->getJson('/api/v1/me/notifications');
$list->assertOk();
$this->assertCount(1, $list->json('data'));
$this->assertSame(1, $list->json('meta.unread_count'));
$id = $list->json('data.0.id');
$this->postJson("/api/v1/me/notifications/{$id}/read")
->assertOk();
$this->getJson('/api/v1/me/notifications/unread-count')
->assertOk()
->assertJsonPath('data.unread_count', 0);
}
public function test_mark_all_read(): void
{
$resident = User::factory()->create(['role' => User::ROLE_RESIDENT, 'status' => 'active']);
$h = Household::factory()->create(['head_user_id' => $resident->id]);
Notification::send($resident, new HouseholdApproved($h, 5));
Notification::send($resident, new HouseholdApproved($h, 7));
Sanctum::actingAs($resident);
$r = $this->postJson('/api/v1/me/notifications/mark-all-read');
$r->assertOk()->assertJsonPath('data.marked_read', 2);
}
}