migrant-web/tests/Feature/WorkerJobApiTest.php

507 lines
19 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\User;
use App\Models\Worker;
use App\Models\JobPost;
use App\Models\JobApplication;
use App\Models\Plan;
use App\Models\Subscription;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class WorkerJobApiTest extends TestCase
{
use RefreshDatabase;
protected $employer;
protected $worker;
protected $plan;
protected function setUp(): void
{
parent::setUp();
// Get standard plan
$this->plan = Plan::find('premium');
// Create employer with active subscription
$this->employer = User::create([
'name' => 'Employer User',
'email' => 'employer@example.com',
'password' => bcrypt('password'),
'role' => 'employer',
'subscription_status' => 'active',
'subscription_expires_at' => now()->addDays(30),
'api_token' => 'employer_api_token',
]);
Subscription::create([
'user_id' => $this->employer->id,
'plan_id' => 'premium',
'amount_aed' => 100.00,
'starts_at' => now(),
'expires_at' => now()->addDays(30),
'status' => 'active',
]);
// Create worker
$this->worker = Worker::create([
'name' => 'Worker User',
'email' => 'worker@example.com',
'password' => bcrypt('password'),
'phone' => '971501234567',
'nationality' => 'Indian',
'status' => 'active',
'api_token' => 'worker_api_token',
'age' => 25,
'salary' => 2000,
'availability' => 'Immediate',
'experience' => '2 Years',
'religion' => 'Christian',
'bio' => 'Helper info',
'passport_status' => 'valid',
]);
}
public function test_employer_job_crud_web()
{
// 1. Create Job (POST /employer/jobs/create)
$response = $this->actingAs($this->employer)
->withSession(['user' => $this->employer])
->post('/employer/jobs/create', [
'title' => 'Test Mason Job',
'workers_needed' => 3,
'location' => 'Dubai',
'salary' => 2000,
'job_type' => 'Full Time',
'start_date' => now()->addDays(5)->format('Y-m-d'),
'description' => 'Test mason job description',
'requirements' => 'Must have experience',
]);
$response->assertRedirect('/employer/jobs');
$this->assertDatabaseHas('job_posts', [
'employer_id' => $this->employer->id,
'title' => 'Test Mason Job',
'location' => 'Dubai',
'status' => 'active',
]);
$job = JobPost::first();
// 2. View Job Details (GET /employer/jobs/{id})
$response = $this->actingAs($this->employer)
->withSession(['user' => $this->employer])
->get("/employer/jobs/{$job->id}");
$response->assertStatus(200);
// 3. Edit Job (POST /employer/jobs/{id}/edit)
$response = $this->actingAs($this->employer)
->withSession(['user' => $this->employer])
->post("/employer/jobs/{$job->id}/edit", [
'title' => 'Updated Mason Job',
'workers_needed' => 2,
'location' => 'Abu Dhabi',
'salary' => 2500,
'job_type' => 'Part Time',
'start_date' => now()->addDays(10)->format('Y-m-d'),
'description' => 'Updated description',
'requirements' => 'Must speak English',
'status' => 'closed',
]);
$response->assertRedirect('/employer/jobs');
$this->assertDatabaseHas('job_posts', [
'id' => $job->id,
'title' => 'Updated Mason Job',
'status' => 'closed',
]);
// 4. Delete Job (DELETE /employer/jobs/{id})
$response = $this->actingAs($this->employer)
->withSession(['user' => $this->employer])
->delete("/employer/jobs/{$job->id}");
$response->assertRedirect('/employer/jobs');
$this->assertSoftDeleted('job_posts', [
'id' => $job->id,
]);
}
public function test_worker_job_apis()
{
// Setup an active job post
$job = JobPost::create([
'employer_id' => $this->employer->id,
'title' => 'Available Plumber Job',
'location' => 'Dubai Marina',
'salary' => 3000,
'workers_needed' => 1,
'job_type' => 'Contract',
'start_date' => now()->addDays(2),
'description' => 'Plumber description',
'status' => 'active',
]);
// 1. List available jobs
$response = $this->getJson('/api/workers/jobs', [
'Authorization' => 'Bearer worker_api_token'
]);
$response->assertStatus(200);
$response->assertJsonPath('success', true);
$response->assertJsonCount(1, 'data.jobs');
$response->assertJsonPath('data.jobs.0.title', 'Available Plumber Job');
// 2. Retrieve job details
$response = $this->getJson("/api/workers/jobs/{$job->id}", [
'Authorization' => 'Bearer worker_api_token'
]);
$response->assertStatus(200);
$response->assertJsonPath('success', true);
$response->assertJsonPath('data.job.title', 'Available Plumber Job');
// 3. Apply for job
$response = $this->postJson("/api/workers/jobs/{$job->id}/apply", [], [
'Authorization' => 'Bearer worker_api_token'
]);
$response->assertStatus(201);
$response->assertJsonPath('success', true);
$this->assertDatabaseHas('job_applications', [
'worker_id' => $this->worker->id,
'job_id' => $job->id,
'status' => 'applied',
]);
// 4. Prevent duplicate job applications
$response = $this->postJson("/api/workers/jobs/{$job->id}/apply", [], [
'Authorization' => 'Bearer worker_api_token'
]);
$response->assertStatus(409);
$response->assertJsonPath('success', false);
$response->assertJsonPath('message', 'You have already applied for this job.');
// 5. View applied jobs
$response = $this->getJson('/api/workers/applied-jobs', [
'Authorization' => 'Bearer worker_api_token'
]);
$response->assertStatus(200);
$response->assertJsonPath('success', true);
$response->assertJsonCount(1, 'data.applications');
$response->assertJsonPath('data.applications.0.job.title', 'Available Plumber Job');
$application = JobApplication::first();
// 6. Employer view applicants for job
$response = $this->getJson("/api/employers/jobs/{$job->id}/applicants", [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(200);
$response->assertJsonPath('success', true);
$response->assertJsonCount(1, 'data.applicants');
$response->assertJsonPath('data.applicants.0.worker.name', 'Worker User');
// 7. Employer update application status
$response = $this->postJson("/api/employers/applications/{$application->id}/status", [
'status' => 'shortlisted'
], [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(200);
$response->assertJsonPath('success', true);
$this->assertDatabaseHas('job_applications', [
'id' => $application->id,
'status' => 'shortlisted',
]);
}
public function test_employer_job_apis_and_access_restrictions()
{
// 1. List jobs via API (premium plan by default)
$response = $this->getJson('/api/employers/jobs', [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(200);
$response->assertJsonPath('success', true);
$response->assertJsonCount(0, 'data.jobs');
// 2. Create job via API
$response = $this->postJson('/api/employers/jobs', [
'title' => 'API Clean Job',
'workers_needed' => 3,
'location' => 'Dubai JLT',
'salary' => 2200,
'job_type' => 'Full Time',
'start_date' => now()->addDays(3)->format('Y-m-d'),
'description' => 'Clean job description',
'requirements' => 'Requirements content'
], [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(201);
$response->assertJsonPath('success', true);
$jobId = $response->json('data.job.id');
$this->assertDatabaseHas('job_posts', [
'id' => $jobId,
'title' => 'API Clean Job',
'location' => 'Dubai JLT',
]);
// 3. Get job detail via API
$response = $this->getJson("/api/employers/jobs/{$jobId}", [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(200);
$response->assertJsonPath('data.job.title', 'API Clean Job');
// 4. Update job via API
$response = $this->postJson("/api/employers/jobs/{$jobId}/update", [
'title' => 'Updated API Clean Job',
'workers_needed' => 4,
'location' => 'Dubai Marina',
'salary' => 2500,
'job_type' => 'Contract',
'start_date' => now()->addDays(4)->format('Y-m-d'),
'description' => 'Updated clean job description',
'requirements' => 'Updated requirements',
'status' => 'draft'
], [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(200);
$response->assertJsonPath('data.job.title', 'Updated API Clean Job');
$response->assertJsonPath('data.job.status', 'draft');
// 5. Delete job via API
$response = $this->deleteJson("/api/employers/jobs/{$jobId}", [], [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(200);
$response->assertJsonPath('success', true);
$this->assertSoftDeleted('job_posts', [
'id' => $jobId
]);
// 6. Test basic plan restrictions (no job access)
Subscription::where('user_id', $this->employer->id)->update(['plan_id' => 'basic']);
// Check List Jobs block
$response = $this->getJson('/api/employers/jobs', [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(403);
$response->assertJsonPath('success', false);
$response->assertJsonPath('message', 'Your current plan does not support the Job Apply module. Please upgrade to a Premium plan to access this feature.');
// Check Create Job block
$response = $this->postJson('/api/employers/jobs', [
'title' => 'Blocked Job'
], [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(403);
// Check Get Detail block
$response = $this->getJson("/api/employers/jobs/{$jobId}", [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(403);
// Check Update block
$response = $this->postJson("/api/employers/jobs/{$jobId}/update", [
'title' => 'Blocked Job'
], [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(403);
// Check Delete block
$response = $this->deleteJson("/api/employers/jobs/{$jobId}", [], [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(403);
// Check Applicants block
$response = $this->getJson("/api/employers/jobs/{$jobId}/applicants", [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(403);
// Check Update status block
$response = $this->postJson("/api/employers/applications/1/status", [
'status' => 'shortlisted'
], [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(403);
// 7. Test contact limits enforcement (both Web and API)
// Set plan limits to max 1 contacted worker
Subscription::where('user_id', $this->employer->id)->update(['plan_id' => 'premium']);
$premiumPlan = Plan::find('premium');
$premiumPlan->update([
'unlimited_contacts' => false,
'max_contact_people' => 1
]);
// Create 2 workers
$workerA = Worker::create([
'name' => 'Worker A',
'email' => 'workera@example.com',
'phone' => '971501234568',
'nationality' => 'Indian',
'status' => 'active',
'age' => 25,
'salary' => 2000,
'availability' => 'Immediate',
'experience' => '2 Years',
'religion' => 'Christian',
'bio' => 'Helper A',
]);
$workerB = Worker::create([
'name' => 'Worker B',
'email' => 'workerb@example.com',
'phone' => '971501234569',
'nationality' => 'Indian',
'status' => 'active',
'age' => 25,
'salary' => 2000,
'availability' => 'Immediate',
'experience' => '2 Years',
'religion' => 'Christian',
'bio' => 'Helper B',
]);
// Create an active job for testing applications status updates
$activeJob = JobPost::create([
'employer_id' => $this->employer->id,
'title' => 'Active Job Limit Test',
'location' => 'Dubai',
'salary' => 2000,
'workers_needed' => 5,
'job_type' => 'Full Time',
'start_date' => now()->addDays(5),
'description' => 'Test description',
'status' => 'active',
]);
$appA = JobApplication::create([
'job_id' => $activeJob->id,
'worker_id' => $workerA->id,
'status' => 'applied'
]);
$appB = JobApplication::create([
'job_id' => $activeJob->id,
'worker_id' => $workerB->id,
'status' => 'applied'
]);
// First contact (Worker A) -> Should succeed
$response = $this->postJson("/api/employers/applications/{$appA->id}/status", [
'status' => 'shortlisted'
], [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(200);
$this->assertEquals(1, User::contactedWorkersCount($this->employer->id));
// Second contact (Worker B) -> Should fail with 403 (contact limit reached)
$response = $this->postJson("/api/employers/applications/{$appB->id}/status", [
'status' => 'shortlisted'
], [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(403);
$response->assertJsonPath('success', false);
$response->assertJsonFragment([
'message' => 'You have reached your contacted workers limit of 1 under your active plan. Please upgrade or renew your subscription to contact more workers.'
]);
// Web status update: first check web login and update status
// Web: contact limit block redirect
$response = $this->actingAs($this->employer)
->withSession(['user' => $this->employer])
->post("/employer/candidates/{$appB->id}/status", [
'status' => 'Offer Sent'
]);
$response->assertRedirect('/employer/subscription');
$response->assertSessionHas('error', 'You have reached your contacted workers limit of 1 under your active plan. Please upgrade or renew your subscription to contact more workers.');
}
public function test_job_hiring_workflow_enhancements()
{
// 1. Create a job
$job = JobPost::create([
'employer_id' => $this->employer->id,
'title' => 'Workflow Developer Job',
'location' => 'Dubai Marina',
'salary' => 5000,
'workers_needed' => 1,
'job_type' => 'Full Time',
'start_date' => now()->addDays(2),
'description' => 'Developer description',
'status' => 'active',
]);
// 2. Worker applies
$response = $this->postJson("/api/workers/jobs/{$job->id}/apply", [], [
'Authorization' => 'Bearer worker_api_token'
]);
$response->assertStatus(201);
$app = JobApplication::first();
// 3. Worker views details of application
$response = $this->getJson("/api/workers/applied-jobs/{$app->id}", [
'Authorization' => 'Bearer worker_api_token'
]);
$response->assertStatus(200);
$response->assertJsonPath('success', true);
$response->assertJsonPath('data.application.status', 'applied');
$response->assertJsonCount(0, 'data.application.status_history');
// 4. Employer updates status to shortlisted with notes
$response = $this->postJson("/api/employers/applications/{$app->id}/status", [
'status' => 'shortlisted',
'notes' => 'Very qualified candidate.'
], [
'Authorization' => 'Bearer employer_api_token'
]);
$response->assertStatus(200);
$response->assertJsonPath('success', true);
$response->assertJsonPath('data.application.status', 'shortlisted');
$response->assertJsonPath('data.application.notes', 'Very qualified candidate.');
$response->assertJsonCount(1, 'data.application.status_history');
$response->assertJsonPath('data.application.status_history.0.status', 'shortlisted');
$response->assertJsonPath('data.application.status_history.0.notes', 'Very qualified candidate.');
// 5. Verify saved workers sync: worker is now saved (shortlisted)
$this->assertDatabaseHas('shortlists', [
'employer_id' => $this->employer->id,
'worker_id' => $this->worker->id,
]);
// 6. Worker withdraws application -> should fail because status is shortlisted (not applied)
$response = $this->postJson("/api/workers/applied-jobs/{$app->id}/withdraw", [], [
'Authorization' => 'Bearer worker_api_token'
]);
$response->assertStatus(400);
// 7. Reset application status to applied to test withdraw
$app->refresh();
$app->update(['status' => 'applied']);
$response = $this->postJson("/api/workers/applied-jobs/{$app->id}/withdraw", [], [
'Authorization' => 'Bearer worker_api_token'
]);
$response->assertStatus(200);
$this->assertDatabaseMissing('job_applications', [
'id' => $app->id
]);
}
}