'John Worker', 'email' => 'worker@example.com', 'password' => bcrypt('password'), 'phone' => '971500000001', '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', ], $overrides)); } private function createTestEmployer(array $overrides = []): User { return User::create(array_merge([ 'name' => 'Alice Employer', 'email' => 'employer@example.com', 'password' => bcrypt('password'), 'role' => 'employer', 'api_token' => 'employer_api_token', 'subscription_status' => 'active', ], $overrides)); } public function test_worker_no_response_reminders() { Notification::fake(); $worker = $this->createTestWorker(); $employer = $this->createTestEmployer(); // 1. Create a pending direct Job Offer created exactly 14 days ago $offer = JobOffer::create([ 'employer_id' => $employer->id, 'worker_id' => $worker->id, 'work_date' => now()->addDays(5)->toDateString(), 'location' => 'Dubai Marina', 'salary' => 3000, 'status' => 'pending', ]); // Force created_at to 14 days ago $offer->created_at = now()->subDays(14); $offer->save(); // 2. Create a conversation where the last message is from employer, sent 14 days ago $conversation = Conversation::create([ 'employer_id' => $employer->id, 'worker_id' => $worker->id, ]); $message = Message::create([ 'conversation_id' => $conversation->id, 'sender_type' => 'App\Models\User', 'sender_id' => $employer->id, 'text' => 'Hello worker, are you interested?', ]); $message->created_at = now()->subDays(14); $message->save(); // Run scheduler Artisan::call('app:send-reminders'); // Assert Worker got WorkerNoResponseNotification for both Notification::assertSentTo( $worker, WorkerNoResponseNotification::class, function ($notification) use ($offer) { return $notification->type === 'worker_no_response_offer' && $notification->daysElapsed === 14 && $notification->entityId === $offer->id; } ); Notification::assertSentTo( $worker, WorkerNoResponseNotification::class, function ($notification) use ($message) { return $notification->type === 'worker_no_response_chat' && $notification->daysElapsed === 14 && $notification->entityId === $message->id; } ); // Assert reminder logs were created $this->assertDatabaseHas('reminder_logs', [ 'user_type' => get_class($worker), 'user_id' => $worker->id, 'event_type' => 'worker_no_response_offer', 'reminder_level' => '14_days', ]); $this->assertDatabaseHas('reminder_logs', [ 'user_type' => get_class($worker), 'user_id' => $worker->id, 'event_type' => 'worker_no_response_chat', 'reminder_level' => '14_days', ]); // Run again to verify duplicate prevention Notification::fake(); Artisan::call('app:send-reminders'); Notification::assertNothingSent(); } public function test_review_reminders_after_joining_date() { Notification::fake(); $worker = $this->createTestWorker(); $employer = $this->createTestEmployer(); $job = JobPost::create([ 'employer_id' => $employer->id, 'title' => 'Nanny', 'location' => 'Dubai', 'salary' => 2500, 'workers_needed' => 1, 'job_type' => 'Full Time', 'start_date' => now()->subMonths(4)->toDateString(), 'description' => 'Test job', 'status' => 'active', ]); // JobApplication with joining_confirmed_at exactly 3 months ago $app = JobApplication::create([ 'job_id' => $job->id, 'worker_id' => $worker->id, 'status' => 'hired', 'joining_confirmed_at' => now()->subMonths(3)->toDateString(), ]); // Run scheduler Artisan::call('app:send-reminders'); // Assert Worker got review reminder for employer Notification::assertSentTo( $worker, WorkerReviewReminderNotification::class, function ($notification) use ($app) { return $notification->employerName === 'Alice Employer' && $notification->applicationId === $app->id && $notification->reviewLevel === '3_months'; } ); // Assert Employer got review reminder for worker Notification::assertSentTo( $employer, EmployerReviewReminderNotification::class, function ($notification) use ($app) { return $notification->workerName === 'John Worker' && $notification->applicationId === $app->id && $notification->reviewLevel === '3_months'; } ); // Assert reminder logs were created $this->assertDatabaseHas('reminder_logs', [ 'user_type' => get_class($worker), 'user_id' => $worker->id, 'event_type' => 'review_employer_reminder', 'reminder_level' => '3_months', ]); $this->assertDatabaseHas('reminder_logs', [ 'user_type' => get_class($employer), 'user_id' => $employer->id, 'event_type' => 'review_worker_reminder', 'reminder_level' => '3_months', ]); // Run again to verify duplicate prevention Notification::fake(); Artisan::call('app:send-reminders'); Notification::assertNothingSent(); } public function test_worker_review_controller_eligibility_and_edit_window() { $worker = $this->createTestWorker(); $employer = $this->createTestEmployer(); $job = JobPost::create([ 'employer_id' => $employer->id, 'title' => 'Test Job', 'location' => 'Dubai', 'salary' => 2500, 'workers_needed' => 1, 'job_type' => 'Full Time', 'start_date' => now()->subMonths(4)->toDateString(), 'description' => 'Test Description', 'status' => 'active', ]); // 1. Cannot review before eligibility period (set eligibility to 3 months) $app = JobApplication::create([ 'job_id' => $job->id, 'worker_id' => $worker->id, 'status' => 'hired', 'joining_confirmed_at' => now()->subMonths(2)->toDateString(), ]); $response = $this->postJson('/api/workers/reviews', [ 'employer_id' => $employer->id, 'job_id' => $job->id, 'rating' => 5, 'comment' => 'Great employer!', ], [ 'Authorization' => 'Bearer worker_api_token' ]); $response->assertStatus(403); $response->assertJsonPath('success', false); $response->assertJsonPath('message', 'You can write a review only after 3 months from the joining date.'); // 2. Can review after eligibility period $app->joining_confirmed_at = now()->subMonths(3)->subDays(5)->toDateString(); $app->save(); $response = $this->postJson('/api/workers/reviews', [ 'employer_id' => $employer->id, 'job_id' => $job->id, 'rating' => 5, 'comment' => 'Great employer!', ], [ 'Authorization' => 'Bearer worker_api_token' ]); $response->assertStatus(201); $response->assertJsonPath('success', true); $reviewId = $response->json('data.review.id'); // 3. Edit review within 7 days $response = $this->putJson("/api/workers/reviews/{$reviewId}", [ 'rating' => 4, 'comment' => 'Good employer indeed.', ], [ 'Authorization' => 'Bearer worker_api_token' ]); $response->assertStatus(200); // 4. Cannot edit review after 7 days $review = EmployerReview::find($reviewId); $review->created_at = now()->subDays(8); $review->save(); $response = $this->putJson("/api/workers/reviews/{$reviewId}", [ 'rating' => 3, 'comment' => 'Okay employer.', ], [ 'Authorization' => 'Bearer worker_api_token' ]); $response->assertStatus(403); $response->assertJsonPath('success', false); $response->assertJsonPath('message', 'The 7-day edit window for this review has expired.'); } public function test_employer_review_controller_eligibility_and_edit_window() { $worker = $this->createTestWorker(); $employer = $this->createTestEmployer(); $job = JobPost::create([ 'employer_id' => $employer->id, 'title' => 'Test Job', 'location' => 'Dubai', 'salary' => 2500, 'workers_needed' => 1, 'job_type' => 'Full Time', 'start_date' => now()->subMonths(4)->toDateString(), 'description' => 'Test Description', 'status' => 'active', ]); // 1. Cannot review before eligibility period (set eligibility to 3 months) $app = JobApplication::create([ 'job_id' => $job->id, 'worker_id' => $worker->id, 'status' => 'hired', 'joining_confirmed_at' => now()->subMonths(2)->toDateString(), ]); $response = $this->postJson('/api/employers/reviews', [ 'worker_id' => $worker->id, 'rating' => 5, 'comment' => 'Great worker!', ], [ 'Authorization' => 'Bearer employer_api_token' ]); $response->assertStatus(403); $response->assertJsonPath('success', false); $response->assertJsonPath('message', 'You can write a review only after 3 months from the joining date.'); // 2. Can review after eligibility period $app->joining_confirmed_at = now()->subMonths(3)->subDays(5)->toDateString(); $app->save(); $response = $this->postJson('/api/employers/reviews', [ 'worker_id' => $worker->id, 'rating' => 5, 'comment' => 'Great worker!', ], [ 'Authorization' => 'Bearer employer_api_token' ]); $response->assertStatus(201); $response->assertJsonPath('success', true); $reviewId = $response->json('data.review.id'); // 3. Edit review within 7 days $response = $this->putJson("/api/employers/reviews/{$reviewId}", [ 'rating' => 4, 'comment' => 'Good worker indeed.', ], [ 'Authorization' => 'Bearer employer_api_token' ]); $response->assertStatus(200); // 4. Cannot edit review after 7 days $review = Review::find($reviewId); $review->created_at = now()->subDays(8); $review->save(); $response = $this->putJson("/api/employers/reviews/{$reviewId}", [ 'rating' => 3, 'comment' => 'Okay worker.', ], [ 'Authorization' => 'Bearer employer_api_token' ]); $response->assertStatus(403); $response->assertJsonPath('success', false); $response->assertJsonPath('message', 'The 7-day edit window for this review has expired.'); } }