migrant-web/tests/Feature/WorkerJourneyApiTest.php
2026-06-03 14:22:56 +05:30

411 lines
13 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Worker;
use App\Models\WorkerCategory;
use App\Models\WorkerDocument;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class WorkerJourneyApiTest extends TestCase
{
use RefreshDatabase;
protected $category;
protected function setUp(): void
{
parent::setUp();
// Create standard General Helper category
\Illuminate\Support\Facades\DB::table('worker_categories')->insert([
'id' => 7,
'name' => 'General Helper',
'created_at' => now(),
'updated_at' => now(),
]);
$this->category = (object) ['id' => 7];
}
/**
* Test S1 Onboard: Get Config and Supported Languages.
*/
public function test_s1_get_languages_config()
{
$response = $this->getJson('/api/workers/config');
$response->assertStatus(200)
->assertJson([
'success' => true,
'languages' => [
['code' => 'HI', 'name' => 'Hindi (हिन्दी)'],
['code' => 'SW', 'name' => 'Swahili (Kiswahili)'],
['code' => 'TL', 'name' => 'Tagalog (Wikang Tagalog)'],
['code' => 'TA', 'name' => 'Tamil (தமிழ்)'],
]
]);
}
/**
* Test Get Skills Master API.
*/
public function test_get_skills_master_api()
{
// Seed some master skills
\Illuminate\Support\Facades\DB::table('skills')->insert([
['name' => 'Cooking', 'created_at' => now(), 'updated_at' => now()],
['name' => 'Cleaning', 'created_at' => now(), 'updated_at' => now()],
]);
$response = $this->getJson('/api/workers/skills');
$response->assertStatus(200)
->assertJson([
'success' => true,
])
->assertJsonStructure([
'success',
'skills' => [
'*' => ['id', 'name']
]
]);
}
/**
* Test S1 Onboard: Send and Verify OTP.
*/
public function test_s1_otp_flow()
{
$phone = '+971501234567';
// 1. Send OTP
$sendResponse = $this->postJson('/api/workers/send-otp', [
'phone' => $phone,
]);
$sendResponse->assertStatus(200)
->assertJson([
'success' => true,
'identifier' => $phone,
]);
// Verify OTP is stored in cache
$cached = Cache::get('worker_otp_' . $phone);
$this->assertNotNull($cached);
$this->assertEquals('111111', $cached['code']); // Dev default OTP
// 2. Verify OTP
$verifyResponse = $this->postJson('/api/workers/verify-otp', [
'phone' => $phone,
'otp' => '111111',
]);
$verifyResponse->assertStatus(200)
->assertJson([
'success' => true,
'identifier' => $phone,
]);
// Verify the gate cache is present
$this->assertTrue(Cache::get('worker_otp_verified_' . $phone));
}
/**
* Test S1 Onboard: Complete Basic Profile Setup.
*/
public function test_s1_complete_basic_profile_setup()
{
$phone = '+971501234567';
// Bypass OTP by seeding the cache gate
Cache::put('worker_otp_verified_' . $phone, true, 3600);
$response = $this->postJson('/api/workers/setup-profile', [
'phone' => $phone,
'name' => 'Rahul Sharma',
'nationality' => 'India',
'language' => 'HI',
'skills' => [],
]);
$response->assertStatus(201)
->assertJsonStructure([
'success',
'message',
'data' => [
'worker' => [
'id',
'name',
'email',
'phone',
'language',
'verified',
'status',
],
'token',
]
]);
$this->assertDatabaseHas('workers', [
'name' => 'Rahul Sharma',
'phone' => $phone,
'language' => 'HI',
'verified' => false,
'status' => 'active',
]);
// Gate cache should be purged
$this->assertNull(Cache::get('worker_otp_verified_' . $phone));
}
/**
* Test S2 Profile: Add Experience & Preferences.
*/
public function test_s2_update_experience_and_preferences()
{
$worker = Worker::create([
'name' => 'John Swahili',
'email' => 'john@example.com',
'phone' => '+971509999999',
'language' => 'SW',
'password' => bcrypt('password'),
'nationality' => 'Kenya',
'age' => 22,
'salary' => 1200,
'availability' => 'Immediate',
'experience' => 'None',
'religion' => 'Christian',
'bio' => 'New helper',
'category_id' => $this->category->id,
'verified' => false,
'status' => 'active',
'api_token' => 'test-bearer-token-123',
]);
$response = $this->withHeaders([
'Authorization' => 'Bearer test-bearer-token-123',
])->postJson('/api/workers/profile/update', [
'experience' => '3 Years Cooking & Childcare',
'salary' => 1800,
'bio' => 'Extremely experienced in Arabic and Western cooking.',
]);
$response->assertStatus(200);
$this->assertDatabaseHas('workers', [
'id' => $worker->id,
'experience' => '3 Years Cooking & Childcare',
'salary' => 1800,
]);
}
/**
* Test S2 Profile: Upload Emirates ID & PDPL Security Purge & Verified Badge Awarded.
*/
public function test_s2_upload_emirates_id_and_pdpl_secure_purge()
{
Storage::fake('local');
$worker = Worker::create([
'name' => 'Juan Dela Cruz',
'email' => 'juan@example.com',
'phone' => '+971507777777',
'language' => 'TL',
'password' => bcrypt('password'),
'nationality' => 'Philippines',
'age' => 28,
'salary' => 1500,
'availability' => 'Immediate',
'experience' => 'Not Specified',
'religion' => 'Not Specified',
'bio' => 'Looking for helper job',
'category_id' => $this->category->id,
'verified' => false,
'status' => 'active',
'api_token' => 'test-bearer-token-456',
]);
$fakeIdFile = UploadedFile::fake()->create('emirates_id.jpg', 800, 'image/jpeg');
$response = $this->withHeaders([
'Authorization' => 'Bearer test-bearer-token-456',
])->postJson('/api/workers/profile/upload-emirates-id', [
'emirates_id_file' => $fakeIdFile,
]);
$response->assertStatus(200)
->assertJsonStructure([
'success',
'message',
'ocr_extracted_data' => [
'name',
'emirates_id_number',
'expiry_date',
],
'data' => [
'worker',
'document',
]
]);
// Asserts database states
$this->assertDatabaseHas('workers', [
'id' => $worker->id,
'verified' => true, // Badge awarded!
]);
$this->assertDatabaseHas('worker_documents', [
'worker_id' => $worker->id,
'type' => 'emirates_id',
'file_path' => '[DELETED_FOR_PDPL_COMPLIANCE]', // Image file removed for PDPL
]);
// Ensure physical file is deleted and not stored in storage disk
Storage::disk('local')->assertDirectoryEmpty('temp/documents');
}
/**
* Test S3 Go Live: Status set to Active.
*/
public function test_s3_go_live()
{
$worker = Worker::create([
'name' => 'Selvi Tamil',
'email' => 'selvi@example.com',
'phone' => '+971508888888',
'language' => 'TA',
'password' => bcrypt('password'),
'nationality' => 'Sri Lanka',
'age' => 32,
'salary' => 1400,
'availability' => 'Not Available',
'experience' => 'Not Specified',
'religion' => 'Not Specified',
'bio' => 'Ready to work',
'category_id' => $this->category->id,
'verified' => true,
'status' => 'inactive',
'api_token' => 'test-bearer-token-789',
]);
$response = $this->withHeaders([
'Authorization' => 'Bearer test-bearer-token-789',
])->postJson('/api/workers/go-live');
$response->assertStatus(200)
->assertJsonPath('data.worker.status', 'active')
->assertJsonPath('data.worker.availability', 'Immediate');
$this->assertDatabaseHas('workers', [
'id' => $worker->id,
'status' => 'active',
'availability' => 'Immediate',
]);
}
/**
* Test S5 Avail?: Toggle availability (Still looking?).
*/
public function test_s5_toggle_availability_still_looking()
{
$worker = Worker::create([
'name' => 'Rahul Sharma',
'email' => 'rahul@example.com',
'phone' => '+971501234567',
'language' => 'HI',
'password' => bcrypt('password'),
'nationality' => 'India',
'age' => 25,
'salary' => 1500,
'availability' => 'Immediate',
'experience' => 'Not Specified',
'religion' => 'Not Specified',
'bio' => 'Test',
'category_id' => $this->category->id,
'verified' => true,
'status' => 'active',
'api_token' => 'test-bearer-token-abc',
]);
// Toggle still_looking = false (NO -> Hidden)
$response1 = $this->withHeaders([
'Authorization' => 'Bearer test-bearer-token-abc',
])->postJson('/api/workers/profile/toggle-availability', [
'still_looking' => false,
]);
$response1->assertStatus(200)
->assertJson([
'success' => true,
'still_looking' => false,
'status' => 'hidden',
]);
$this->assertDatabaseHas('workers', [
'id' => $worker->id,
'status' => 'hidden',
]);
// Toggle still_looking = true (YES -> Visible/Active)
$response2 = $this->withHeaders([
'Authorization' => 'Bearer test-bearer-token-abc',
])->postJson('/api/workers/profile/toggle-availability', [
'still_looking' => true,
]);
$response2->assertStatus(200)
->assertJson([
'success' => true,
'still_looking' => true,
'status' => 'active',
]);
$this->assertDatabaseHas('workers', [
'id' => $worker->id,
'status' => 'active',
]);
}
/**
* Test S6 Outcome: Marked as Hired (Profile removed from search - auto-hidden).
*/
public function test_s6_outcome_mark_hired()
{
$worker = Worker::create([
'name' => 'Rahul Sharma',
'email' => 'rahul@example.com',
'phone' => '+971501234567',
'language' => 'HI',
'password' => bcrypt('password'),
'nationality' => 'India',
'age' => 25,
'salary' => 1500,
'availability' => 'Immediate',
'experience' => 'Not Specified',
'religion' => 'Not Specified',
'bio' => 'Test',
'category_id' => $this->category->id,
'verified' => true,
'status' => 'active',
'api_token' => 'test-bearer-token-abc',
]);
$response = $this->withHeaders([
'Authorization' => 'Bearer test-bearer-token-abc',
])->postJson('/api/workers/profile/mark-hired');
$response->assertStatus(200)
->assertJsonPath('data.worker.status', 'Hired');
$this->assertDatabaseHas('workers', [
'id' => $worker->id,
'status' => 'Hired',
]);
}
}