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'); $response->assertJsonPath('data.jobs.0.posted_by', 'Employer User'); // 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'); // 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'); $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', '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->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 ]); } }