- integrate spatie/icalendar-generator for candidate & panelist .ics invitations (RSVP & 15m alerts)
- add StoreInterviewRequest, ScheduleInterviewDto, and invitation/reminder queued actions
- add migration for job_title, round, description, and reminder_sent_at on interviews table
- style datetime-local picker indicators to pure white on dark input backgrounds
- support button loading spinners with :showLoader="true" and loadingText
- auto-open schedule drawer on validation failure with inline @error alerts and old() bindings
170 lines
6.3 KiB
PHP
170 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Mail\CandidateInterviewInvitationMail;
|
|
use App\Mail\InterviewerAssessmentNotificationMail;
|
|
use App\Mail\InterviewReminderMail;
|
|
use App\Models\Interview;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Tests\TestCase;
|
|
|
|
class ScheduleInterviewWithInvitationsTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
}
|
|
|
|
public function test_scheduling_interview_creates_record_and_queues_candidate_and_interviewer_invitations(): void
|
|
{
|
|
Mail::fake();
|
|
|
|
$hr = User::create([
|
|
'name' => 'HR Manager',
|
|
'email' => 'hr@company.com',
|
|
'role' => 'hr',
|
|
]);
|
|
|
|
$interviewer1 = User::create([
|
|
'name' => 'Tech Lead',
|
|
'email' => 'techlead@company.com',
|
|
'role' => 'user',
|
|
]);
|
|
|
|
$interviewer2 = User::create([
|
|
'name' => 'Senior Engineer',
|
|
'email' => 'srengineer@company.com',
|
|
'role' => 'user',
|
|
]);
|
|
|
|
$response = $this->actingAs($hr)->post(route('interview.store'), [
|
|
'candidate_name' => 'Soumyadeep Mondal',
|
|
'candidate_email' => 'soumyadeep@example.com',
|
|
'candidate_phone' => '9876543210',
|
|
'job_title' => 'AIML (Computer Vision & Agentic AI) Engineer',
|
|
'round' => 'R1',
|
|
'description' => 'Live coding session on Agentic workflows',
|
|
'scheduled_at' => now()->addDays(2)->format('Y-m-d\TH:i'),
|
|
'valid_hours' => 2,
|
|
'language' => 'python',
|
|
'assigned_interviewers' => [$interviewer1->id, $interviewer2->id],
|
|
]);
|
|
|
|
$response->assertRedirect(route('interview.index'));
|
|
|
|
// Database assertion
|
|
$this->assertDatabaseHas('interviews', [
|
|
'candidate_name' => 'Soumyadeep Mondal',
|
|
'candidate_email' => 'soumyadeep@example.com',
|
|
'job_title' => 'AIML (Computer Vision & Agentic AI) Engineer',
|
|
'round' => 'R1',
|
|
'description' => 'Live coding session on Agentic workflows',
|
|
'language' => 'python',
|
|
'status' => 'scheduled',
|
|
]);
|
|
|
|
$interview = Interview::where('candidate_email', 'soumyadeep@example.com')->first();
|
|
$this->assertNotNull($interview);
|
|
|
|
// Candidate mail assertion
|
|
Mail::assertQueued(CandidateInterviewInvitationMail::class, function ($mail) use ($interview) {
|
|
return $mail->hasTo('soumyadeep@example.com')
|
|
&& $mail->interview->id === $interview->id
|
|
&& ! empty($mail->icsContent);
|
|
});
|
|
|
|
// Interviewers mail assertions
|
|
Mail::assertQueued(InterviewerAssessmentNotificationMail::class, function ($mail) use ($interviewer1) {
|
|
return $mail->hasTo('techlead@company.com') && $mail->interviewer->id === $interviewer1->id;
|
|
});
|
|
|
|
Mail::assertQueued(InterviewerAssessmentNotificationMail::class, function ($mail) use ($interviewer2) {
|
|
return $mail->hasTo('srengineer@company.com') && $mail->interviewer->id === $interviewer2->id;
|
|
});
|
|
}
|
|
|
|
public function test_send_interview_reminders_command_dispatches_15min_advance_emails(): void
|
|
{
|
|
Mail::fake();
|
|
|
|
$interviewer = User::create([
|
|
'name' => 'Panelist User',
|
|
'email' => 'panelist@company.com',
|
|
'role' => 'user',
|
|
]);
|
|
|
|
// Interview scheduled 10 minutes from now (within 20m window)
|
|
$interview = Interview::create([
|
|
'candidate_name' => 'Upcoming Candidate',
|
|
'candidate_email' => 'upcoming@example.com',
|
|
'candidate_phone' => '1122334455',
|
|
'job_title' => 'Backend Architect',
|
|
'round' => 'Technical Round',
|
|
'temp_password' => 'Pass-778899',
|
|
'scheduled_at' => now()->addMinutes(10),
|
|
'expires_at' => now()->addHours(2),
|
|
'language' => 'php',
|
|
'status' => 'scheduled',
|
|
'assigned_interviewers' => [$interviewer->id],
|
|
'submission_unique_id' => 'upcoming-candidate_1122334455_1752000099',
|
|
'reminder_sent_at' => null,
|
|
]);
|
|
|
|
$this->artisan('interview:send-reminders')
|
|
->expectsOutputToContain('Successfully processed and dispatched reminders for 1 interview session(s).')
|
|
->assertExitCode(0);
|
|
|
|
// Assert reminder email sent to candidate
|
|
Mail::assertQueued(InterviewReminderMail::class, function ($mail) {
|
|
return $mail->hasTo('upcoming@example.com') && $mail->recipientType === 'candidate';
|
|
});
|
|
|
|
// Assert reminder email sent to interviewer
|
|
Mail::assertQueued(InterviewReminderMail::class, function ($mail) {
|
|
return $mail->hasTo('panelist@company.com') && $mail->recipientType === 'interviewer';
|
|
});
|
|
|
|
$interview->refresh();
|
|
$this->assertNotNull($interview->reminder_sent_at);
|
|
}
|
|
|
|
public function test_scheduling_validation_errors_render_open_drawer_and_error_directives(): void
|
|
{
|
|
$this->withMiddleware();
|
|
|
|
$hr = User::create([
|
|
'name' => 'HR Manager',
|
|
'email' => 'hr@company.com',
|
|
'role' => 'hr',
|
|
]);
|
|
|
|
$response = $this->actingAs($hr)
|
|
->from(route('interview.index'))
|
|
->followingRedirects()
|
|
->post(route('interview.store'), [
|
|
'candidate_name' => '',
|
|
'candidate_email' => 'invalid-email-address',
|
|
'candidate_phone' => '',
|
|
'job_title' => '',
|
|
'valid_hours' => 2,
|
|
'language' => 'python',
|
|
'description' => 'Test instructions',
|
|
]);
|
|
|
|
$response->assertOk();
|
|
$response->assertSee('create-interview-drawer');
|
|
$response->assertSee('active');
|
|
$response->assertSee('Please correct the following errors:');
|
|
$response->assertSee('The candidate name is required.');
|
|
$response->assertSee('Please provide a valid candidate email address.');
|
|
$response->assertSee('The candidate phone number is required.');
|
|
$response->assertSee('The job title / position is required.');
|
|
$response->assertSee('Test instructions');
|
|
}
|
|
}
|