worker = Worker::create([ 'name' => 'Jane Smith', 'email' => 'jane@example.com', 'phone' => '+971500000001', 'password' => bcrypt('password'), 'status' => 'Available', 'nationality' => 'Ethiopian', 'age' => 28, 'salary' => 1500.00, 'availability' => 'Immediate', 'experience' => '2 Years', 'religion' => 'Christian', 'bio' => 'Experienced housemaid seeking employment.', ]); // Create an employer user with token $this->token = 'employer-test-token-12345'; $this->employer = User::create([ 'name' => 'John Employer', 'email' => 'employer_test@example.com', 'password' => bcrypt('password'), 'role' => 'employer', 'api_token' => $this->token, ]); // Create employer profile EmployerProfile::create([ 'user_id' => $this->employer->id, 'company_name' => 'John Ltd', 'phone' => '+971500000002', 'country' => 'United Arab Emirates', 'verification_status' => 'approved', 'language' => 'English', 'notifications' => true, ]); } /** * Test employer can login via API. */ public function test_employer_can_login() { $response = $this->postJson('/api/employers/login', [ 'email' => 'employer_test@example.com', 'password' => 'password', ]); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'message', 'data' => [ 'employer', 'token' ] ]); $this->assertNotNull($response->json('data.token')); } /** * Test employer can register via API. */ public function test_employer_can_register() { \Illuminate\Support\Facades\Storage::fake('local'); $fakeIdFile = \Illuminate\Http\UploadedFile::fake()->create('emirates_id.jpg', 800, 'image/jpeg'); // Step 1: Register $response = $this->postJson('/api/employers/register', [ 'company_name' => 'New Company Corp', 'name' => 'Alice Employer', 'email' => 'alice@example.com', 'phone' => '+971500000003', 'password' => 'Password@123', 'password_confirmation' => 'Password@123', ]); $response->assertStatus(201) ->assertJsonStructure([ 'success', 'message', 'email' ]); $this->assertDatabaseHas('users', [ 'email' => 'alice@example.com', 'role' => 'employer', ]); $this->assertDatabaseHas('employer_profiles', [ 'company_name' => 'Alice Employer Household', ]); // Step 2: Upload Emirates ID Front $uploadFrontResponse = $this->postJson('/api/employers/register/emirates-front', [ 'email' => 'alice@example.com', 'emirates_id_front' => $fakeIdFile, ]); $uploadFrontResponse->assertStatus(200) ->assertJsonStructure([ 'success', 'message', 'ocr_extracted_data' => [ 'emirates_id_number', ], 'email' ]); // Step 3: Upload Emirates ID Back $uploadBackResponse = $this->postJson('/api/employers/register/emirates-back', [ 'email' => 'alice@example.com', 'emirates_id_back' => $fakeIdFile, ]); $uploadBackResponse->assertStatus(200) ->assertJsonStructure([ 'success', 'message', 'ocr_extracted_data' => [ 'expiry_date', ], 'email' ]); $user = User::where('email', 'alice@example.com')->first(); $profile = EmployerProfile::where('user_id', $user->id)->first(); $this->assertNull($profile->emirates_id_front); $this->assertNotNull($profile->emirates_id_number); $this->assertNotNull($profile->emirates_id_expiry); } /** * Test employer can upload front and back side Emirates ID separately via distinct API endpoints. */ public function test_employer_can_upload_separate_emirates_id_front_and_back() { \Illuminate\Support\Facades\Storage::fake('local'); $frontFile = \Illuminate\Http\UploadedFile::fake()->create('front.jpg', 500, 'image/jpeg'); $backFile = \Illuminate\Http\UploadedFile::fake()->create('back.jpg', 500, 'image/jpeg'); // Create an employer user to upload for $user = User::create([ 'name' => 'Charlie Employer', 'email' => 'charlie@example.com', 'password' => bcrypt('password'), 'role' => 'employer', ]); // Upload Front $responseFront = $this->postJson('/api/employers/register/emirates-front', [ 'email' => 'charlie@example.com', 'emirates_id_front' => $frontFile, ]); $responseFront->assertStatus(200) ->assertJsonStructure([ 'success', 'message', 'ocr_extracted_data' => [ 'emirates_id_number', ], 'email' ]); $this->assertEquals('784-1988-5310327-2', $responseFront->json('ocr_extracted_data.emirates_id_number')); // Upload Back $responseBack = $this->postJson('/api/employers/register/emirates-back', [ 'email' => 'charlie@example.com', 'emirates_id_back' => $backFile, ]); $responseBack->assertStatus(200) ->assertJsonStructure([ 'success', 'message', 'ocr_extracted_data' => [ 'expiry_date', ], 'email' ]); $this->assertEquals('2028-04-11', $responseBack->json('ocr_extracted_data.expiry_date')); $profile = EmployerProfile::where('user_id', $user->id)->first(); $this->assertNotNull($profile); $this->assertEquals('784-1988-5310327-2', $profile->emirates_id_number); $this->assertEquals('2028-04-11', $profile->emirates_id_expiry); } /** * Test retrieving conversation list for an employer. */ public function test_employer_can_get_conversations() { // Create conversation $conversation = Conversation::create([ 'employer_id' => $this->employer->id, 'worker_id' => $this->worker->id, ]); // Create a message from worker Message::create([ 'conversation_id' => $conversation->id, 'sender_type' => 'worker', 'sender_id' => $this->worker->id, 'text' => 'Hello from candidate!', ]); // Request API $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->getJson('/api/employers/conversations'); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'data' => [ 'conversations' => [ '*' => [ 'id', 'worker_id', 'worker_name', 'nationality', 'salary', 'last_message', 'unread_count', 'sent_at', 'updated_at', ] ] ] ]); $this->assertEquals('Hello from candidate!', $response->json('data.conversations.0.last_message')); $this->assertEquals(1, $response->json('data.conversations.0.unread_count')); } /** * Test retrieving messages in a conversation and marking them as read. */ public function test_employer_can_get_messages_and_mark_as_read() { // Create conversation $conversation = Conversation::create([ 'employer_id' => $this->employer->id, 'worker_id' => $this->worker->id, ]); // Create message $message = Message::create([ 'conversation_id' => $conversation->id, 'sender_type' => 'worker', 'sender_id' => $this->worker->id, 'text' => 'Unread message from candidate', 'read_at' => null, ]); // Request API $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->getJson('/api/employers/conversations/' . $conversation->id . '/messages'); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'data' => [ 'conversation' => [ 'id', 'worker_name', 'nationality', 'salary', ], 'messages' => [ '*' => [ 'id', 'sender_type', 'text', 'time', 'read_at', 'created_at', ] ] ] ]); // Verify message marked as read in database $this->assertNotNull($message->fresh()->read_at); } /** * Test employer can send reply. */ public function test_employer_can_send_reply() { // Create conversation $conversation = Conversation::create([ 'employer_id' => $this->employer->id, 'worker_id' => $this->worker->id, ]); // Reply request $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->postJson('/api/employers/conversations/' . $conversation->id . '/messages', [ 'text' => 'Hello Candidate, when are you free for an interview?' ]); $response->assertStatus(201) ->assertJsonStructure([ 'success', 'message', 'data' => [ 'message' => [ 'id', 'sender_type', 'text', 'time', 'created_at', ] ] ]); // Verify message created in database $this->assertDatabaseHas('messages', [ 'conversation_id' => $conversation->id, 'sender_type' => 'employer', 'sender_id' => $this->employer->id, 'text' => 'Hello Candidate, when are you free for an interview?', ]); } /** * Test employer can start conversation. */ public function test_employer_can_start_conversation() { $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->postJson('/api/employers/conversations/start', [ 'worker_id' => $this->worker->id, ]); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'message', 'data' => [ 'conversation_id' ] ]); $this->assertDatabaseHas('conversations', [ 'employer_id' => $this->employer->id, 'worker_id' => $this->worker->id, ]); } /** * Test request fails without authorization token. */ public function test_unauthenticated_requests_are_blocked() { $response = $this->getJson('/api/employers/conversations'); $response->assertStatus(401); } /** * Test employer message triggers push notification. */ public function test_employer_message_triggers_push_notification() { \Illuminate\Support\Facades\Http::fake([ 'https://oauth2.googleapis.com/token' => \Illuminate\Support\Facades\Http::response(['access_token' => 'mocked-fcm-token'], 200), 'https://fcm.googleapis.com/*' => \Illuminate\Support\Facades\Http::response(['name' => 'projects/migrant-fe5a7/messages/mock-id'], 200), ]); // Set fcm_token on worker $this->worker->update(['fcm_token' => 'test-device-fcm-token-xyz']); // Create conversation $conversation = Conversation::create([ 'employer_id' => $this->employer->id, 'worker_id' => $this->worker->id, ]); // Reply request $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, ])->postJson('/api/employers/conversations/' . $conversation->id . '/messages', [ 'text' => 'Testing push notifications!' ]); $response->assertStatus(201); // Assert Http call was made to FCM endpoint \Illuminate\Support\Facades\Http::assertSent(function ($request) { return str_contains($request->url(), 'fcm.googleapis.com') && $request['message']['token'] === 'test-device-fcm-token-xyz' && $request['message']['notification']['body'] === 'Testing push notifications!'; }); } }