827 lines
32 KiB
PHP
827 lines
32 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',
|
|
'main_profession' => 'Plumber',
|
|
]);
|
|
|
|
$skill = \App\Models\Skill::firstOrCreate(['name' => 'Piping']);
|
|
$this->worker->skills()->sync([$skill->id]);
|
|
}
|
|
|
|
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',
|
|
'main_profession' => 'Plumber',
|
|
'skills' => 'Piping',
|
|
'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',
|
|
'main_profession' => 'Plumber',
|
|
'skills' => 'Piping',
|
|
'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',
|
|
'main_profession' => 'Plumber',
|
|
'location' => 'Dubai Marina',
|
|
'salary' => 3000,
|
|
'workers_needed' => 1,
|
|
'job_type' => 'Contract',
|
|
'start_date' => now()->addDays(2),
|
|
'description' => 'Plumber description',
|
|
'status' => 'active',
|
|
]);
|
|
$skill = \App\Models\Skill::firstOrCreate(['name' => 'Piping']);
|
|
$job->skills()->sync([$skill->id]);
|
|
|
|
// 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');
|
|
$response->assertJsonPath('data.jobs.0.posted_by', 'Employer User');
|
|
$response->assertJsonPath('data.jobs.0.status', 'new');
|
|
|
|
// 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');
|
|
$response->assertJsonPath('data.job.posted_by', 'Employer User');
|
|
$response->assertJsonPath('data.job.status', 'new');
|
|
|
|
// 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',
|
|
]);
|
|
|
|
// Verify status becomes 'applied' in list available jobs
|
|
$response = $this->getJson('/api/workers/jobs', [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
$response->assertJsonPath('data.jobs.0.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');
|
|
$response->assertJsonPath('data.applications.0.job.posted_by', 'Employer User');
|
|
|
|
$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',
|
|
'main_profession' => 'Plumber',
|
|
'skills' => 'Piping',
|
|
'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',
|
|
'main_profession' => 'Plumber',
|
|
'skills' => 'Piping',
|
|
'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->assertJsonPath('data.application.job.posted_by', 'Employer User');
|
|
$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
|
|
]);
|
|
}
|
|
|
|
public function test_hired_worker_visibility_and_profile_access()
|
|
{
|
|
// Create EmployerProfile with a phone number
|
|
\App\Models\EmployerProfile::create([
|
|
'user_id' => $this->employer->id,
|
|
'company_name' => 'Premium Sponsoring Group',
|
|
'phone' => '971501234567',
|
|
'city' => 'Dubai',
|
|
'nationality' => 'UAE',
|
|
]);
|
|
|
|
// 1. Create a job post
|
|
$job = JobPost::create([
|
|
'employer_id' => $this->employer->id,
|
|
'title' => 'Special Hired Job',
|
|
'main_profession' => 'Plumber',
|
|
'location' => 'Dubai Jumeirah',
|
|
'salary' => 4500,
|
|
'workers_needed' => 1,
|
|
'job_type' => 'Full Time',
|
|
'start_date' => now()->addDays(5)->format('Y-m-d'),
|
|
'description' => 'A special job details description',
|
|
'status' => 'active',
|
|
]);
|
|
$skill = \App\Models\Skill::firstOrCreate(['name' => 'Piping']);
|
|
$job->skills()->sync([$skill->id]);
|
|
|
|
// 2. Worker applies for the job
|
|
$response = $this->postJson("/api/workers/jobs/{$job->id}/apply", [], [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(201);
|
|
$app = JobApplication::latest()->first();
|
|
|
|
// 3. Employer hires the worker by setting status to 'hired' (lowercase, as in the database)
|
|
$response = $this->postJson("/api/employers/applications/{$app->id}/status", [
|
|
'status' => 'hired',
|
|
'notes' => 'Hired through job flow'
|
|
], [
|
|
'Authorization' => 'Bearer employer_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
|
|
// Under two-way confirmation, it should be in hire_requested status
|
|
$this->assertDatabaseHas('job_applications', [
|
|
'id' => $app->id,
|
|
'status' => 'hire_requested',
|
|
]);
|
|
|
|
// Worker confirms the hiring
|
|
$response = $this->postJson("/api/workers/applied-jobs/{$app->id}/confirm-hire", [], [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
|
|
// Verify status updates
|
|
$this->assertDatabaseHas('job_applications', [
|
|
'id' => $app->id,
|
|
'status' => 'hired',
|
|
]);
|
|
$this->assertDatabaseHas('workers', [
|
|
'id' => $this->worker->id,
|
|
'status' => 'Hired',
|
|
]);
|
|
|
|
// 4. Verify candidate is visible in Hired list (web: CandidateController::index)
|
|
$response = $this->actingAs($this->employer)
|
|
->withSession(['user' => $this->employer])
|
|
->get('/employer/candidates');
|
|
$response->assertStatus(200);
|
|
// Extract the Inertia prop 'selectedWorkers' to check
|
|
$inertiaData = $response->original->getData();
|
|
$selectedWorkers = $inertiaData['page']['props']['selectedWorkers'] ?? [];
|
|
$hiredList = array_values(array_filter($selectedWorkers, function ($w) {
|
|
return $w['worker_id'] === $this->worker->id;
|
|
}));
|
|
$this->assertCount(1, $hiredList);
|
|
$this->assertEquals('Hired', $hiredList[0]['status']);
|
|
|
|
// 5. Verify candidate is visible in Hired list (API: EmployerWorkerController::getCandidates)
|
|
$response = $this->getJson('/api/employers/candidates', [
|
|
'Authorization' => 'Bearer employer_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
$response->assertJsonPath('success', true);
|
|
// Find the worker in candidates array
|
|
$candidates = $response->json('data.candidates');
|
|
$hiredApiList = array_values(array_filter($candidates, function ($c) {
|
|
return $c['worker_id'] === $this->worker->id;
|
|
}));
|
|
$this->assertCount(1, $hiredApiList);
|
|
$this->assertEquals('Hired', $hiredApiList[0]['status']);
|
|
|
|
// 6. Verify hired worker can view employer profile details bypassing subscription limits (WorkerProfileController::getEmployerProfile)
|
|
$response = $this->getJson("/api/workers/employers/{$this->employer->id}", [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
$response->assertJsonPath('success', true);
|
|
$response->assertJsonPath('data.employer.email', 'employer@example.com');
|
|
$response->assertJsonPath('data.employer.phone', '971501234567'); // should be exposed/visible because worker is hired
|
|
|
|
// 7. Verify hired worker dashboard shows correct currently working sponsor
|
|
$response = $this->getJson('/api/workers/dashboard', [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
$response->assertJsonPath('success', true);
|
|
$response->assertJsonPath('data.current_employer.id', $this->employer->id);
|
|
$response->assertJsonPath('data.currently_working_sponsor.id', $this->employer->id);
|
|
}
|
|
|
|
public function test_worker_can_decline_hire()
|
|
{
|
|
// 1. Create a job posting matching the worker's profession/skills
|
|
$job = JobPost::create([
|
|
'employer_id' => $this->employer->id,
|
|
'title' => 'Housekeeper/Nanny',
|
|
'main_profession' => 'Housekeeper',
|
|
'location' => 'Dubai Marina',
|
|
'salary' => 2500,
|
|
'workers_needed' => 1,
|
|
'job_type' => 'Full Time',
|
|
'start_date' => now()->addDays(5)->format('Y-m-d'),
|
|
'description' => 'Need clean house and nanny care.',
|
|
'status' => 'active',
|
|
]);
|
|
$skill = \App\Models\Skill::firstOrCreate(['name' => 'Piping']);
|
|
$job->skills()->sync([$skill->id]);
|
|
|
|
// 2. Worker applies for the job
|
|
$response = $this->postJson("/api/workers/jobs/{$job->id}/apply", [], [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(201);
|
|
$app = JobApplication::latest()->first();
|
|
|
|
// 3. Employer hires the worker by setting status to 'hired'
|
|
$response = $this->postJson("/api/employers/applications/{$app->id}/status", [
|
|
'status' => 'hired',
|
|
'notes' => 'Declining test offer'
|
|
], [
|
|
'Authorization' => 'Bearer employer_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
|
|
// Under two-way confirmation, it should be in hire_requested status
|
|
$this->assertDatabaseHas('job_applications', [
|
|
'id' => $app->id,
|
|
'status' => 'hire_requested',
|
|
]);
|
|
|
|
// Worker declines the hiring
|
|
$response = $this->postJson("/api/workers/applied-jobs/{$app->id}/decline-hire", [], [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
|
|
// Verify status updates
|
|
$this->assertDatabaseHas('job_applications', [
|
|
'id' => $app->id,
|
|
'status' => 'declined',
|
|
]);
|
|
$this->assertDatabaseHas('workers', [
|
|
'id' => $this->worker->id,
|
|
'status' => 'active', // Should still be active
|
|
]);
|
|
}
|
|
|
|
public function test_worker_can_view_job_matching_only_skills()
|
|
{
|
|
// 1. Create a job where main_profession is different from worker's (Plumber)
|
|
// Let's set it to 'Electrician'
|
|
$job = JobPost::create([
|
|
'employer_id' => $this->employer->id,
|
|
'title' => 'Electrician Needed',
|
|
'main_profession' => 'Electrician',
|
|
'location' => 'Dubai Marina',
|
|
'salary' => 3000,
|
|
'workers_needed' => 1,
|
|
'job_type' => 'Contract',
|
|
'start_date' => now()->addDays(2),
|
|
'description' => 'Electrician description',
|
|
'status' => 'active',
|
|
]);
|
|
|
|
// Sync the same skill 'Piping' (which the worker has)
|
|
$skill = \App\Models\Skill::firstOrCreate(['name' => 'Piping']);
|
|
$job->skills()->sync([$skill->id]);
|
|
|
|
// 2. Request list jobs
|
|
$response = $this->getJson('/api/workers/jobs', [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
|
|
$response->assertStatus(200);
|
|
$response->assertJsonPath('success', true);
|
|
|
|
// The worker's main profession is 'Plumber', but the job's main profession is 'Electrician'.
|
|
// However, the worker has the 'Piping' skill, which matches the job.
|
|
// Therefore, the job should show up.
|
|
$response->assertJsonFragment([
|
|
'title' => 'Electrician Needed'
|
|
]);
|
|
}
|
|
|
|
public function test_worker_can_filter_and_sort_jobs()
|
|
{
|
|
// 1. Create jobs with different contractTypes and salaries
|
|
$job1 = JobPost::create([
|
|
'employer_id' => $this->employer->id,
|
|
'title' => 'Contract Plumber Job',
|
|
'main_profession' => 'Plumber',
|
|
'location' => 'Dubai Marina',
|
|
'salary' => 1000,
|
|
'workers_needed' => 1,
|
|
'job_type' => 'Contract',
|
|
'start_date' => now()->addDays(2),
|
|
'description' => 'Fix pipes description',
|
|
'status' => 'active',
|
|
]);
|
|
$skill = \App\Models\Skill::firstOrCreate(['name' => 'Piping']);
|
|
$job1->skills()->sync([$skill->id]);
|
|
|
|
$job2 = JobPost::create([
|
|
'employer_id' => $this->employer->id,
|
|
'title' => 'Full Time Plumber Job',
|
|
'main_profession' => 'Plumber',
|
|
'location' => 'Dubai Marina',
|
|
'salary' => 5000,
|
|
'workers_needed' => 1,
|
|
'job_type' => 'Full Time',
|
|
'start_date' => now()->addDays(2),
|
|
'description' => 'Manage plumbing description',
|
|
'status' => 'active',
|
|
]);
|
|
$job2->skills()->sync([$skill->id]);
|
|
|
|
// 2. Filter by job_type = Contract
|
|
$response = $this->getJson('/api/workers/jobs?job_type=Contract', [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
$response->assertJsonFragment(['title' => 'Contract Plumber Job']);
|
|
$response->assertJsonMissingExact(['title' => 'Full Time Plumber Job']);
|
|
|
|
// 3. Search for "Manage"
|
|
$response = $this->getJson('/api/workers/jobs?search=Manage', [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
$response->assertJsonFragment(['title' => 'Full Time Plumber Job']);
|
|
$response->assertJsonMissingExact(['title' => 'Contract Plumber Job']);
|
|
|
|
// 4. Sort by PRICE: HIGH TO LOW
|
|
$response = $this->getJson('/api/workers/jobs?sortBy=PRICE:%20HIGH%20TO%20LOW', [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
// The first job in list should be the one with salary 5000 (Job 2)
|
|
$this->assertEquals('Full Time Plumber Job', $response->json('data.jobs.0.title'));
|
|
|
|
// 5. Filter by job_type directly
|
|
$response = $this->getJson('/api/workers/jobs?job_type=Full%20Time', [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
$response->assertJsonFragment(['title' => 'Full Time Plumber Job']);
|
|
$response->assertJsonMissingExact(['title' => 'Contract Plumber Job']);
|
|
|
|
// 6. Filter by skills
|
|
$cookingSkill = \App\Models\Skill::firstOrCreate(['name' => 'Cooking']);
|
|
$job2->skills()->sync([$skill->id, $cookingSkill->id]);
|
|
|
|
$response = $this->getJson('/api/workers/jobs?skills=Cooking', [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
$response->assertJsonFragment(['title' => 'Full Time Plumber Job']);
|
|
$response->assertJsonMissingExact(['title' => 'Contract Plumber Job']);
|
|
|
|
// 7. Filter by salary range
|
|
$response = $this->getJson('/api/workers/jobs?salary_min=2000&salary_max=6000', [
|
|
'Authorization' => 'Bearer worker_api_token'
|
|
]);
|
|
$response->assertStatus(200);
|
|
$response->assertJsonFragment(['title' => 'Full Time Plumber Job']);
|
|
$response->assertJsonMissingExact(['title' => 'Contract Plumber Job']);
|
|
}
|
|
}
|
|
|
|
|