create('license.pdf', 500, 'application/pdf'); $emiratesIdFile = UploadedFile::fake()->create('emirates_id.pdf', 500, 'application/pdf'); $response = $this->postJson('/api/sponsors/register', [ 'full_name' => 'Test Sponsor Organization', 'mobile' => '+971509990001', 'password' => 'securepassword', 'license_file' => $licenseFile, 'emirates_id_file' => $emiratesIdFile, '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', 'license_expiry', ], 'token', ] ]); $this->assertDatabaseHas('sponsors', [ 'mobile' => '+971509990001', 'full_name' => 'Test Sponsor Organization', 'license_expiry' => '2028-06-12 00:00:00', ]); $sponsor = Sponsor::where('mobile', '+971509990001')->first(); $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 uploading Emirates ID front/back and license files separately. */ public function test_sponsor_can_upload_separate_documents() { // 1. Register Sponsor without documents $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', ]); $response->assertStatus(201); $sponsor = Sponsor::where('email', 'test.sponsor.sep@example.com')->first(); $this->assertNotNull($sponsor); $this->assertNull($sponsor->license_expiry); // 2. Upload Emirates ID Front $frontFile = UploadedFile::fake()->create('emirates_id_front.jpg', 500, 'image/jpeg'); $responseFront = $this->postJson('/api/sponsors/register/emirates-front', [ 'email' => 'test.sponsor.sep@example.com', 'emirates_id_front' => $frontFile, ]); $responseFront->assertStatus(200) ->assertJson([ 'success' => true, 'message' => 'Emirates ID front uploaded and processed successfully.', ]) ->assertJsonStructure([ 'ocr_extracted_data' => [ 'emirates_id_number', 'name', 'nationality', 'date_of_birth', ] ]); // 3. Upload Emirates ID Back $backFile = UploadedFile::fake()->create('emirates_id_back.jpg', 500, 'image/jpeg'); $responseBack = $this->postJson('/api/sponsors/register/emirates-back', [ 'email' => 'test.sponsor.sep@example.com', 'emirates_id_back' => $backFile, ]); $responseBack->assertStatus(200) ->assertJson([ 'success' => true, 'message' => 'Emirates ID back uploaded and processed successfully.', ]) ->assertJsonStructure([ 'ocr_extracted_data' => [ 'expiry_date', 'issue_date', ] ]); // 4. 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()); } }