withoutMiddleware(EnsurePlatformSession::class); $this->user = User::create([ 'public_id' => 'test-user-transcript', 'name' => 'Test User', 'email' => 'transcript@example.com', ]); $this->organization = Organization::create([ 'owner_ref' => $this->user->public_id, 'name' => 'Test Org', 'slug' => 'test-org-transcript', 'timezone' => 'UTC', 'settings' => ['onboarded' => true], ]); Member::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->user->public_id, 'role' => 'owner', ]); } public function test_finalize_and_summarize_merges_chat_and_captions_before_dispatching_summary(): void { Queue::fake(); [$session] = $this->liveSession(); Message::create([ 'owner_ref' => $this->user->public_id, 'session_id' => $session->id, 'sender_ref' => $this->user->public_id, 'sender_name' => 'Host', 'body' => 'Let us review the launch timeline.', 'type' => 'public', ]); $service = app(TranscriptService::class); $transcript = $service->getOrCreateForSession($session); $service->appendSegment($transcript, 'Host', 'We should ship the dashboard next week.'); $recording = Recording::create([ 'owner_ref' => $this->user->public_id, 'session_id' => $session->id, 'status' => 'ready', 'layout' => 'gallery', 'started_by_ref' => $this->user->public_id, 'started_at' => now()->subMinutes(30), 'ended_at' => now(), ]); $service->finalizeAndSummarize($session, $recording); $transcript->refresh(); $this->assertSame($recording->id, $transcript->recording_id); $this->assertStringContainsString('Let us review the launch timeline.', $transcript->content); $this->assertStringContainsString('We should ship the dashboard next week.', $transcript->content); Queue::assertPushed(GenerateAiSummary::class, function (GenerateAiSummary $job) use ($transcript) { return $job->transcriptId === $transcript->id; }); } public function test_caption_append_does_not_queue_summary_job(): void { Queue::fake(); [$session] = $this->liveSession(); $service = app(TranscriptService::class); $transcript = $service->getOrCreateForSession($session); $service->appendSegment($transcript, 'Host', 'Opening remarks.'); Queue::assertNothingPushed(); } public function test_ending_session_queues_ai_summary_without_recording(): void { Queue::fake(); $room = Room::create([ 'organization_id' => $this->organization->id, 'owner_ref' => $this->user->public_id, 'host_user_ref' => $this->user->public_id, 'title' => 'No recording meeting', 'type' => 'instant', 'status' => 'live', 'timezone' => 'UTC', 'settings' => [ 'auto_ai_summary' => true, 'live_captions' => true, ], ]); $session = Session::create([ 'room_id' => $room->id, 'owner_ref' => $this->user->public_id, 'media_room_name' => 'room-'.$room->uuid, 'status' => 'live', 'started_at' => now()->subMinutes(15), ]); Participant::create([ 'owner_ref' => $this->user->public_id, 'session_id' => $session->id, 'user_ref' => $this->user->public_id, 'role' => 'host', 'display_name' => 'Host', 'status' => 'joined', 'joined_at' => now()->subMinutes(15), ]); Message::create([ 'owner_ref' => $this->user->public_id, 'session_id' => $session->id, 'sender_ref' => $this->user->public_id, 'sender_name' => 'Host', 'body' => 'We agreed to ship Friday.', 'type' => 'public', ]); app(\App\Services\Meet\SessionService::class)->end($session, $this->user->public_id); $session->refresh(); $this->assertSame('ended', $session->status); $this->assertNotNull($session->ended_at); $transcript = $session->transcripts()->latest()->first(); $this->assertNotNull($transcript); $this->assertStringContainsString('We agreed to ship Friday.', (string) $transcript->content); Queue::assertPushed(GenerateAiSummary::class, function (GenerateAiSummary $job) use ($transcript) { return $job->transcriptId === $transcript->id; }); } public function test_ending_session_skips_summary_when_auto_ai_disabled(): void { Queue::fake(); config(['meet.ai.auto_summarize' => false]); $room = Room::create([ 'organization_id' => $this->organization->id, 'owner_ref' => $this->user->public_id, 'host_user_ref' => $this->user->public_id, 'title' => 'No AI meeting', 'type' => 'instant', 'status' => 'live', 'timezone' => 'UTC', // Explicit false in settings (setting() reads room.settings first) 'settings' => ['auto_ai_summary' => false], ]); $session = Session::create([ 'room_id' => $room->id, 'owner_ref' => $this->user->public_id, 'media_room_name' => 'room-'.$room->uuid, 'status' => 'live', 'started_at' => now()->subMinutes(5), ]); // Room::setting falls back to default_settings when key missing; with explicit false // AND config false, shouldAutoSummarize must be false. // Note: shouldAutoSummarize is (room setting || config) — explicit false || false = false. $this->assertFalse(app(TranscriptService::class)->shouldAutoSummarize($session)); app(\App\Services\Meet\SessionService::class)->end($session, $this->user->public_id); Queue::assertNotPushed(GenerateAiSummary::class); $this->assertSame(0, $session->fresh()->transcripts()->count()); } public function test_caption_segments_collapse_progressive_duplicates(): void { Queue::fake(); [$session] = $this->liveSession(); $service = app(TranscriptService::class); $transcript = $service->getOrCreateForSession($session); $service->appendSegment($transcript, 'Trypose', 'Yeah'); $service->appendSegment($transcript, 'Trypose', 'Yeah I want to ask'); $service->appendSegment($transcript, 'Trypose', 'Yeah I want to ask question please'); $service->appendSegment($transcript, 'Trypose', 'Okay'); $service->appendSegment($transcript, 'Trypose', 'Okay'); $transcript->refresh(); $this->assertSame([ 'Trypose: Yeah I want to ask question please', 'Trypose: Okay', ], explode("\n", $transcript->content)); } /** @return array{0: Session, 1: Participant} */ protected function liveSession(): array { $room = Room::create([ 'organization_id' => $this->organization->id, 'owner_ref' => $this->user->public_id, 'host_user_ref' => $this->user->public_id, 'title' => 'Transcript meeting', 'type' => 'instant', 'status' => 'live', 'timezone' => 'UTC', 'settings' => ['live_captions' => true], ]); $session = Session::create([ 'room_id' => $room->id, 'owner_ref' => $this->user->public_id, 'media_room_name' => 'room-'.$room->uuid, 'status' => 'ended', 'started_at' => now()->subMinutes(30), 'ended_at' => now(), ]); $participant = Participant::create([ 'owner_ref' => $this->user->public_id, 'session_id' => $session->id, 'user_ref' => $this->user->public_id, 'role' => 'host', 'display_name' => 'Host', 'status' => 'joined', 'joined_at' => now()->subMinutes(30), ]); return [$session, $participant]; } }