postJson('/api/sponsors/register', [ 'full_name' => 'Test Sponsor Organization', 'mobile' => '+971509990001', 'password' => 'securepassword', 'license' => [ 'license_number' => '123456', 'organization_name' => 'Charity Co', 'expiry_date' => '2028-06-12', ], 'emirates_id' => [ 'emirates_id_number' => '784-1988-5310327-2', 'name' => 'Ahmad Bin Ahmed', 'date_of_birth' => '1990-01-01', 'nationality' => 'Emirati', 'issue_date' => '2023-04-11', 'expiry_date' => '2028-04-11', 'employer' => 'Federal Authority', 'issue_place' => 'Abu Dhabi', 'occupation' => 'Manager' ], 'organization_name' => 'Charity Co', 'email' => 'test.sponsor@example.com', 'nationality' => 'Emirati', 'city' => 'Abu Dhabi', 'address' => 'Villa 45, Street 12', 'country_code' => '+971', 'license_expiry' => '2028-06-12', ]); $response->assertStatus(201) ->assertJsonStructure([ 'success', 'message', 'data' => [ 'sponsor' => [ 'id', 'full_name', 'mobile', 'organization_name', 'nationality', 'city', 'license_file', 'emirates_id_file', 'emirates_id', 'license_expiry', ], 'token', ] ]); $this->assertDatabaseHas('sponsors', [ 'mobile' => '+971509990001', 'full_name' => 'Test Sponsor Organization', 'license_expiry' => '2028-06-12 00:00:00', 'emirates_id_name' => 'Ahmad Bin Ahmed', 'emirates_id_dob' => '1990-01-01', 'emirates_id_issue_date' => '2023-04-11', 'emirates_id_expiry' => '2028-04-11', 'emirates_id_employer' => 'Federal Authority', 'emirates_id_issue_place' => 'Abu Dhabi', 'emirates_id_occupation' => 'Manager', ]); $sponsor = Sponsor::where('mobile', '+971509990001')->first(); $this->assertEquals('784-1988-5310327-2', $sponsor->emirates_id); $this->assertNull($sponsor->emirates_id_file); $this->assertNull($sponsor->license_file); } /** * Test both Sponsor and Employer can log in using the unified login endpoint. */ public function test_unified_login_returns_correct_role_and_data() { // 1. Create a pure Sponsor (no User record) $sponsorUser = Sponsor::create([ 'full_name' => 'Sponsor User', 'email' => 'sponsor@example.com', 'mobile' => '+971509991111', 'password' => Hash::make('password123'), 'license_file' => 'uploads/licenses/fake.pdf', 'status' => 'active', ]); // 2. Create an Employer (has both User and Sponsor records, legacy setup) $employerUser = User::create([ 'name' => 'Employer User', 'email' => 'employer@example.com', 'password' => Hash::make('password123'), 'role' => 'employer', 'api_token' => 'old_token', ]); EmployerProfile::create([ 'user_id' => $employerUser->id, 'company_name' => 'Employer Corp', 'phone' => '+971509992222', 'country' => 'United Arab Emirates', 'verification_status' => 'approved', ]); Sponsor::create([ 'full_name' => 'Employer User', 'email' => 'employer@example.com', 'mobile' => '+971509992222', 'password' => $employerUser->password, 'status' => 'active', ]); // --- Test Sponsor Login via /api/employers/login --- $responseSponsor = $this->postJson('/api/employers/login', [ 'mobile' => '+971509991111', 'password' => 'password123', ]); $responseSponsor->assertStatus(200) ->assertJson([ 'success' => true, 'role' => 'sponsor', ]) ->assertJsonStructure([ 'data' => [ 'sponsor', 'token', ] ]); // --- Test Employer Login via /api/employers/login --- $responseEmployer = $this->postJson('/api/employers/login', [ 'mobile' => '+971509992222', 'password' => 'password123', ]); $responseEmployer->assertStatus(200) ->assertJson([ 'success' => true, 'role' => 'employer', ]) ->assertJsonStructure([ 'data' => [ 'employer' => [ 'id', 'name', 'email', 'employer_profile', ], 'token', ] ]); } /** * Test that pure sponsors can only access their limited routes. */ public function test_sponsor_restricted_access() { $sponsor = Sponsor::create([ 'full_name' => 'Pure Sponsor', 'email' => 'pure_sponsor@example.com', 'mobile' => '+971509993333', 'password' => Hash::make('password123'), 'api_token' => 'sponsor_token_abc', 'status' => 'active', ]); // Access dashboard successfully $responseDash = $this->withHeaders([ 'Authorization' => 'Bearer sponsor_token_abc', ])->getJson('/api/sponsors/dashboard'); $responseDash->assertStatus(200); // Try to access employer protected route (should get blocked as they don't have employer token/role) $responseEmployerRoute = $this->withHeaders([ 'Authorization' => 'Bearer sponsor_token_abc', ])->getJson('/api/employers/profile'); $responseEmployerRoute->assertStatus(401); } /** * Test that a sponsor can post a charity event. */ public function test_sponsor_can_post_charity_event() { $sponsor = Sponsor::create([ 'full_name' => 'Test Sponsor', 'organization_name' => 'Save the Children', 'email' => 'tsponsor@example.com', 'mobile' => '+971509994444', 'password' => Hash::make('password123'), 'api_token' => 'sponsor_token_xyz', 'status' => 'active', ]); $response = $this->withHeaders([ 'Authorization' => 'Bearer sponsor_token_xyz', ])->postJson('/api/sponsors/charity-events', [ 'title' => 'Community Ramadan Iftar', 'body' => 'Join us for a free community Iftar gathering at the center.', 'type' => 'charity', 'event_date' => '2026-06-15', 'start_time' => '09:00 AM', 'end_time' => '04:00 PM', 'provided_items' => 'Free Iftar meals, Water, Dates', 'location_details' => 'Al Quoz Community Center, Dubai', 'location_pin' => 'https://maps.app.goo.gl/xyz', ]); $response->assertStatus(201) ->assertJson([ 'success' => true, 'message' => 'Charity event posted successfully.', 'data' => [ 'title' => 'Community Ramadan Iftar', 'body' => 'Join us for a free community Iftar gathering at the center.', 'type' => 'charity', 'posted_by' => 'Test Sponsor', 'organization' => 'Save the Children', 'charity_details' => [ 'type' => 'Charity', 'provided_items' => 'Free Iftar meals, Water, Dates', 'event_date' => '2026-06-15', 'event_time' => '09:00 AM - 04:00 PM', 'location_details' => 'Al Quoz Community Center, Dubai', 'location_pin' => 'https://maps.app.goo.gl/xyz', 'content' => 'Join us for a free community Iftar gathering at the center.', ] ] ]) ->assertJsonStructure([ 'data' => [ 'id', 'title', 'body', 'type', 'posted_by', 'organization', 'created_at', 'charity_details' => [ 'type', 'provided_items', 'event_date', 'event_time', 'location_details', 'location_pin', 'content', ] ] ]); $this->assertDatabaseHas('announcements', [ 'title' => 'Community Ramadan Iftar', 'sponsor_id' => $sponsor->id, 'status' => 'pending', ]); } /** * Test that a sponsor can list charity events showing both employer and sponsor events. */ public function test_sponsor_can_list_charity_events() { $sponsor = Sponsor::create([ 'full_name' => 'Test Sponsor', 'organization_name' => 'Save the Children', 'email' => 'tsponsor@example.com', 'mobile' => '+971509994444', 'password' => Hash::make('password123'), 'api_token' => 'sponsor_token_xyz', 'status' => 'active', ]); // Post a sponsor announcement \App\Models\Announcement::create([ 'title' => 'Sponsor Event', 'body' => 'Body of sponsor event', 'type' => 'charity', 'sponsor_id' => $sponsor->id, 'status' => 'approved', ]); // Post an employer announcement $employer = User::create([ 'name' => 'Employer User', 'email' => 'employer@example.com', 'password' => Hash::make('password123'), 'role' => 'employer', ]); EmployerProfile::create([ 'user_id' => $employer->id, 'company_name' => 'Employer Co', 'phone' => '+971509995555', 'country' => 'United Arab Emirates', ]); \App\Models\Announcement::create([ 'title' => 'Employer Event', 'body' => 'Body of employer event', 'type' => 'charity', 'employer_id' => $employer->id, 'status' => 'approved', ]); $response = $this->withHeaders([ 'Authorization' => 'Bearer sponsor_token_xyz', ])->getJson('/api/sponsors/charity-events'); $response->assertStatus(200) ->assertJson([ 'success' => true, ]); $events = $response->json('data.events'); $this->assertCount(2, $events); // Verify sponsor event details $this->assertEquals('Sponsor Event', $events[0]['title']); $this->assertEquals('Test Sponsor', $events[0]['posted_by']); $this->assertEquals('Save the Children', $events[0]['organization']); // Verify employer event details $this->assertEquals('Employer Event', $events[1]['title']); $this->assertEquals('Employer User', $events[1]['posted_by']); $this->assertEquals('Employer Co', $events[1]['organization']); } /** * Test that sponsor dashboard returns correct employer and worker counts. */ public function test_sponsor_dashboard_returns_stats() { $sponsor = Sponsor::create([ 'full_name' => 'Test Sponsor', 'organization_name' => 'Save the Children', 'email' => 'tsponsor@example.com', 'mobile' => '+971509994444', 'password' => Hash::make('password123'), 'api_token' => 'sponsor_token_xyz', 'status' => 'active', 'license_expiry' => now()->addYear()->toDateString(), ]); // Create some employers (Users with role = employer) User::create([ 'name' => 'Emp 1', 'email' => 'emp1@example.com', 'password' => Hash::make('password123'), 'role' => 'employer', 'subscription_status' => 'active', ]); User::create([ 'name' => 'Emp 2', 'email' => 'emp2@example.com', 'password' => Hash::make('password123'), 'role' => 'employer', 'subscription_status' => 'none', ]); // Create some workers \App\Models\Worker::create([ 'name' => 'Worker 1', 'email' => 'w1@example.com', 'phone' => '+971509998881', 'password' => Hash::make('password123'), 'status' => 'active', 'language' => 'en', 'nationality' => 'Indian', 'city' => 'Dubai', 'age' => 25, 'salary' => 1500, 'experience' => 'None', 'religion' => 'Christian', 'bio' => 'Test', 'availability' => 'Immediate', ]); \App\Models\Worker::create([ 'name' => 'Worker 2', 'email' => 'w2@example.com', 'phone' => '+971509998882', 'password' => Hash::make('password123'), 'status' => 'hidden', 'language' => 'en', 'nationality' => 'Indian', 'city' => 'Dubai', 'age' => 25, 'salary' => 1500, 'experience' => 'None', 'religion' => 'Christian', 'bio' => 'Test', 'availability' => 'Immediate', ]); $response = $this->withHeaders([ 'Authorization' => 'Bearer sponsor_token_xyz', ])->getJson('/api/sponsors/dashboard'); $response->assertStatus(200) ->assertJson([ 'success' => true, 'data' => [ 'sponsor' => [ 'license_expiry' => now()->addYear()->toDateString(), 'validity' => 'Valid', ], 'employer_stats' => [ 'total' => 2, 'active' => 1, ], 'worker_stats' => [ 'total' => 2, 'active' => 1, ], ], ]); $profileResponse = $this->withHeaders([ 'Authorization' => 'Bearer sponsor_token_xyz', ])->getJson('/api/sponsors/profile'); $profileResponse->assertStatus(200) ->assertJson([ 'success' => true, 'data' => [ 'sponsor' => [ 'license_expiry' => now()->addYear()->toDateString(), 'validity' => 'Valid', ] ] ]); } /** * Test that sponsor can retrieve their own pending charity events. */ public function test_sponsor_can_list_own_pending_charity_event() { $sponsor = Sponsor::create([ 'full_name' => 'Test Sponsor', 'organization_name' => 'Save the Children', 'email' => 'tsponsor@example.com', 'mobile' => '+971509994444', 'password' => Hash::make('password123'), 'api_token' => 'sponsor_token_xyz', 'status' => 'active', ]); // Post a sponsor announcement (pending) \App\Models\Announcement::create([ 'title' => 'Sponsor Pending Event', 'body' => 'Body of sponsor event', 'type' => 'charity', 'sponsor_id' => $sponsor->id, 'status' => 'pending', ]); // Post another sponsor announcement (approved) \App\Models\Announcement::create([ 'title' => 'Sponsor Approved Event', 'body' => 'Body of sponsor event 2', 'type' => 'charity', 'sponsor_id' => $sponsor->id, 'status' => 'approved', ]); // Post another sponsor announcement (pending but from different sponsor - should NOT be returned) $otherSponsor = Sponsor::create([ 'full_name' => 'Other Sponsor', 'organization_name' => 'Other Org', 'email' => 'other@example.com', 'mobile' => '+971509990000', 'password' => Hash::make('password123'), 'status' => 'active', ]); \App\Models\Announcement::create([ 'title' => 'Other Sponsor Pending Event', 'body' => 'Body of other sponsor event', 'type' => 'charity', 'sponsor_id' => $otherSponsor->id, 'status' => 'pending', ]); $response = $this->withHeaders([ 'Authorization' => 'Bearer sponsor_token_xyz', ])->getJson('/api/sponsors/charity-events'); $response->assertStatus(200) ->assertJson([ 'success' => true, ]); $events = $response->json('data.events'); $this->assertCount(2, $events); // Sort or check for titles $titles = collect($events)->pluck('title'); $this->assertTrue($titles->contains('Sponsor Pending Event')); $this->assertTrue($titles->contains('Sponsor Approved Event')); $this->assertFalse($titles->contains('Other Sponsor Pending Event')); } /** * Test registering Sponsor with explicit emirates_id, and uploading license separately. */ public function test_sponsor_registration_with_emirates_id_and_separate_license() { // 1. Register Sponsor with emirates_id in payload $response = $this->postJson('/api/sponsors/register', [ 'full_name' => 'Test Sponsor Sep', 'mobile' => '+971509990901', 'password' => 'securepassword', 'organization_name' => 'Charity Sep', 'email' => 'test.sponsor.sep@example.com', 'nationality' => 'Emirati', 'city' => 'Abu Dhabi', 'address' => 'Villa 45, Street 12', 'country_code' => '+971', 'emirates_id' => [ 'emirates_id_number' => '784-1988-5310327-2', ], ]); $response->assertStatus(201); $sponsor = Sponsor::where('email', 'test.sponsor.sep@example.com')->first(); $this->assertNotNull($sponsor); $this->assertEquals('784-1988-5310327-2', $sponsor->emirates_id); $this->assertNull($sponsor->license_expiry); // 2. Upload License File $licenseFile = UploadedFile::fake()->create('license.pdf', 500, 'application/pdf'); $responseLicense = $this->postJson('/api/sponsors/register/license', [ 'email' => 'test.sponsor.sep@example.com', 'license_file' => $licenseFile, ]); $responseLicense->assertStatus(200) ->assertJson([ 'success' => true, 'message' => 'License uploaded and processed successfully.', ]) ->assertJsonStructure([ 'ocr_extracted_data' => [ 'expiry_date', 'license_number', 'organization_name', ] ]); // Reload sponsor and verify license_expiry is set $sponsor->refresh(); $this->assertEquals('2028-06-12 00:00:00', $sponsor->license_expiry->toDateTimeString()); } /** * Test worker forgot password user not found error. */ public function test_worker_forgot_password_user_not_found() { $response = $this->postJson('/api/workers/forgot-password', [ 'phone' => '+971501111111', ]); $response->assertStatus(404) ->assertJson([ 'success' => false, 'message' => 'user not found this mobile number', ]); } /** * Test sponsor forgot password user not found error. */ public function test_sponsor_forgot_password_user_not_found() { $response = $this->postJson('/api/sponsors/forgot-password', [ 'email' => 'nonexistent.sponsor@example.com', ]); $response->assertStatus(404) ->assertJson([ 'success' => false, 'message' => 'user not found this email id', ]); } /** * Test sponsor can change password successfully. */ public function test_sponsor_can_change_password() { $sponsor = Sponsor::create([ 'full_name' => 'Sponsor Changer', 'email' => 'changer.sponsor@example.com', 'mobile' => '+971509997788', 'password' => Hash::make('Password@123'), 'api_token' => 'sponsor-changer-token', 'status' => 'active', ]); // Sync with corresponding employer user record to test sync $user = User::create([ 'name' => 'Sponsor Changer', 'email' => 'changer.sponsor@example.com', 'password' => Hash::make('Password@123'), 'role' => 'employer', ]); // 1. Try with wrong current password $response = $this->withHeaders([ 'Authorization' => 'Bearer sponsor-changer-token', ])->postJson('/api/sponsors/change-password', [ 'current_password' => 'WrongPassword', 'new_password' => 'NewPassword@123', 'new_password_confirmation' => 'NewPassword@123', ]); $response->assertStatus(422) ->assertJsonValidationErrors(['current_password']); // 2. Try with correct current password $response = $this->withHeaders([ 'Authorization' => 'Bearer sponsor-changer-token', ])->postJson('/api/sponsors/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 Sponsor and User $this->assertTrue(Hash::check('NewPassword@123', $sponsor->fresh()->password)); $this->assertTrue(Hash::check('NewPassword@123', $user->fresh()->password)); } }