token = 'test-employer-token'; $this->employer = User::create([ 'name' => 'Original Name', 'email' => 'employer@example.com', 'password' => bcrypt('Password@123'), 'role' => 'employer', 'api_token' => $this->token, ]); EmployerProfile::create([ 'user_id' => $this->employer->id, 'address' => 'Original Address', 'phone' => '+971501112222', 'verification_status' => 'approved', 'notifications' => true, ]); } /** * Test employer can retrieve profile. */ public function test_employer_can_get_profile() { $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->getJson('/api/employers/profile'); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'data' => [ 'profile' => [ 'name', 'email', 'phone', 'address', 'notifications', 'verification_status', 'emirates_id', ] ] ]); $this->assertEquals('Original Name', $response->json('data.profile.name')); $this->assertEquals('Original Address', $response->json('data.profile.address')); } /** * Test employer can update profile details. */ public function test_employer_can_update_profile() { $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->postJson('/api/employers/profile/update', [ 'name' => 'Updated Name', 'email' => 'employer_updated@example.com', 'phone' => '+971509999999', 'address' => 'Updated Address Info', 'notifications' => false, ]); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'message', 'data' => [ 'profile' => [ 'name', 'email', 'phone', 'address', 'notifications', 'emirates_id', ] ] ]); $this->assertDatabaseHas('users', [ 'id' => $this->employer->id, 'name' => 'Updated Name', 'email' => 'employer_updated@example.com', ]); $this->assertDatabaseHas('employer_profiles', [ 'user_id' => $this->employer->id, 'address' => 'Updated Address Info', 'notifications' => false, ]); } /** * Test employer can update profile with optional Emirates ID fields. */ public function test_employer_can_update_profile_with_emirates_id_fields() { $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->postJson('/api/employers/profile/update', [ 'name' => 'Ahmad', 'email' => 'ahmad@example.com', 'phone' => '+971509990001', 'address' => 'Villa 12, Jumeirah 2', 'notifications' => true, 'id_number' => '784-1987-5493842-5', 'card_number' => '151023946', 'full_name' => 'Mohammad Jobaier Mohammad Abul Kalam', 'date_of_birth' => '14/04/1987', 'nationality' => 'Bangladesh', 'gender' => 'M', 'issue_date' => '12/12/2025', 'expiry_date' => '11/12/2027', 'occupation' => 'Electrician', 'employer' => 'Msj International Technical Services L.L.C UAE', 'issuing_place' => 'Dubai', ]); $response->assertStatus(200) ->assertJson([ 'success' => true, 'data' => [ 'profile' => [ 'name' => 'Mohammad Jobaier Mohammad Abul Kalam', 'email' => 'ahmad@example.com', 'phone' => '+971509990001', 'address' => 'Villa 12, Jumeirah 2', 'id_number' => '784-1987-5493842-5', 'card_number' => '151023946', 'full_name' => 'Mohammad Jobaier Mohammad Abul Kalam', 'gender' => 'M', 'emirates_id' => [ 'emirates_id_number' => '784-1987-5493842-5', 'name' => 'Mohammad Jobaier Mohammad Abul Kalam', 'date_of_birth' => '1987-04-14', 'nationality' => 'Bangladesh', 'issue_date' => '2025-12-12', 'expiry_date' => '2027-12-11', 'employer' => 'Msj International Technical Services L.L.C UAE', 'issue_place' => 'Dubai', 'occupation' => 'Electrician', ] ] ] ]); $this->assertDatabaseHas('employer_profiles', [ 'user_id' => $this->employer->id, 'emirates_id_number' => '784-1987-5493842-5', 'emirates_id_card_number' => '151023946', 'emirates_id_name' => 'Mohammad Jobaier Mohammad Abul Kalam', 'emirates_id_gender' => 'M', ]); } /** * Test employer cannot change an existing Emirates ID number. */ public function test_employer_cannot_change_existing_emirates_id_number() { // First, set the Emirates ID $profile = $this->employer->employerProfile; $profile->emirates_id_number = '784-1987-5493842-5'; $profile->save(); // Attempt to change to a different ID $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->postJson('/api/employers/profile/update', [ 'name' => 'Original Name', 'email' => 'employer@example.com', 'phone' => '+971501112222', 'address' => 'Original Address', 'id_number' => '784-9999-0000000-0', ]); $response->assertStatus(422) ->assertJson([ 'success' => false, 'errors' => [ 'id_number' => ['Emirates ID mismatch.'] ] ]); // Verify ID was NOT changed $this->assertEquals('784-1987-5493842-5', $profile->fresh()->emirates_id_number); } /** * Test employer dashboard returns only the latest 2 approved charity drives. */ public function test_employer_dashboard_returns_only_two_charity_drives() { // Create 5 approved announcements for ($i = 1; $i <= 5; $i++) { $ann = new \App\Models\Announcement([ 'title' => "Charity Drive $i", 'body' => "Details of charity drive $i", 'type' => 'charity', 'status' => 'approved', ]); $ann->created_at = now()->subMinutes(6 - $i); $ann->save(); } // Create 1 pending announcement $pendingAnn = new \App\Models\Announcement([ 'title' => "Pending Charity Drive", 'body' => "Details of pending charity drive", 'type' => 'charity', 'status' => 'pending', ]); $pendingAnn->created_at = now(); $pendingAnn->save(); $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->getJson('/api/employers/dashboard'); $response->assertStatus(200) ->assertJson([ 'success' => true, ]); $announcements = $response->json('data.recent_announcements'); $this->assertCount(2, $announcements); // Make sure it contains the latest 2 approved ones $titles = collect($announcements)->pluck('title'); $this->assertTrue($titles->contains('Charity Drive 5')); $this->assertTrue($titles->contains('Charity Drive 4')); $this->assertFalse($titles->contains('Charity Drive 3')); $this->assertFalse($titles->contains('Charity Drive 2')); $this->assertFalse($titles->contains('Charity Drive 1')); $this->assertFalse($titles->contains('Pending Charity Drive')); } public function test_employer_dashboard_returns_total_workers_active() { // Delete any existing workers seeded or created to make count precise \App\Models\Worker::query()->delete(); // Create 3 active workers $w1 = \App\Models\Worker::create([ 'name' => 'Active 1', 'email' => 'a1@test.com', 'phone' => '+971501110001', 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', 'preferred_location' => 'Dubai', 'age' => 25, 'salary' => 1500, 'experience' => '3 Years', 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', 'verified' => true, ]); \App\Models\WorkerDocument::create([ 'worker_id' => $w1->id, 'type' => 'passport', 'number' => 'P111', 'issue_date' => '2020-01-01', 'expiry_date' => '2030-01-01', ]); $w2 = \App\Models\Worker::create([ 'name' => 'Active 2', 'email' => 'a2@test.com', 'phone' => '+971501110002', 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', 'preferred_location' => 'Dubai', 'age' => 26, 'salary' => 1500, 'experience' => '3 Years', 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', 'verified' => true, ]); \App\Models\WorkerDocument::create([ 'worker_id' => $w2->id, 'type' => 'passport', 'number' => 'P222', 'issue_date' => '2020-01-01', 'expiry_date' => '2030-01-01', ]); $w3 = \App\Models\Worker::create([ 'name' => 'Active 3', 'email' => 'a3@test.com', 'phone' => '+971501110003', 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', 'preferred_location' => 'Dubai', 'age' => 27, 'salary' => 1500, 'experience' => '3 Years', 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', 'verified' => true, ]); \App\Models\WorkerDocument::create([ 'worker_id' => $w3->id, 'type' => 'passport', 'number' => 'P333', 'issue_date' => '2020-01-01', 'expiry_date' => '2030-01-01', ]); // Create 2 inactive/hidden workers \App\Models\Worker::create([ 'name' => 'Inactive 1', 'email' => 'i1@test.com', 'phone' => '+971502220001', 'password' => bcrypt('password'), 'status' => 'hidden', 'nationality' => 'Indian', 'preferred_location' => 'Dubai', 'age' => 28, 'salary' => 1500, 'experience' => '3 Years', 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', ]); \App\Models\Worker::create([ 'name' => 'Inactive 2', 'email' => 'i2@test.com', 'phone' => '+971502220002', 'password' => bcrypt('password'), 'status' => 'Hired', 'nationality' => 'Indian', 'preferred_location' => 'Dubai', 'age' => 29, 'salary' => 1500, 'experience' => '3 Years', 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', ]); $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->getJson('/api/employers/dashboard'); $response->assertStatus(200); $this->assertEquals(3, $response->json('data.stats.total_workers')); } /** * Test employer can change password successfully. */ public function test_employer_can_change_password() { // 1. Create a matching sponsor $sponsor = \App\Models\Sponsor::create([ 'full_name' => 'Employer One', 'email' => $this->employer->email, 'mobile' => '+971509990011', 'organization_name' => 'Elite Services', 'address' => 'Dubai', 'role' => 'sponsor', 'password' => bcrypt('Password@123'), ]); // Verify initial password hash is set $this->employer->update([ 'password' => bcrypt('Password@123') ]); // 2. Try with wrong current password $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->postJson('/api/employers/change-password', [ 'current_password' => 'WrongPassword', 'new_password' => 'NewPassword@123', 'new_password_confirmation' => 'NewPassword@123', ]); $response->assertStatus(422) ->assertJsonValidationErrors(['current_password']); // 3. Try with correct current password $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->postJson('/api/employers/change-password', [ 'current_password' => 'Password@123', 'new_password' => 'NewPassword@123', 'new_password_confirmation' => 'NewPassword@123', ]); $response->assertStatus(200) ->assertJson([ 'success' => true, 'message' => 'Password changed successfully.' ]); // Verify password is updated for both User and Sponsor $this->assertTrue(\Illuminate\Support\Facades\Hash::check('NewPassword@123', $this->employer->fresh()->password)); $this->assertTrue(\Illuminate\Support\Facades\Hash::check('NewPassword@123', $sponsor->fresh()->password)); } /** * Test registering an employer with DD/MM/YYYY dates normalizes them to Y-m-d. */ public function test_employer_registration_normalizes_dates() { $response = $this->postJson('/api/employers/register', [ 'name' => 'Sandeep Vishwakarma', 'email' => 'sandeep@example.com', 'phone' => '+971509990002', 'address' => 'Dubai, UAE', 'emirates_id' => [ 'emirates_id_number' => '784-2002-4977006-4', 'name' => 'Sandeep Vishwakarma', 'date_of_birth' => '05/07/2002', 'issue_date' => '07/06/2023', 'expiry_date' => '06/06/2025', ], ]); $response->assertStatus(201) ->assertJson([ 'success' => true, ]); $this->assertDatabaseHas('employer_profiles', [ 'phone' => '+971509990002', 'emirates_id_dob' => '2002-07-05', 'emirates_id_issue_date' => '2023-06-07', 'emirates_id_expiry' => '2025-06-06', ]); $this->assertDatabaseHas('sponsors', [ 'mobile' => '+971509990002', 'emirates_id_dob' => '2002-07-05', 'emirates_id_issue_date' => '2023-06-07', 'emirates_id_expiry' => '2025-06-06', ]); } /** * Test employer dashboard returns subscription plan details and access rights. */ public function test_employer_dashboard_returns_plan_details() { // 1. First, request dashboard without any subscription in DB. // It should fallback to the default 'premium' plan. $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->getJson('/api/employers/dashboard'); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'data' => [ 'current_plan' => [ 'plan_id', 'name', 'status', 'starts_at', 'expires_at', 'max_contact_people', 'unlimited_contacts', 'enable_job_apply', 'features', 'price', 'duration', ] ] ]); $this->assertEquals(2, $response->json('data.current_plan.plan_id')); $this->assertEquals(50, $response->json('data.current_plan.max_contact_people')); $this->assertFalse($response->json('data.current_plan.unlimited_contacts')); $this->assertTrue($response->json('data.current_plan.enable_job_apply')); // 2. Now update/prepare the plan and create an active subscription for the employer \App\Models\Plan::updateOrCreate( ['id' => 'basic'], [ 'name' => 'Basic Plan', 'price' => 99.00, 'duration' => 'Monthly', 'features' => ['Feature 1', 'Feature 2'], 'status' => 'Active', 'max_contact_people' => 10, 'unlimited_contacts' => false, 'enable_job_apply' => false, ] ); \Illuminate\Support\Facades\DB::table('subscriptions')->insert([ 'user_id' => $this->employer->id, 'plan_id' => 'basic', 'amount_aed' => 99.00, 'starts_at' => now(), 'expires_at' => now()->addDays(30), 'paytabs_transaction_id' => 'tx_test123', 'status' => 'active', 'created_at' => now(), 'updated_at' => now(), ]); $responseSub = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->getJson('/api/employers/dashboard'); $responseSub->assertStatus(200); $this->assertEquals(1, $responseSub->json('data.current_plan.plan_id')); $this->assertEquals('Basic Plan', $responseSub->json('data.current_plan.name')); $this->assertEquals(10, $responseSub->json('data.current_plan.max_contact_people')); $this->assertFalse($responseSub->json('data.current_plan.unlimited_contacts')); $this->assertFalse($responseSub->json('data.current_plan.enable_job_apply')); $this->assertEquals(99.00, $responseSub->json('data.current_plan.price')); // 3. Test that a subscription with plan_id 'enterprise' resolves correctly to 'vip' in the response. \App\Models\Plan::updateOrCreate( ['id' => 'vip'], [ 'name' => 'VIP Concierge', 'price' => 499.00, 'duration' => 'Monthly', 'features' => ['VIP Feature 1'], 'status' => 'Active', 'max_contact_people' => null, 'unlimited_contacts' => true, 'enable_job_apply' => true, ] ); \Illuminate\Support\Facades\DB::table('subscriptions')->where('user_id', $this->employer->id)->delete(); \Illuminate\Support\Facades\DB::table('subscriptions')->insert([ 'user_id' => $this->employer->id, 'plan_id' => 'enterprise', 'amount_aed' => 499.00, 'starts_at' => now(), 'expires_at' => now()->addDays(30), 'paytabs_transaction_id' => 'tx_test456', 'status' => 'active', 'created_at' => now(), 'updated_at' => now(), ]); $responseVip = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->getJson('/api/employers/dashboard'); $responseVip->assertStatus(200); $this->assertEquals(3, $responseVip->json('data.current_plan.plan_id')); $this->assertEquals('VIP Concierge', $responseVip->json('data.current_plan.name')); $this->assertNull($responseVip->json('data.current_plan.max_contact_people')); $this->assertTrue($responseVip->json('data.current_plan.unlimited_contacts')); } /** * Test employer can fetch plans and pay with numeric plan ID. */ public function test_employer_can_get_plans_and_pay_with_numeric_id() { // 1. Fetch plans $responsePlans = $this->getJson('/api/employers/plans'); $responsePlans->assertStatus(200) ->assertJsonStructure([ 'success', 'data' => [ 'plans' => [ '*' => [ 'id', 'name', 'price', 'currency', 'period', 'features', 'popular', 'max_contact_people', 'unlimited_contacts', 'enable_job_apply', 'status', 'duration', ] ] ] ]); $plans = $responsePlans->json('data.plans'); $this->assertNotEmpty($plans); foreach ($plans as $plan) { $this->assertIsInt($plan['id']); } // Create sponsor record for payment verification \App\Models\Sponsor::create([ 'email' => $this->employer->email, 'full_name' => $this->employer->name, 'mobile' => '+971501112222', 'password' => $this->employer->password, 'is_verified' => true, 'status' => 'active', ]); // 2. Submit payment with a numeric plan ID (e.g. 3) $responsePayment = $this->postJson('/api/employers/payment', [ 'email' => $this->employer->email, 'plan_id' => 3, 'amount_aed' => 499.00, 'paytabs_transaction_id' => 'tx_numeric123', ]); $responsePayment->assertStatus(200) ->assertJson([ 'success' => true, ]); // Assert database subscription resolved to 'vip' string $this->assertDatabaseHas('subscriptions', [ 'user_id' => $this->employer->id, 'plan_id' => 'vip', 'amount_aed' => 499.00, 'paytabs_transaction_id' => 'tx_numeric123', 'status' => 'active', ]); } }