diff --git a/app/Http/Controllers/InterviewController.php b/app/Http/Controllers/InterviewController.php index a71007f..7aae89d 100644 --- a/app/Http/Controllers/InterviewController.php +++ b/app/Http/Controllers/InterviewController.php @@ -32,6 +32,15 @@ public function index() } $interviews = $user->getAccessibleInterviews(); + + // Auto-terminate call session if interview timeline has expired + foreach ($interviews as $interview) { + if ($interview->isExpired() && $interview->call_status === 'active') { + $interview->update(['call_status' => 'ended', 'active_peers' => []]); + $interview->call_status = 'ended'; + } + } + $interviewers = User::orderBy('name', 'asc')->get(); return view('interview.index', compact('interviews', 'interviewers')); @@ -49,6 +58,13 @@ public function show($id) } $interview = Interview::findOrFail($id); + + // Auto-terminate call session if interview timeline has expired + if ($interview->isExpired() && $interview->call_status === 'active') { + $interview->update(['call_status' => 'ended', 'active_peers' => []]); + $interview->call_status = 'ended'; + } + $interviewers = User::orderBy('name', 'asc')->get(); return view('interview.show', compact('interview', 'interviewers')); @@ -625,7 +641,8 @@ public function getActiveCalls(Request $request) // Auto-terminate call session if interview timeline has expired if ($interview->isExpired()) { if ($interview->call_status === 'active') { - $interview->update(['call_status' => 'ended']); + $interview->update(['call_status' => 'ended', 'active_peers' => []]); + $interview->call_status = 'ended'; } continue; } diff --git a/app/Models/Interview.php b/app/Models/Interview.php index a81f96b..22bdc45 100644 --- a/app/Models/Interview.php +++ b/app/Models/Interview.php @@ -70,6 +70,18 @@ public function isExpired(): bool return now()->greaterThan($this->expires_at); } + /** + * Check if the interview call is active and unexpired. + */ + public function isCallActive(): bool + { + if ($this->isExpired() || in_array($this->status, ['completed', 'expired'])) { + return false; + } + + return $this->call_status === 'active'; + } + /** * Check if the current time is within the interview timeline. */ diff --git a/resources/views/components/interview/call-controls.blade.php b/resources/views/components/interview/call-controls.blade.php index 35804d7..cc2f890 100644 --- a/resources/views/components/interview/call-controls.blade.php +++ b/resources/views/components/interview/call-controls.blade.php @@ -10,11 +10,11 @@ - @if($interview->call_status === 'ended') + @if($interview->isExpired() || $interview->call_status === 'ended') Call Ended - @elseif($interview->call_status === 'active') + @elseif(!$interview->isExpired() && $interview->call_status === 'active') Join Call diff --git a/resources/views/components/interview/candidate-header.blade.php b/resources/views/components/interview/candidate-header.blade.php index d621e83..e58d82f 100644 --- a/resources/views/components/interview/candidate-header.blade.php +++ b/resources/views/components/interview/candidate-header.blade.php @@ -76,7 +76,7 @@ Expired @elseif($interview->status === 'completed') Completed - @elseif($interview->call_status === 'active') + @elseif(!$interview->isExpired() && $interview->call_status === 'active') Call in progress @elseif($interview->isActiveTimeline()) Timeline live diff --git a/resources/views/components/interview/session-row.blade.php b/resources/views/components/interview/session-row.blade.php index 34936e1..be312cc 100644 --- a/resources/views/components/interview/session-row.blade.php +++ b/resources/views/components/interview/session-row.blade.php @@ -4,6 +4,10 @@ @php $metrics = $interview->proctor_metrics; + if ($interview->isExpired() && $interview->call_status === 'active') { + $interview->update(['call_status' => 'ended', 'active_peers' => []]); + $interview->call_status = 'ended'; + } @endphp @@ -36,7 +40,7 @@ EXPIRED @elseif($interview->status === 'completed') COMPLETED - @elseif($interview->call_status === 'active') + @elseif(!$interview->isExpired() && $interview->call_status === 'active') CALL IN PROGRESS @elseif($interview->isActiveTimeline()) TIMELINE LIVE @@ -71,7 +75,7 @@ - @if($interview->call_status === 'active') + @if(!$interview->isExpired() && $interview->call_status === 'active') Join Active Call diff --git a/tests/Feature/CandidateInterviewPortalTest.php b/tests/Feature/CandidateInterviewPortalTest.php index fe71bbb..e19aeb6 100644 --- a/tests/Feature/CandidateInterviewPortalTest.php +++ b/tests/Feature/CandidateInterviewPortalTest.php @@ -416,4 +416,49 @@ public function test_tab_switch_logging_and_external_ai_detection_behavior(): vo ->assertJsonFragment(['type' => 'talking_on_phone']) ->assertJsonFragment(['type' => 'reading_external_device']); } + + public function test_expired_interview_automatically_ends_call_status_when_rendering_view(): void + { + $admin = User::create([ + 'name' => 'Admin Test', + 'email' => 'admintest@example.com', + 'role' => 'admin', + ]); + + // Create an expired interview that had active call_status in DB + $expiredInterview = Interview::create([ + 'candidate_name' => 'Expired Candidate', + 'candidate_email' => 'expired@example.com', + 'candidate_phone' => '1231231234', + 'temp_password' => 'Pass-111222', + 'expires_at' => now()->subHour(), + 'language' => 'python', + 'status' => 'scheduled', + 'call_status' => 'active', + 'active_peers' => [['peer_id' => 'peer_1', 'last_seen' => now()->subHour()->timestamp]], + 'submission_unique_id' => 'expired-cand_1231231234_1752000100', + ]); + + $this->assertTrue($expiredInterview->isExpired()); + $this->assertFalse($expiredInterview->isCallActive()); + + // 1. Loading index page view should automatically end the call status + $response = $this->actingAs($admin)->get(route('interview.index')); + $response->assertStatus(200); + $response->assertSee('EXPIRED'); + $response->assertDontSee('Join Active Call'); + + $expiredInterview->refresh(); + $this->assertEquals('ended', $expiredInterview->call_status); + $this->assertEmpty($expiredInterview->active_peers); + + // 2. Reset back to active and test show page view + $expiredInterview->update(['call_status' => 'active']); + $showResp = $this->actingAs($admin)->get(route('interview.show', $expiredInterview->id)); + $showResp->assertStatus(200); + $showResp->assertSee('Call Ended'); + + $expiredInterview->refresh(); + $this->assertEquals('ended', $expiredInterview->call_status); + } }